fix(coding-agent): preserve run prompt during tool refresh
closes #6162
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
import { fauxAssistantMessage, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import { Type } from "typebox";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ExtensionFactory } from "../../../src/index.ts";
|
||||
import { createHarness } from "../harness.ts";
|
||||
|
||||
describe("extension active tools next-turn refresh", () => {
|
||||
it("applies pi.setActiveTools before the next provider request in the same run", async () => {
|
||||
const extensionFactories: ExtensionFactory[] = [
|
||||
(pi) => {
|
||||
pi.registerTool({
|
||||
name: "switch_tools",
|
||||
label: "Switch Tools",
|
||||
description: "Switch the active extension tool set",
|
||||
promptSnippet: "Switch to the next extension tool",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => {
|
||||
pi.setActiveTools(["after_switch"]);
|
||||
return {
|
||||
content: [{ type: "text", text: "switched" }],
|
||||
details: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "after_switch",
|
||||
label: "After Switch",
|
||||
description: "Tool that should be available after switching",
|
||||
promptSnippet: "Run after the active tool set changes",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => ({
|
||||
content: [{ type: "text", text: "after" }],
|
||||
details: {},
|
||||
}),
|
||||
});
|
||||
},
|
||||
];
|
||||
const harness = await createHarness({
|
||||
extensionFactories,
|
||||
});
|
||||
|
||||
try {
|
||||
harness.session.setActiveToolsByName(["switch_tools"]);
|
||||
|
||||
const providerToolNames: string[][] = [];
|
||||
harness.setResponses([
|
||||
(context) => {
|
||||
providerToolNames.push((context.tools ?? []).map((tool) => tool.name).sort());
|
||||
return fauxAssistantMessage(fauxToolCall("switch_tools", {}), { stopReason: "toolUse" });
|
||||
},
|
||||
(context) => {
|
||||
providerToolNames.push((context.tools ?? []).map((tool) => tool.name).sort());
|
||||
return fauxAssistantMessage("done");
|
||||
},
|
||||
]);
|
||||
|
||||
expect(harness.session.getActiveToolNames()).toEqual(["switch_tools"]);
|
||||
|
||||
await harness.session.prompt("start");
|
||||
|
||||
expect(harness.session.getActiveToolNames()).toEqual(["after_switch"]);
|
||||
expect(providerToolNames).toEqual([["switch_tools"], ["after_switch"]]);
|
||||
} finally {
|
||||
harness.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves before_agent_start system prompt overrides when tools change mid-run", async () => {
|
||||
const extensionFactories: ExtensionFactory[] = [
|
||||
(pi) => {
|
||||
pi.on("before_agent_start", async (event) => ({
|
||||
systemPrompt: `${event.systemPrompt}\n\nkeep this run override`,
|
||||
}));
|
||||
|
||||
pi.registerTool({
|
||||
name: "switch_tools",
|
||||
label: "Switch Tools",
|
||||
description: "Switch the active extension tool set",
|
||||
promptSnippet: "Switch to the next extension tool",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => {
|
||||
pi.setActiveTools(["after_switch"]);
|
||||
return {
|
||||
content: [{ type: "text", text: "switched" }],
|
||||
details: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "after_switch",
|
||||
label: "After Switch",
|
||||
description: "Tool that should be available after switching",
|
||||
promptSnippet: "Run after the active tool set changes",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => ({
|
||||
content: [{ type: "text", text: "after" }],
|
||||
details: {},
|
||||
}),
|
||||
});
|
||||
},
|
||||
];
|
||||
const harness = await createHarness({
|
||||
extensionFactories,
|
||||
});
|
||||
|
||||
try {
|
||||
harness.session.setActiveToolsByName(["switch_tools"]);
|
||||
|
||||
const providerSystemPrompts: string[] = [];
|
||||
const providerToolNames: string[][] = [];
|
||||
harness.setResponses([
|
||||
(context) => {
|
||||
providerSystemPrompts.push(context.systemPrompt ?? "");
|
||||
providerToolNames.push((context.tools ?? []).map((tool) => tool.name).sort());
|
||||
return fauxAssistantMessage(fauxToolCall("switch_tools", {}), { stopReason: "toolUse" });
|
||||
},
|
||||
(context) => {
|
||||
providerSystemPrompts.push(context.systemPrompt ?? "");
|
||||
providerToolNames.push((context.tools ?? []).map((tool) => tool.name).sort());
|
||||
return fauxAssistantMessage("done");
|
||||
},
|
||||
]);
|
||||
|
||||
await harness.session.prompt("start");
|
||||
|
||||
expect(providerToolNames).toEqual([["switch_tools"], ["after_switch"]]);
|
||||
expect(providerSystemPrompts).toHaveLength(2);
|
||||
expect(providerSystemPrompts[0]).toContain("keep this run override");
|
||||
expect(providerSystemPrompts[1]).toContain("keep this run override");
|
||||
} finally {
|
||||
harness.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
import { fauxAssistantMessage, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import { Type } from "typebox";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ExtensionFactory } from "../../../src/index.ts";
|
||||
import { createHarness } from "../harness.ts";
|
||||
|
||||
describe("extension active tools next-turn refresh", () => {
|
||||
it("applies pi.setActiveTools before the next provider request in the same run", async () => {
|
||||
const extensionFactories: ExtensionFactory[] = [
|
||||
(pi) => {
|
||||
pi.registerTool({
|
||||
name: "switch_tools",
|
||||
label: "Switch Tools",
|
||||
description: "Switch the active extension tool set",
|
||||
promptSnippet: "Switch to the next extension tool",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => {
|
||||
pi.setActiveTools(["after_switch"]);
|
||||
return {
|
||||
content: [{ type: "text", text: "switched" }],
|
||||
details: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "after_switch",
|
||||
label: "After Switch",
|
||||
description: "Tool that should be available after switching",
|
||||
promptSnippet: "Run after the active tool set changes",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => ({
|
||||
content: [{ type: "text", text: "after" }],
|
||||
details: {},
|
||||
}),
|
||||
});
|
||||
},
|
||||
];
|
||||
const harness = await createHarness({
|
||||
extensionFactories,
|
||||
});
|
||||
|
||||
try {
|
||||
harness.session.setActiveToolsByName(["switch_tools"]);
|
||||
|
||||
const providerToolNames: string[][] = [];
|
||||
harness.setResponses([
|
||||
(context) => {
|
||||
providerToolNames.push((context.tools ?? []).map((tool) => tool.name).sort());
|
||||
return fauxAssistantMessage(fauxToolCall("switch_tools", {}), { stopReason: "toolUse" });
|
||||
},
|
||||
(context) => {
|
||||
providerToolNames.push((context.tools ?? []).map((tool) => tool.name).sort());
|
||||
return fauxAssistantMessage("done");
|
||||
},
|
||||
]);
|
||||
|
||||
expect(harness.session.getActiveToolNames()).toEqual(["switch_tools"]);
|
||||
|
||||
await harness.session.prompt("start");
|
||||
|
||||
expect(harness.session.getActiveToolNames()).toEqual(["after_switch"]);
|
||||
expect(providerToolNames).toEqual([["switch_tools"], ["after_switch"]]);
|
||||
} finally {
|
||||
harness.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user