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:
@@ -624,6 +624,11 @@ export class ExtensionRunner {
|
||||
this.shutdownHandler();
|
||||
}
|
||||
|
||||
getActiveTools(): string[] {
|
||||
this.assertActive();
|
||||
return this.runtime.getActiveTools();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ExtensionContext for use in event handlers and tool execution.
|
||||
* Context values are resolved at call time, so changes via bindCore/bindUI are reflected.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { wrapToolDefinition, wrapToolDefinitions } from "../tools/tool-definition-wrapper.ts";
|
||||
import { wrapToolDefinition } from "../tools/tool-definition-wrapper.ts";
|
||||
import type { ExtensionRunner } from "./runner.ts";
|
||||
import type { RegisteredTool } from "./types.ts";
|
||||
|
||||
@@ -15,7 +15,25 @@ import type { RegisteredTool } from "./types.ts";
|
||||
* Uses the runner's createContext() for consistent context across tools and event handlers.
|
||||
*/
|
||||
export function wrapRegisteredTool(registeredTool: RegisteredTool, runner: ExtensionRunner): AgentTool {
|
||||
return wrapToolDefinition(registeredTool.definition, () => runner.createContext());
|
||||
const tool = wrapToolDefinition(registeredTool.definition, () => runner.createContext());
|
||||
const execute = tool.execute;
|
||||
return {
|
||||
...tool,
|
||||
execute: async (toolCallId, params, signal, onUpdate) => {
|
||||
const activeBefore = runner.getActiveTools();
|
||||
const result = await execute(toolCallId, params, signal, onUpdate);
|
||||
const activeAfter = runner.getActiveTools();
|
||||
if (!activeBefore.every((name) => activeAfter.includes(name))) return result;
|
||||
|
||||
const beforeNames = new Set(activeBefore);
|
||||
const addedToolNames = activeAfter.filter((name) => !beforeNames.has(name));
|
||||
if (addedToolNames.length === 0) return result;
|
||||
return {
|
||||
...result,
|
||||
addedToolNames: [...new Set([...(result.addedToolNames ?? []), ...addedToolNames])],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,8 +41,5 @@ export function wrapRegisteredTool(registeredTool: RegisteredTool, runner: Exten
|
||||
* Uses the runner's createContext() for consistent context across tools and event handlers.
|
||||
*/
|
||||
export function wrapRegisteredTools(registeredTools: RegisteredTool[], runner: ExtensionRunner): AgentTool[] {
|
||||
return wrapToolDefinitions(
|
||||
registeredTools.map((registeredTool) => registeredTool.definition),
|
||||
() => runner.createContext(),
|
||||
);
|
||||
return registeredTools.map((tool) => wrapRegisteredTool(tool, runner));
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ const OpenAIResponsesCompatSchema = Type.Object({
|
||||
supportsDeveloperRole: Type.Optional(Type.Boolean()),
|
||||
sendSessionIdHeader: Type.Optional(Type.Boolean()),
|
||||
supportsLongCacheRetention: Type.Optional(Type.Boolean()),
|
||||
supportsToolSearch: Type.Optional(Type.Boolean()),
|
||||
});
|
||||
|
||||
const AnthropicMessagesCompatSchema = Type.Object({
|
||||
@@ -148,6 +149,7 @@ const AnthropicMessagesCompatSchema = Type.Object({
|
||||
sendSessionAffinityHeaders: Type.Optional(Type.Boolean()),
|
||||
supportsCacheControlOnTools: Type.Optional(Type.Boolean()),
|
||||
forceAdaptiveThinking: Type.Optional(Type.Boolean()),
|
||||
supportsToolReferences: Type.Optional(Type.Boolean()),
|
||||
});
|
||||
|
||||
const ProviderCompatSchema = Type.Union([
|
||||
|
||||
Reference in New Issue
Block a user