fix(coding-agent): chain tool_result extension patches

fixes #1280
This commit is contained in:
Mario Zechner 2026-02-06 11:48:51 +01:00
parent 961d3aacbc
commit 2668326e05
5 changed files with 162 additions and 13 deletions

View file

@ -399,6 +399,98 @@ describe("ExtensionRunner", () => {
});
});
describe("tool_result chaining", () => {
it("chains content modifications across handlers", async () => {
const extCode1 = `
export default function(pi) {
pi.on("tool_result", async (event) => {
return {
content: [...event.content, { type: "text", text: "ext1" }],
};
});
}
`;
const extCode2 = `
export default function(pi) {
pi.on("tool_result", async (event) => {
return {
content: [...event.content, { type: "text", text: "ext2" }],
};
});
}
`;
fs.writeFileSync(path.join(extensionsDir, "tool-result-1.ts"), extCode1);
fs.writeFileSync(path.join(extensionsDir, "tool-result-2.ts"), extCode2);
const result = await discoverAndLoadExtensions([], tempDir, tempDir);
const runner = new ExtensionRunner(result.extensions, result.runtime, tempDir, sessionManager, modelRegistry);
const chained = await runner.emitToolResult({
type: "tool_result",
toolName: "my_tool",
toolCallId: "call-1",
input: {},
content: [{ type: "text", text: "base" }],
details: { initial: true },
isError: false,
});
expect(chained).toBeDefined();
const chainedContent = chained?.content;
expect(chainedContent).toBeDefined();
expect(chainedContent![0]).toEqual({ type: "text", text: "base" });
expect(chainedContent).toHaveLength(3);
const appendedText = chainedContent!
.slice(1)
.filter((item): item is { type: "text"; text: string } => item.type === "text")
.map((item) => item.text);
expect(appendedText.sort()).toEqual(["ext1", "ext2"]);
});
it("preserves previous modifications when later handlers return partial patches", async () => {
const extCode1 = `
export default function(pi) {
pi.on("tool_result", async () => {
return {
content: [{ type: "text", text: "first" }],
details: { source: "ext1" },
};
});
}
`;
const extCode2 = `
export default function(pi) {
pi.on("tool_result", async () => {
return {
isError: true,
};
});
}
`;
fs.writeFileSync(path.join(extensionsDir, "tool-result-partial-1.ts"), extCode1);
fs.writeFileSync(path.join(extensionsDir, "tool-result-partial-2.ts"), extCode2);
const result = await discoverAndLoadExtensions([], tempDir, tempDir);
const runner = new ExtensionRunner(result.extensions, result.runtime, tempDir, sessionManager, modelRegistry);
const chained = await runner.emitToolResult({
type: "tool_result",
toolName: "my_tool",
toolCallId: "call-2",
input: {},
content: [{ type: "text", text: "base" }],
details: { initial: true },
isError: false,
});
expect(chained).toEqual({
content: [{ type: "text", text: "first" }],
details: { source: "ext1" },
isError: true,
});
});
});
describe("hasHandlers", () => {
it("returns true when handlers exist for event type", async () => {
const extCode = `