UNPKG

@tanstack/ai-code-mode

Version:

Secure TypeScript Code Mode for TanStack AI agents to execute sandboxed tool orchestration programs.

121 lines (120 loc) • 4.69 kB
import { convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema } from "@tanstack/ai"; //#region src/bindings/tool-to-binding.ts /** * Convert an array of TanStack AI tools to a Record of ToolBindings * * @param tools - Array of tools to convert * @param prefix - Optional prefix to add to binding names (e.g., 'external_') */ function toolsToBindings(tools, prefix = "") { const bindings = {}; for (const tool of tools) { const bindingName = `${prefix}${tool.name}`; bindings[bindingName] = toolToBinding(tool, prefix); } return bindings; } /** * Convert a single TanStack AI tool to a ToolBinding * * @param tool - Tool to convert * @param prefix - Optional prefix to add to binding name (e.g., 'external_') * @throws Error if the tool doesn't have an execute function */ function toolToBinding(tool, prefix = "") { if (tool.needsApproval) throw new Error(`Tool "${tool.name}" requires approval (needsApproval: true) and cannot be used in Code Mode. Code Mode executes tools inside a sandbox and cannot pause for the approval interrupt, so the tool would run without approval. Remove it from the Code Mode config, or drop needsApproval if the tool is safe to call unattended.`); const inputSchema = convertSchemaToJsonSchema(tool.inputSchema) || { type: "object", properties: {} }; const outputSchema = tool.outputSchema ? convertSchemaToJsonSchema(tool.outputSchema) : void 0; if (typeof tool.execute !== "function") throw new Error(`Tool "${tool.name}" does not have an execute function. Code Mode requires server tools with implementations.`); const toolExecute = tool.execute; const execute = async (args, context) => { let input = args; if (tool.inputSchema && isStandardSchema(tool.inputSchema)) try { input = parseWithStandardSchema(tool.inputSchema, args); } catch (error) { const message = error instanceof Error ? error.message : "Validation failed"; throw new Error(`Input validation failed for tool ${tool.name}: ${message}`); } let result = await Promise.resolve(toolExecute(input, context)); if (tool.outputSchema && isStandardSchema(tool.outputSchema)) try { result = parseWithStandardSchema(tool.outputSchema, result); } catch (error) { const message = error instanceof Error ? error.message : "Validation failed"; throw new Error(`Output validation failed for tool ${tool.name}: ${message}`); } return result; }; return { name: `${prefix}${tool.name}`, description: tool.description, inputSchema, outputSchema, execute }; } /** * Create event-aware bindings that emit custom events for each external function call. * Wraps each binding's execute function to emit events before and after execution. * * Each call gets the parent tool's `abortSignal`, `threadId`, and runtime `context`, so a * tool can cancel in-flight work when the run aborts. `toolCallId` and `inputResponse` are * not passed: they belong to the parent tool call, not to this nested call. Without a * parent context, the context has no `threadId`. * * @param bindings - Original tool bindings * @param emitCustomEvent - Callback to emit custom events to the stream * @param parentContext - Context of the tool that runs the code (e.g. `execute_typescript`) * @param hook - Optional internal hook that runs around each call. The events * still fire around it. */ function createEventAwareBindings(bindings, emitCustomEvent, parentContext, hook) { const toolContext = { context: parentContext?.context, abortSignal: parentContext?.abortSignal, threadId: parentContext?.threadId, emitCustomEvent }; const wrapped = {}; for (const [name, binding] of Object.entries(bindings)) { const toolName = name.replace(/^external_/, ""); wrapped[name] = { ...binding, execute: async (args) => { emitCustomEvent("code_mode:external_call", { function: name, args, timestamp: Date.now() }); const startTime = Date.now(); try { toolContext.abortSignal?.throwIfAborted(); const run = () => binding.execute(args, toolContext); const result = await (hook ? hook({ name: toolName, args }, run) : run()); emitCustomEvent("code_mode:external_result", { function: name, result, duration: Date.now() - startTime }); return result; } catch (error) { emitCustomEvent("code_mode:external_error", { function: name, error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime }); throw error; } } }; } return wrapped; } //#endregion export { createEventAwareBindings, toolToBinding, toolsToBindings }; //# sourceMappingURL=tool-to-binding.js.map