feat(ai): support message-anchored tool loading (#6474)

This adds cache-friendly dynamic tool loading anchored to tool results. Purely additive active-tool changes are recorded with `addedToolNames`, allowing supported Anthropic and OpenAI Responses models to load tool definitions at the point they become available instead of placing them in the cached prompt prefix.

It retains safe fallback behavior for unsupported models and non-additive changes but it will wipe caches.
This commit is contained in:
Armin Ronacher
2026-07-10 23:52:54 +02:00
committed by GitHub
parent d7a48d30a0
commit 3d8f74357c
19 changed files with 894 additions and 63 deletions
@@ -66,6 +66,62 @@ describe("extension active tools next-turn refresh", () => {
}
});
it("records additive active tool changes on the current tool result", async () => {
const extensionFactories: ExtensionFactory[] = [
(pi) => {
pi.registerTool({
name: "load_more_tools",
label: "Load More Tools",
description: "Load more tools",
parameters: Type.Object({}),
execute: async () => {
pi.setActiveTools([...pi.getActiveTools(), "after_load"]);
return {
content: [{ type: "text", text: "loaded" }],
details: {},
};
},
});
pi.registerTool({
name: "after_load",
label: "After Load",
description: "Tool available after loading",
parameters: Type.Object({}),
execute: async () => ({
content: [{ type: "text", text: "after" }],
details: {},
}),
});
},
];
const harness = await createHarness({ extensionFactories });
try {
harness.session.setActiveToolsByName(["load_more_tools"]);
const addedToolNames: string[][] = [];
harness.setResponses([
() => fauxAssistantMessage(fauxToolCall("load_more_tools", {}), { stopReason: "toolUse" }),
(context) => {
addedToolNames.push(
context.messages
.filter((message) => message.role === "toolResult")
.flatMap((message) => message.addedToolNames ?? []),
);
return fauxAssistantMessage("done");
},
]);
await harness.session.prompt("start");
expect(harness.session.getActiveToolNames()).toEqual(["load_more_tools", "after_load"]);
expect(addedToolNames).toEqual([["after_load"]]);
} finally {
harness.cleanup();
}
});
it("preserves before_agent_start system prompt overrides when tools change mid-run", async () => {
const extensionFactories: ExtensionFactory[] = [
(pi) => {