UNPKG

@tanstack/ai

Version:

Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.

141 lines (140 loc) 6.36 kB
import { AnyTool, CustomEvent, ModelMessage, RunFinishedEvent, Tool, ToolCall, ToolCallArgsEvent, ToolCallEndEvent, ToolCallStartEvent } from '../../../types.js'; import { AfterToolCallInfo, BeforeToolCallDecision } from '../middleware/types.js'; import { ContextFromTool, DefinedContext, MergeContext, UnionToIntersection } from '../runtime-context-types.js'; /** * Optional middleware hooks for tool execution. * When provided, these callbacks are invoked before/after each tool execution. */ export interface ToolExecutionMiddlewareHooks { onBeforeToolCall?: (toolCall: ToolCall, tool: Tool | undefined, args: unknown) => Promise<BeforeToolCallDecision>; onAfterToolCall?: (info: AfterToolCallInfo) => Promise<void>; } /** * Error thrown when middleware decides to abort the chat run during tool execution. */ export declare class MiddlewareAbortError extends Error { constructor(reason: string); } type RequiredContextFromToolUnion<T> = T extends unknown ? undefined extends ContextFromTool<T> ? never : ContextFromTool<T> : never; type ContextFromToolUnion<T> = [ UnionToIntersection<DefinedContext<ContextFromTool<T>>> ] extends [never] ? unknown : [RequiredContextFromToolUnion<T>] extends [never] ? UnionToIntersection<DefinedContext<ContextFromTool<T>>> | undefined : UnionToIntersection<DefinedContext<ContextFromTool<T>>>; type ContextFromTools<TTools> = TTools extends readonly [ infer THead, ...infer TTail ] ? MergeContext<ContextFromTool<THead>, ContextFromTools<TTail>> : TTools extends ReadonlyArray<infer TTool> ? ContextFromToolUnion<TTool> : unknown; type ExecuteToolsContextArgs<TContext> = undefined extends TContext ? [userContext?: TContext] : [userContext: TContext]; /** * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop. * * Responsibilities: * - Accumulates streaming tool call events (ID, name, arguments) * - Validates tool calls (filters out incomplete ones) * - Executes tool `execute` functions with parsed arguments * - Emits `TOOL_CALL_END` events for client visibility * - Returns tool result messages for conversation history * * This class is used internally by the AI.chat() method to handle the automatic * tool execution loop. It can also be used independently for custom tool execution logic. * * @example * ```typescript * const manager = new ToolCallManager(tools); * * // During streaming, accumulate tool calls * for await (const chunk of stream) { * if (chunk.type === 'TOOL_CALL_START') { * manager.addToolCallStartEvent(chunk); * } else if (chunk.type === 'TOOL_CALL_ARGS') { * manager.addToolCallArgsEvent(chunk); * } * } * * // After stream completes, execute tools * if (manager.hasToolCalls()) { * const toolResults = yield* manager.executeTools(finishEvent); * messages = [...messages, ...toolResults]; * manager.clear(); * } * ``` */ export declare class ToolCallManager<TToolsOrContext = ReadonlyArray<AnyTool>, TContext = TToolsOrContext extends ReadonlyArray<AnyTool> ? ContextFromTools<TToolsOrContext> : TToolsOrContext> { private readonly toolCallsMap; private readonly tools; constructor(tools: TToolsOrContext extends ReadonlyArray<AnyTool> ? TToolsOrContext : ReadonlyArray<AnyTool>); /** * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI) */ addToolCallStartEvent(event: ToolCallStartEvent): void; /** * Add a TOOL_CALL_ARGS event to accumulate arguments (AG-UI) */ addToolCallArgsEvent(event: ToolCallArgsEvent): void; /** * Complete a tool call with its final input * Called when TOOL_CALL_END is received */ completeToolCall(event: ToolCallEndEvent): void; /** * Check if there are any complete tool calls to execute */ hasToolCalls(): boolean; /** * Get all complete tool calls (filtered for valid ID and name) */ getToolCalls(): Array<ToolCall>; /** * Execute all tool calls and return tool result messages * Yields TOOL_CALL_END events for streaming * @param finishEvent - RUN_FINISHED event from the stream */ executeTools(finishEvent: RunFinishedEvent, ...contextArgs: ExecuteToolsContextArgs<TContext>): AsyncGenerator<ToolCallEndEvent, Array<ModelMessage>, void>; /** * Clear the tool calls map for the next iteration */ clear(): void; } export interface ToolResult { toolCallId: string; toolName: string; result: any; state?: 'output-available' | 'output-error'; /** Duration of tool execution in milliseconds (only for server-executed tools) */ duration?: number; } export interface ApprovalRequest { toolCallId: string; toolName: string; input: any; approvalId: string; } export interface ClientToolRequest { toolCallId: string; toolName: string; input: any; } interface ExecuteToolCallsResult { /** Tool results ready to send to LLM */ results: Array<ToolResult>; /** Tools that need user approval before execution */ needsApproval: Array<ApprovalRequest>; /** Tools that need client-side execution */ needsClientExecution: Array<ClientToolRequest>; } /** * Execute tool calls based on their configuration. * Yields CustomEvent chunks during tool execution for real-time progress updates. * * Handles three cases: * 1. Client tools (no execute) - request client to execute * 2. Server tools with approval - check approval before executing * 3. Normal server tools - execute immediately * * @param toolCalls - Tool calls from the LLM * @param tools - Available tools with their configurations * @param approvals - Map of approval decisions (approval.id -> approved boolean) * @param clientResults - Map of client-side execution results (toolCallId -> result) * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional) */ export declare function executeToolCalls<TContext = unknown>(toolCalls: Array<ToolCall>, tools: ReadonlyArray<AnyTool>, approvals?: Map<string, boolean>, clientResults?: Map<string, any>, createCustomEventChunk?: (eventName: string, value: Record<string, any>) => CustomEvent, middlewareHooks?: ToolExecutionMiddlewareHooks, userContext?: TContext, abortSignal?: AbortSignal): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void>; export {};