add prependSystemContext and appendSystemContext to before_prompt_build (fixes #35131) (#35177)

Merged via squash.

Prepared head SHA: d9a2869ad6
Co-authored-by: maweibin <18023423+maweibin@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
maweibin
2026-03-06 02:06:59 +08:00
committed by GitHub
parent 174eeea76c
commit 09c68f8f0e
11 changed files with 265 additions and 11 deletions

View File

@@ -7,6 +7,7 @@
* 3. before_agent_start remains a legacy compatibility fallback
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import { joinPresentTextSegments } from "../shared/text/join-segments.js";
import { createHookRunner } from "./hooks.js";
import { addTestHook, TEST_PLUGIN_AGENT_CTX } from "./hooks.test-helpers.js";
import { createEmptyPluginRegistry, type PluginRegistry } from "./registry.js";
@@ -154,9 +155,10 @@ describe("model override pipeline wiring", () => {
{ prompt: "test", messages: [{ role: "user", content: "x" }] as unknown[] },
stubCtx,
);
const prependContext = [promptBuild?.prependContext, legacy?.prependContext]
.filter((value): value is string => Boolean(value))
.join("\n\n");
const prependContext = joinPresentTextSegments([
promptBuild?.prependContext,
legacy?.prependContext,
]);
expect(prependContext).toBe("new context\n\nlegacy context");
});

View File

@@ -72,4 +72,33 @@ describe("phase hooks merger", () => {
expect(result?.prependContext).toBe("context A\n\ncontext B");
expect(result?.systemPrompt).toBe("system A");
});
it("before_prompt_build concatenates prependSystemContext and appendSystemContext", async () => {
addTypedHook(
registry,
"before_prompt_build",
"first",
() => ({
prependSystemContext: "prepend A",
appendSystemContext: "append A",
}),
10,
);
addTypedHook(
registry,
"before_prompt_build",
"second",
() => ({
prependSystemContext: "prepend B",
appendSystemContext: "append B",
}),
1,
);
const runner = createHookRunner(registry);
const result = await runner.runBeforePromptBuild({ prompt: "test", messages: [] }, {});
expect(result?.prependSystemContext).toBe("prepend A\n\nprepend B");
expect(result?.appendSystemContext).toBe("append A\n\nappend B");
});
});

View File

@@ -5,6 +5,7 @@
* error handling, priority ordering, and async support.
*/
import { concatOptionalTextSegments } from "../shared/text/join-segments.js";
import type { PluginRegistry } from "./registry.js";
import type {
PluginHookAfterCompactionEvent,
@@ -140,10 +141,18 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
next: PluginHookBeforePromptBuildResult,
): PluginHookBeforePromptBuildResult => ({
systemPrompt: next.systemPrompt ?? acc?.systemPrompt,
prependContext:
acc?.prependContext && next.prependContext
? `${acc.prependContext}\n\n${next.prependContext}`
: (next.prependContext ?? acc?.prependContext),
prependContext: concatOptionalTextSegments({
left: acc?.prependContext,
right: next.prependContext,
}),
prependSystemContext: concatOptionalTextSegments({
left: acc?.prependSystemContext,
right: next.prependSystemContext,
}),
appendSystemContext: concatOptionalTextSegments({
left: acc?.appendSystemContext,
right: next.appendSystemContext,
}),
});
const mergeSubagentSpawningResult = (

View File

@@ -369,6 +369,16 @@ export type PluginHookBeforePromptBuildEvent = {
export type PluginHookBeforePromptBuildResult = {
systemPrompt?: string;
prependContext?: string;
/**
* Prepended to the agent system prompt so providers can cache it (e.g. prompt caching).
* Use for static plugin guidance instead of prependContext to avoid per-turn token cost.
*/
prependSystemContext?: string;
/**
* Appended to the agent system prompt so providers can cache it (e.g. prompt caching).
* Use for static plugin guidance instead of prependContext to avoid per-turn token cost.
*/
appendSystemContext?: string;
};
// before_agent_start hook (legacy compatibility: combines both phases)