3d8f74357c
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.
40 lines
1.4 KiB
TypeScript
40 lines
1.4 KiB
TypeScript
import type { Context, Tool } from "../types.ts";
|
|
|
|
type ToolNameNormalizer = (name: string) => string;
|
|
|
|
const identityToolName: ToolNameNormalizer = (name) => name;
|
|
|
|
/** Split current tools into prefix and transcript-loaded definitions. */
|
|
export function splitDeferredTools(
|
|
context: Context,
|
|
enabled: boolean,
|
|
normalizeName: ToolNameNormalizer = identityToolName,
|
|
): { immediate: Tool[]; deferred: Map<string, Tool> } {
|
|
const uniqueTools = new Map<string, Tool>();
|
|
for (const tool of context.tools ?? []) uniqueTools.set(normalizeName(tool.name), tool);
|
|
if (!enabled) return { immediate: [...uniqueTools.values()], deferred: new Map() };
|
|
|
|
const deferredNames = new Set<string>();
|
|
const usedNames = new Set<string>();
|
|
for (const message of context.messages) {
|
|
if (message.role === "assistant") {
|
|
for (const block of message.content) {
|
|
if (block.type === "toolCall") usedNames.add(normalizeName(block.name));
|
|
}
|
|
} else if (message.role === "toolResult") {
|
|
for (const name of message.addedToolNames ?? []) {
|
|
const normalizedName = normalizeName(name);
|
|
if (!usedNames.has(normalizedName)) deferredNames.add(normalizedName);
|
|
}
|
|
}
|
|
}
|
|
|
|
const immediate: Tool[] = [];
|
|
const deferred = new Map<string, Tool>();
|
|
for (const [name, tool] of uniqueTools) {
|
|
if (deferredNames.has(name)) deferred.set(name, tool);
|
|
else immediate.push(tool);
|
|
}
|
|
return { immediate, deferred };
|
|
}
|