import type { ContextMessage } from '#/agent/contextMemory/types'; export const DYNAMIC_TOOL_SCHEMA_VARIANT = 'dynamic_tool_schema'; export const LOADABLE_TOOLS_VARIANT = 'loadable-tools'; export function isDynamicToolSchemaMessage(message: ContextMessage): boolean { return message.tools !== undefined && message.tools.length > 0; } export function isLoadableToolsAnnouncement(message: ContextMessage): boolean { const origin = message.origin; if (origin?.kind === 'injection') return origin.variant === LOADABLE_TOOLS_VARIANT; return origin?.kind === 'system_trigger' && origin.name === LOADABLE_TOOLS_VARIANT; } export function stripDynamicToolContext( history: readonly ContextMessage[], ): readonly ContextMessage[] { if (!history.some((m) => isDynamicToolSchemaMessage(m) || isLoadableToolsAnnouncement(m))) { return history; } const out: ContextMessage[] = []; for (const message of history) { if (isLoadableToolsAnnouncement(message)) continue; if (isDynamicToolSchemaMessage(message)) { const { tools: _tools, ...rest } = message; void _tools; if (rest.content.length === 0 && rest.toolCalls.length === 0) continue; out.push(rest); continue; } out.push(message); } return out; } export function collectLoadedDynamicToolNames( history: readonly ContextMessage[], ): Set { const names = new Set(); for (const message of history) { if (message.tools === undefined) continue; for (const tool of message.tools) { names.add(tool.name); } } return names; } const TOOLS_ADDED_BLOCK = /\n?([\s\S]*?)\n?<\/tools_added>/g; const TOOLS_REMOVED_BLOCK = /\n?([\s\S]*?)\n?<\/tools_removed>/g; export function foldAnnouncedToolNames(history: readonly ContextMessage[]): Set { const announced = new Set(); for (const message of history) { if (!isLoadableToolsAnnouncement(message)) continue; const text = message.content .map((part) => (part.type === 'text' ? part.text : '')) .join(''); for (const name of matchToolNameBlocks(text, TOOLS_REMOVED_BLOCK)) { announced.delete(name); } for (const name of matchToolNameBlocks(text, TOOLS_ADDED_BLOCK)) { announced.add(name); } } return announced; } function matchToolNameBlocks(text: string, pattern: RegExp): string[] { const names: string[] = []; pattern.lastIndex = 0; for (const match of text.matchAll(pattern)) { const body = match[1] ?? ''; for (const line of body.split('\n')) { const name = line.trim(); if (name.length > 0) names.push(name); } } return names; } export function renderLoadableToolsAnnouncement( added: readonly string[], removed: readonly string[], ): string { const sections: string[] = []; if (added.length > 0) { sections.push(`\n${added.join('\n')}\n`); } if (removed.length > 0) { sections.push(`\n${removed.join('\n')}\n`); } sections.push( 'Use the select_tools tool with exact names to load full tool definitions before calling them. ' + 'Names listed as removed are no longer loadable — do not select them. ' + 'Fold all announcements in this conversation in order to get the current list.', ); return sections.join('\n\n'); }