@tanstack/ai 0.61.0 → 0.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +1 -0
  2. package/dist/esm/activities/chat/agents/define-agent.d.ts +17 -5
  3. package/dist/esm/activities/chat/agents/define-agent.js.map +1 -1
  4. package/dist/esm/activities/chat/agents/spawn.d.ts +2 -0
  5. package/dist/esm/activities/chat/agents/spawn.js +10 -33
  6. package/dist/esm/activities/chat/agents/spawn.js.map +1 -1
  7. package/dist/esm/activities/chat/index.d.ts +2 -0
  8. package/dist/esm/activities/chat/index.js +166 -60
  9. package/dist/esm/activities/chat/index.js.map +1 -1
  10. package/dist/esm/activities/chat/messages.js +43 -22
  11. package/dist/esm/activities/chat/messages.js.map +1 -1
  12. package/dist/esm/activities/chat/middleware/types.d.ts +1 -0
  13. package/dist/esm/activities/chat/middleware/types.js.map +1 -1
  14. package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
  15. package/dist/esm/activities/chat/stream/message-updaters.js +6 -2
  16. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  17. package/dist/esm/activities/chat/stream/processor.d.ts +11 -9
  18. package/dist/esm/activities/chat/stream/processor.js +38 -18
  19. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  20. package/dist/esm/activities/chat/tools/schema-converter.d.ts +8 -0
  21. package/dist/esm/activities/chat/tools/schema-converter.js +6 -5
  22. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  23. package/dist/esm/activities/chat/tools/tool-calls.d.ts +20 -3
  24. package/dist/esm/activities/chat/tools/tool-calls.js +126 -36
  25. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  26. package/dist/esm/activities/chat/tools/tool-definition.d.ts +4 -0
  27. package/dist/esm/activities/chat/tools/tool-definition.js +4 -0
  28. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  29. package/dist/esm/activities/evaluate/adapter.d.ts +4 -0
  30. package/dist/esm/activities/evaluate/adapter.js.map +1 -1
  31. package/dist/esm/activities/evaluate/index.d.ts +4 -0
  32. package/dist/esm/activities/evaluate/index.js +3 -1
  33. package/dist/esm/activities/evaluate/index.js.map +1 -1
  34. package/dist/esm/activities/generateSpeech/index.d.ts +1 -1
  35. package/dist/esm/activities/generateSpeech/index.js +1 -1
  36. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  37. package/dist/esm/activities/generateVideo/adapter.d.ts +14 -6
  38. package/dist/esm/activities/generateVideo/adapter.js +6 -3
  39. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  40. package/dist/esm/activities/generateVideo/index.d.ts +5 -4
  41. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  42. package/dist/esm/activities/generateVideo/snap.d.ts +12 -3
  43. package/dist/esm/activities/generateVideo/snap.js +47 -8
  44. package/dist/esm/activities/generateVideo/snap.js.map +1 -1
  45. package/dist/esm/activities/generateVoice/index.d.ts +1 -1
  46. package/dist/esm/activities/generateVoice/index.js +1 -1
  47. package/dist/esm/activities/generateVoice/index.js.map +1 -1
  48. package/dist/esm/activities/index.d.ts +2 -2
  49. package/dist/esm/activities/index.js +2 -2
  50. package/dist/esm/adapter-internals.d.ts +1 -0
  51. package/dist/esm/adapter-internals.js +2 -1
  52. package/dist/esm/client.d.ts +1 -1
  53. package/dist/esm/client.js.map +1 -1
  54. package/dist/esm/index.d.ts +1 -1
  55. package/dist/esm/index.js +2 -2
  56. package/dist/esm/interrupt-resume.js +29 -4
  57. package/dist/esm/interrupt-resume.js.map +1 -1
  58. package/dist/esm/middlewares/otel.d.ts +5 -2
  59. package/dist/esm/middlewares/otel.js +114 -0
  60. package/dist/esm/middlewares/otel.js.map +1 -1
  61. package/dist/esm/types.d.ts +70 -4
  62. package/dist/esm/utilities/ag-ui-wire.js +8 -4
  63. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  64. package/dist/esm/utilities/merge-streams.d.ts +6 -0
  65. package/dist/esm/utilities/merge-streams.js +37 -0
  66. package/dist/esm/utilities/merge-streams.js.map +1 -0
  67. package/dist/esm/utilities/reasoning-encrypted-value.d.ts +8 -0
  68. package/dist/esm/utilities/reasoning-encrypted-value.js +11 -1
  69. package/dist/esm/utilities/reasoning-encrypted-value.js.map +1 -1
  70. package/dist/esm/utilities/tool-result.d.ts +2 -1
  71. package/dist/esm/utilities/tool-result.js +4 -1
  72. package/dist/esm/utilities/tool-result.js.map +1 -1
  73. package/package.json +3 -3
  74. package/skills/ai-core/chat-experience/SKILL.md +120 -0
  75. package/skills/ai-core/media-generation/SKILL.md +3 -3
  76. package/skills/ai-core/tool-calling/SKILL.md +103 -0
  77. package/src/activities/chat/agents/define-agent.ts +20 -3
  78. package/src/activities/chat/agents/spawn.ts +22 -43
  79. package/src/activities/chat/index.ts +248 -65
  80. package/src/activities/chat/messages.ts +54 -7
  81. package/src/activities/chat/middleware/types.ts +1 -0
  82. package/src/activities/chat/stream/message-updaters.ts +8 -0
  83. package/src/activities/chat/stream/processor.ts +57 -27
  84. package/src/activities/chat/tools/schema-converter.ts +17 -5
  85. package/src/activities/chat/tools/tool-calls.ts +215 -68
  86. package/src/activities/chat/tools/tool-definition.ts +8 -0
  87. package/src/activities/evaluate/adapter.ts +4 -0
  88. package/src/activities/evaluate/index.ts +6 -0
  89. package/src/activities/generateSpeech/index.ts +1 -1
  90. package/src/activities/generateVideo/adapter.ts +21 -7
  91. package/src/activities/generateVideo/index.ts +5 -4
  92. package/src/activities/generateVideo/snap.ts +64 -6
  93. package/src/activities/generateVoice/index.ts +1 -1
  94. package/src/activities/index.ts +2 -1
  95. package/src/adapter-internals.ts +1 -0
  96. package/src/client.ts +1 -0
  97. package/src/index.ts +1 -0
  98. package/src/interrupt-resume.ts +55 -4
  99. package/src/middlewares/otel.ts +161 -3
  100. package/src/types.ts +66 -5
  101. package/src/utilities/ag-ui-wire.ts +17 -2
  102. package/src/utilities/merge-streams.ts +34 -0
  103. package/src/utilities/reasoning-encrypted-value.ts +12 -0
  104. package/src/utilities/tool-result.ts +7 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tool-calls.js","names":[],"sources":["../../../../../src/activities/chat/tools/tool-calls.ts"],"sourcesContent":["import { normalizeToolResult } from '../../../utilities/tool-result'\nimport { tanstackMetadata } from '../../../utilities/merge-metadata'\nimport { isProviderExecutedToolCall } from '../../../utilities/provider-executed'\nimport type { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk'\nimport { isStandardSchema, parseWithStandardSchema } from './schema-converter'\nimport type { ToolApprovalResolution } from '../../../interrupts'\nimport type {\n AnyTool,\n ContentPart,\n CustomEvent,\n EmitCustomEventOptions,\n Interrupt,\n ModelMessage,\n RunFinishedEvent,\n StreamChunk,\n Tool,\n ToolCall,\n ToolCallArgsEvent,\n ToolCallEndEvent,\n ToolCallStartEvent,\n ToolExecutionContext,\n ToolOutputState,\n} from '../../../types'\nimport type {\n AfterToolCallInfo,\n BeforeToolCallDecision,\n} from '../middleware/types'\nimport type { McpResourceReadResult } from '../mcp/types'\nimport type {\n ContextFromTool,\n DefinedContext,\n MergeContext,\n UnionToIntersection,\n} from '../runtime-context-types'\n\nfunction safeJsonParse(value: string): unknown {\n try {\n return JSON.parse(value)\n } catch {\n return value\n }\n}\n\n/** Marks the synthetic tool that runs a subagent. */\nexport const SUBAGENT_TOOL = Symbol.for('tanstack.ai.subagentTool')\n\n/** Set on the tool context of a subagent tool. Streams a child chunk live. */\nexport const EMIT_STREAM_CHUNK = Symbol.for('tanstack.ai.emitStreamChunk')\n\n/** What a subagent tool's `execute` returns. */\nexport interface SubagentToolOutcome {\n subagentRunId: string\n text: string\n error?: string\n /** Set when the child stopped for outside input. The tool call stays open. */\n interrupts?: Array<Interrupt>\n}\n\nfunction isSubagentTool(tool: AnyTool): boolean {\n return (tool as { [SUBAGENT_TOOL]?: true })[SUBAGENT_TOOL] === true\n}\n\n/**\n * MCP Apps metadata attached to a server tool at discovery (see\n * `@tanstack/ai-mcp` discovery + `MCPManager.discover()`).\n *\n * - `uiResourceUri` / `serverId` are stamped by ai-mcp at tool discovery.\n * - `readResource` is bound by `MCPManager.discover()` (the one site that has\n * both the tool and its originating source) so the resource can be eagerly\n * read at the emit site. Under `chat()`-managed MCP lifecycle\n * (`connection:'close'`), the MCP source is not disposed until the run\n * drains, so `readResource` is still live at this emit point. Note: a caller\n * who closes the MCP source early (outside `chat()`'s managed lifecycle)\n * degrades fail-soft — `readResource` may reject, the widget is absent, but\n * the tool result still flows to the model.\n * `@tanstack/ai` never imports `@tanstack/ai-mcp`; this travels structurally\n * on the tool.\n */\ninterface McpToolAppMeta {\n uiResourceUri?: string\n serverId?: string\n /** Server-native (unprefixed) MCP tool name — used as the renderer's toolName. */\n serverToolName?: string\n readResource?: (uri: string) => Promise<McpResourceReadResult>\n}\n\nfunction readMcpAppMeta(tool: AnyTool): McpToolAppMeta | undefined {\n const meta = (tool.metadata as { mcp?: McpToolAppMeta } | undefined)?.mcp\n return meta\n}\n\n/**\n * Eagerly read a tool's linked `ui://` resource (MCP Apps) and emit a\n * `ui-resource` CUSTOM event so the client can render the widget. The model\n * still receives the normal text tool-result; the widget rides alongside and\n * never enters model input.\n *\n * Fail-soft: any read error logs a warning and emits nothing — it never throws,\n * so the normal tool-result still flows and a broken widget cannot break the run.\n */\nasync function emitUiResourceIfLinked<TContext>(\n tool: AnyTool,\n context: ToolExecutionContext<TContext>,\n): Promise<void> {\n const mcp = readMcpAppMeta(tool)\n const uiUri = mcp?.uiResourceUri\n if (!uiUri || !mcp.readResource) return\n\n // The try covers ONLY the fallible read — keep `emitCustomEvent` out of it so\n // an exception from the emit path can't be mislabeled as a read failure.\n let matched: McpResourceReadResult['contents'][number] | undefined\n try {\n const res = await mcp.readResource(uiUri)\n // Emit ONLY the content whose uri matches the requested `uiUri`. A source\n // can return unrelated contents; falling back to `contents[0]` would risk\n // rendering a widget that doesn't correspond to the linked resource. This\n // is a display widget — a mismatched resource is worse than none, so if no\n // content matches we fail-soft (warn + return) rather than emit.\n matched = res.contents.find((c) => c.uri === uiUri)\n } catch (err) {\n // fail-soft — the text tool-result already flows; a broken widget must\n // not break the run.\n console.warn(`[mcp-apps] failed to read ui resource ${uiUri}:`, err)\n return\n }\n if (!matched) {\n console.warn(\n `[mcp-apps] ui resource ${uiUri} returned no content matching that uri; not emitting`,\n )\n return\n }\n // NOTE: `toolCallId` is intentionally NOT set here — it is stamped onto\n // every emitted event by the `executeToolCalls` context wrapper, so the\n // UIResourceEvent.value.toolCallId / UIResourcePart.toolCallId contract is\n // still satisfied downstream.\n context.emitCustomEvent('ui-resource', {\n resource: {\n uri: matched.uri,\n mimeType: matched.mimeType ?? 'text/html',\n text: matched.text,\n blob: matched.blob,\n },\n serverId: mcp.serverId,\n toolName: mcp.serverToolName ?? tool.name,\n meta: undefined,\n })\n}\n\n/**\n * Optional middleware hooks for tool execution.\n * When provided, these callbacks are invoked before/after each tool execution.\n */\nexport interface ToolExecutionMiddlewareHooks {\n onBeforeToolCall?: (\n toolCall: ToolCall,\n tool: Tool | undefined,\n args: unknown,\n ) => Promise<BeforeToolCallDecision>\n onAfterToolCall?: (info: AfterToolCallInfo) => Promise<void>\n}\n\n/**\n * Error thrown when middleware decides to abort the chat run during tool execution.\n */\nexport class MiddlewareAbortError extends Error {\n constructor(reason: string) {\n super(reason)\n this.name = 'MiddlewareAbortError'\n }\n}\n\n// The leaf context-inference primitives (ContextFromTool, MergeContext,\n// UnionToIntersection, DefinedContext) are shared with the chat activity\n// options layer — see ../runtime-context-types.\ntype RequiredContextFromToolUnion<T> = T extends unknown\n ? undefined extends ContextFromTool<T>\n ? never\n : ContextFromTool<T>\n : never\n\ntype ContextFromToolUnion<T> = [\n UnionToIntersection<DefinedContext<ContextFromTool<T>>>,\n] extends [never]\n ? unknown\n : [RequiredContextFromToolUnion<T>] extends [never]\n ? UnionToIntersection<DefinedContext<ContextFromTool<T>>> | undefined\n : UnionToIntersection<DefinedContext<ContextFromTool<T>>>\n\ntype ContextFromTools<TTools> = TTools extends readonly [\n infer THead,\n ...infer TTail,\n]\n ? MergeContext<ContextFromTool<THead>, ContextFromTools<TTail>>\n : TTools extends ReadonlyArray<infer TTool>\n ? ContextFromToolUnion<TTool>\n : unknown\n\ntype ExecuteToolsContextArgs<TContext> = undefined extends TContext\n ? [userContext?: TContext]\n : [userContext: TContext]\n\n/**\n * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.\n *\n * Responsibilities:\n * - Accumulates streaming tool call events (ID, name, arguments)\n * - Validates tool calls (filters out incomplete ones)\n * - Executes tool `execute` functions with parsed arguments\n * - Emits `TOOL_CALL_END` events for client visibility\n * - Returns tool result messages for conversation history\n *\n * This class is used internally by the AI.chat() method to handle the automatic\n * tool execution loop. It can also be used independently for custom tool execution logic.\n *\n * @example\n * ```typescript\n * const manager = new ToolCallManager(tools);\n *\n * // During streaming, accumulate tool calls\n * for await (const chunk of stream) {\n * if (chunk.type === 'TOOL_CALL_START') {\n * manager.addToolCallStartEvent(chunk);\n * } else if (chunk.type === 'TOOL_CALL_ARGS') {\n * manager.addToolCallArgsEvent(chunk);\n * }\n * }\n *\n * // After stream completes, execute tools\n * if (manager.hasToolCalls()) {\n * const toolResults = yield* manager.executeTools(finishEvent);\n * messages = [...messages, ...toolResults];\n * manager.clear();\n * }\n * ```\n */\nexport class ToolCallManager<\n TToolsOrContext = ReadonlyArray<AnyTool>,\n TContext = TToolsOrContext extends ReadonlyArray<AnyTool>\n ? ContextFromTools<TToolsOrContext>\n : TToolsOrContext,\n> {\n private readonly toolCallsMap = new Map<number, ToolCall>()\n private readonly tools: TToolsOrContext extends ReadonlyArray<AnyTool>\n ? TToolsOrContext\n : ReadonlyArray<AnyTool>\n\n constructor(\n tools: TToolsOrContext extends ReadonlyArray<AnyTool>\n ? TToolsOrContext\n : ReadonlyArray<AnyTool>,\n ) {\n this.tools = tools\n }\n\n /**\n * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)\n */\n addToolCallStartEvent(event: ToolCallStartEvent): void {\n // AG-UI's TOOL_CALL_START carries no index, and a non-first-party or\n // malformed producer can send a second START for a toolCallId that is\n // already tracked. Without this guard, a repeat with the same index\n // overwrites the slot (wiping any TOOL_CALL_ARGS already accumulated),\n // and a repeat with a missing/different index inserts a duplicate row\n // that getToolCalls() returns twice, running the tool twice.\n for (const toolCall of this.toolCallsMap.values()) {\n if (toolCall.id === event.toolCallId) return\n }\n const index = (event as AdapterYieldChunk).index ?? this.toolCallsMap.size\n const name = event.toolCallName ?? event.toolName\n this.toolCallsMap.set(index, {\n id: event.toolCallId,\n type: 'function',\n function: {\n name,\n arguments: '',\n },\n ...(event.metadata !== undefined && { metadata: event.metadata }),\n })\n }\n\n /**\n * Add a TOOL_CALL_ARGS event to accumulate arguments (AG-UI)\n */\n addToolCallArgsEvent(event: ToolCallArgsEvent): void {\n const extra = event as AdapterYieldChunk\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n if (typeof extra.args === 'string' && extra.args !== '') {\n toolCall.function.arguments = extra.args\n } else {\n toolCall.function.arguments += event.delta\n }\n break\n }\n }\n }\n\n /**\n * Complete a tool call with its final input\n * Called when TOOL_CALL_END is received\n */\n completeToolCall(event: ToolCallEndEvent): void {\n for (const toolCall of this.toolCallsMap.values()) {\n if (toolCall.id !== event.toolCallId) continue\n if (event.input === undefined) return\n const normalized =\n event.input && typeof event.input === 'object' ? event.input : {}\n toolCall.function.arguments = JSON.stringify(normalized)\n return\n }\n }\n\n /**\n * Check if there are any complete tool calls to execute\n */\n hasToolCalls(): boolean {\n return this.getToolCalls().length > 0\n }\n\n /**\n * Get all complete tool calls (filtered for valid ID and name)\n */\n getToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCallsMap.values()).filter(\n (tc) => tc.id && tc.function.name && tc.function.name.trim().length > 0,\n )\n }\n\n /**\n * Execute all tool calls and return tool result messages\n * Yields TOOL_CALL_END events for streaming\n * @param finishEvent - RUN_FINISHED event from the stream\n */\n async *executeTools(\n finishEvent: RunFinishedEvent,\n ...contextArgs: ExecuteToolsContextArgs<TContext>\n ): AsyncGenerator<AdapterYieldChunk, Array<ModelMessage>, void> {\n const toolCallsArray = this.getToolCalls()\n const toolResults: Array<ModelMessage> = []\n const hasRuntimeContext = contextArgs.length > 0\n const userContext = contextArgs[0]\n\n for (const toolCall of toolCallsArray) {\n const tool = this.tools.find((t) => t.name === toolCall.function.name)\n\n let toolResultContent: string | Array<ContentPart>\n let toolResultState: ToolOutputState | undefined\n // Holds the parsed/validated execution output before serialization.\n // Stays `undefined` when the tool has no `execute` (client-only\n // tools) or when execution throws.\n let toolOutput: unknown\n if (tool?.execute) {\n try {\n // Parse arguments (normalize null/non-object to {} for empty tool_use blocks)\n let args: unknown\n try {\n const argsString = toolCall.function.arguments.trim() || '{}'\n const parsed = JSON.parse(argsString)\n args = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n throw new Error(\n `Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`,\n )\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n args = parseWithStandardSchema(tool.inputSchema, args)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Input validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n // Execute the tool\n const executionContext = {\n toolCallId: toolCall.id,\n context: userContext,\n emitCustomEvent: () => {},\n } as ToolExecutionContext<TContext>\n let result = hasRuntimeContext\n ? await tool.execute(args, executionContext)\n : await tool.execute(args)\n\n // Validate output against outputSchema if provided (for Standard\n // Schema compliant schemas). Unlike the previous implementation we\n // intentionally validate `undefined`/`null` results too, so a tool\n // whose schema forbids them surfaces a validation error instead of\n // silently passing — the schema itself decides whether they're valid.\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n try {\n result = parseWithStandardSchema(tool.outputSchema, result)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n toolOutput = result\n toolResultContent = normalizeToolResult(result)\n } catch (error: unknown) {\n // If tool execution fails, add error message\n const message =\n error instanceof Error ? error.message : 'Unknown error'\n toolResultContent = `Error executing tool: ${message}`\n toolResultState = 'output-error'\n }\n } else {\n // Tool doesn't have execute function, add placeholder\n toolResultContent = `Tool ${toolCall.function.name} does not have an execute function`\n }\n\n // Emit TOOL_CALL_END event\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolCallName: toolCall.function.name,\n toolName: toolCall.function.name,\n model: (() => {\n const model = tanstackMetadata(finishEvent)?.model\n return typeof model === 'string' ? model : undefined\n })(),\n timestamp: Date.now(),\n // Typed parsed output (undefined for failed exec / client-only tools).\n ...(toolOutput !== undefined ? { output: toolOutput } : {}),\n result: toolResultContent,\n ...(toolResultState !== undefined && { state: toolResultState }),\n }\n\n // Add tool result message\n toolResults.push({\n role: 'tool',\n content: toolResultContent,\n toolCallId: toolCall.id,\n })\n }\n\n return toolResults\n }\n\n /**\n * Clear the tool calls map for the next iteration\n */\n clear(): void {\n this.toolCallsMap.clear()\n }\n}\n\nexport interface ToolResult {\n toolCallId: string\n toolName: string\n result: any\n state?: 'output-available' | 'output-error'\n /** Duration of tool execution in milliseconds (only for server-executed tools) */\n duration?: number\n /**\n * Parsed tool input (after JSON parse + optional Standard Schema validation).\n * Parsed tool input after JSON parse + optional Standard Schema validation.\n */\n input?: unknown\n /**\n * Parsed tool output before wire serialization. Surfaced on engine-emitted\n * `TOOL_CALL_END` events so consumers can read typed `output` without\n * re-parsing `result`. Undefined on error paths and when execution is skipped.\n */\n output?: unknown\n}\n\nexport interface ApprovalRequest {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n}\n\nexport interface ClientToolRequest {\n toolCallId: string\n toolName: string\n input: any\n}\n\nexport interface ToolResumeExecutionState {\n deniedToolResults?: ReadonlyMap<string, unknown>\n cancelledToolCallIds?: ReadonlySet<string>\n}\n\nfunction approvalResolution(\n approvals: ReadonlyMap<string, ToolApprovalResolution>,\n toolCallId: string,\n): ToolApprovalResolution | undefined {\n return approvals.get(toolCallId) ?? approvals.get(`approval_${toolCallId}`)\n}\n\nfunction isApproved(resolution: ToolApprovalResolution): boolean {\n return typeof resolution === 'boolean' ? resolution : resolution.approved\n}\n\nfunction editedApprovalArgs(\n resolution: ToolApprovalResolution,\n): unknown | undefined {\n return typeof resolution === 'object' && resolution.approved\n ? resolution.editedArgs\n : undefined\n}\n\nfunction deniedApprovalResult(resolution: ToolApprovalResolution): unknown {\n return typeof resolution === 'object' && !resolution.approved\n ? (resolution.payload ?? { error: 'User declined tool execution' })\n : { error: 'User declined tool execution' }\n}\n\ninterface ExecuteToolCallsResult {\n /** Tool results ready to send to LLM */\n results: Array<ToolResult>\n /** Tools that need user approval before execution */\n needsApproval: Array<ApprovalRequest>\n /** Tools that need client-side execution */\n needsClientExecution: Array<ClientToolRequest>\n /** Interrupts raised by subagents that run as tools */\n subagentInterrupts: Array<Interrupt>\n}\n\n/**\n * Helper that runs a tool execution promise while polling for pending custom events.\n * Yields any custom events that are emitted during execution, then returns the\n * execution result.\n */\nasync function* executeWithEventPolling<T>(\n executionPromise: Promise<T>,\n pendingEvents: Array<CustomEvent | StreamChunk>,\n): AsyncGenerator<CustomEvent | StreamChunk, T, void> {\n // Use an object to track mutable state across the async boundary\n const state = { done: false, result: undefined as T }\n const executionWithFlag = executionPromise.then((r) => {\n state.done = true\n state.result = r\n return r\n })\n\n while (!state.done) {\n // Wait for either the execution to complete or a short timeout\n await Promise.race([\n executionWithFlag,\n new Promise((resolve) => setTimeout(resolve, 10)),\n ])\n\n // Flush any pending events\n let event: CustomEvent | StreamChunk | undefined\n while ((event = pendingEvents.shift()) !== undefined) {\n yield event\n }\n }\n\n // Final flush in case events were emitted right at completion\n let event: CustomEvent | StreamChunk | undefined\n while ((event = pendingEvents.shift()) !== undefined) {\n yield event\n }\n\n return state.result\n}\n\n/**\n * Apply a middleware onBeforeToolCall decision.\n * Returns the (possibly transformed) input if execution should proceed,\n * or undefined if the tool call was skipped (result already pushed).\n * Throws MiddlewareAbortError if the decision is 'abort'.\n */\nasync function applyBeforeToolCallDecision(\n toolCall: ToolCall,\n tool: Tool,\n input: unknown,\n toolName: string,\n middlewareHooks: ToolExecutionMiddlewareHooks,\n results: Array<ToolResult>,\n): Promise<{ proceed: true; input: unknown } | { proceed: false }> {\n if (!middlewareHooks.onBeforeToolCall) {\n return { proceed: true, input }\n }\n\n const decision = await middlewareHooks.onBeforeToolCall(toolCall, tool, input)\n if (!decision) {\n return { proceed: true, input }\n }\n\n if (decision.type === 'abort') {\n throw new MiddlewareAbortError(decision.reason || 'Aborted by middleware')\n }\n\n if (decision.type === 'skip') {\n const skipResult = decision.result\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof skipResult === 'string'\n ? safeJsonParse(skipResult)\n : (skipResult ?? null),\n duration: 0,\n })\n if (middlewareHooks.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration: 0,\n result: skipResult,\n })\n }\n return { proceed: false }\n }\n\n return { proceed: true, input: decision.args }\n}\n\n/**\n * Execute a server-side tool with event polling, output validation, and middleware hooks.\n * Yields CustomEvent chunks during execution and pushes the result to the results array.\n */\nexport async function* executeServerTool<TContext = unknown>(\n toolCall: ToolCall,\n tool: AnyTool,\n toolName: string,\n input: unknown,\n context: ToolExecutionContext<TContext>,\n pendingEvents: Array<CustomEvent | StreamChunk>,\n results: Array<ToolResult>,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n subagentInterrupts?: Array<Interrupt>,\n): AsyncGenerator<CustomEvent | StreamChunk, void, void> {\n const startTime = Date.now()\n try {\n if (!tool.execute) {\n throw new Error(`Tool ${toolName} has no execute() implementation`)\n }\n const subagent = isSubagentTool(tool)\n if (subagent) {\n Object.assign(context, {\n [EMIT_STREAM_CHUNK]: (chunk: StreamChunk) => pendingEvents.push(chunk),\n })\n }\n const executionPromise = Promise.resolve(tool.execute(input, context))\n let result = yield* executeWithEventPolling(executionPromise, pendingEvents)\n const duration = Date.now() - startTime\n\n if (subagent) {\n const outcome = result as SubagentToolOutcome\n if (outcome.interrupts?.length) {\n // The child waits for outside input. Leave the call open so the\n // resume run executes it again and continues the same child.\n subagentInterrupts?.push(...outcome.interrupts)\n return\n }\n const modelResult = outcome.error\n ? { subagentRunId: outcome.subagentRunId, error: outcome.error }\n : { subagentRunId: outcome.subagentRunId, result: outcome.text }\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: modelResult,\n input,\n output: modelResult,\n duration,\n ...(outcome.error ? { state: 'output-error' as const } : {}),\n })\n await middlewareHooks?.onAfterToolCall?.({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n duration,\n ...(outcome.error\n ? { ok: false as const, error: new Error(outcome.error) }\n : { ok: true as const, result: modelResult }),\n })\n return\n }\n\n // MCP Apps: if this tool links a ui:// resource, eagerly read it and queue\n // a `ui-resource` CUSTOM event. The MCP source stays live until the run\n // drains (MCPManager's `connection:'close'` policy disposes on completion),\n // so `readResource` is callable here. Fail-soft: a read error warns and\n // emits nothing — the text result still flows.\n await emitUiResourceIfLinked(tool, context)\n\n // Flush remaining events (including any queued ui-resource event)\n let pendingEvent: CustomEvent | StreamChunk | undefined\n while ((pendingEvent = pendingEvents.shift()) !== undefined) {\n yield pendingEvent\n }\n\n // Validate output against outputSchema if provided. Validates\n // `undefined`/`null` too — the schema decides whether they're valid.\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n const finalResult =\n typeof result === 'string' ? safeJsonParse(result) : (result ?? null)\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: finalResult,\n input,\n output: finalResult,\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration,\n result: finalResult,\n })\n }\n } catch (error: unknown) {\n const duration = Date.now() - startTime\n\n // Flush remaining events\n let pendingEvent: CustomEvent | StreamChunk | undefined\n while ((pendingEvent = pendingEvents.shift()) !== undefined) {\n yield pendingEvent\n }\n\n if (error instanceof MiddlewareAbortError) {\n throw error\n }\n\n const message = error instanceof Error ? error.message : 'Unknown error'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: message },\n input,\n state: 'output-error',\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: false,\n duration,\n error,\n })\n }\n }\n}\n\nfunction buildClientToolResult(\n toolCallId: string,\n toolName: string,\n tool: AnyTool,\n rawResult: unknown,\n input?: unknown,\n): ToolResult {\n try {\n let result = rawResult\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n const parsed =\n typeof result === 'string' ? safeJsonParse(result) : (result ?? null)\n return {\n toolCallId,\n toolName,\n result: parsed,\n input,\n output: parsed,\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Validation failed'\n return {\n toolCallId,\n toolName,\n result: { error: message },\n input,\n state: 'output-error',\n }\n }\n}\n\n/**\n * Execute tool calls based on their configuration.\n * Yields CustomEvent chunks during tool execution for real-time progress updates.\n *\n * Handles three cases:\n * 1. Client tools (no execute) - request client to execute\n * 2. Server tools with approval - check approval before executing\n * 3. Normal server tools - execute immediately\n *\n * @param toolCalls - Tool calls from the LLM\n * @param tools - Available tools with their configurations\n * @param approvals - Map keyed by toolCallId (or `approval_${toolCallId}`) → ToolApprovalResolution\n * @param clientResults - Map of client-side execution results (toolCallId -> result)\n * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)\n */\nexport async function* executeToolCalls<TContext = unknown>(\n toolCalls: Array<ToolCall>,\n tools: ReadonlyArray<AnyTool>,\n approvals: Map<string, ToolApprovalResolution> = new Map(),\n clientResults: Map<string, any> = new Map(),\n createCustomEventChunk?: (\n eventName: string,\n value: Record<string, any>,\n options?: EmitCustomEventOptions,\n ) => CustomEvent,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n userContext?: TContext,\n abortSignal?: AbortSignal,\n resumeState?: ToolResumeExecutionState,\n): AsyncGenerator<CustomEvent | StreamChunk, ExecuteToolCallsResult, void> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\n const subagentInterrupts: Array<Interrupt> = []\n\n // Create tool lookup map\n const toolMap = new Map<string, AnyTool>()\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n // Batch gating: when any tool in the batch still needs an approval decision,\n // defer all execution so side effects don't happen before the user decides.\n const hasPendingApprovals = toolCalls.some((tc) => {\n const t = toolMap.get(tc.function.name)\n return (\n t?.needsApproval &&\n approvalResolution(approvals, tc.id) === undefined &&\n !resumeState?.cancelledToolCallIds?.has(tc.id)\n )\n })\n\n for (const toolCall of toolCalls) {\n // Provider-executed tools (Anthropic web_search / web_fetch) already ran\n // inside the provider response and carry their result on the call's\n // metadata. They have no execute() and must not become client requests.\n if (isProviderExecutedToolCall(toolCall)) continue\n\n const tool = toolMap.get(toolCall.function.name)\n const toolName = toolCall.function.name\n\n if (!tool) {\n // Unknown tool - return error\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: `Unknown tool: ${toolName}` },\n state: 'output-error',\n })\n continue\n }\n\n // Skip non-pending tools while approvals are outstanding\n if (hasPendingApprovals) {\n const isPendingApproval =\n tool.needsApproval &&\n approvalResolution(approvals, toolCall.id) === undefined\n const isPlainClientRequest = !tool.needsApproval && !tool.execute\n if (!isPendingApproval && !isPlainClientRequest) {\n continue\n }\n }\n\n if (resumeState?.cancelledToolCallIds?.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'Tool execution cancelled' },\n state: 'output-error',\n })\n continue\n }\n\n // Parse arguments\n let input: unknown = {}\n const argsStr = toolCall.function.arguments.trim() || '{}'\n if (argsStr) {\n try {\n const parsed = JSON.parse(argsStr)\n // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)\n input = parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Failed to parse tool arguments as JSON: ${argsStr}`,\n },\n input,\n state: 'output-error',\n })\n continue\n }\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n input = parseWithStandardSchema(tool.inputSchema, input)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Input validation failed for tool ${tool.name}: ${message}`,\n },\n // raw parse may have failed validation — still attach best-effort input\n input,\n state: 'output-error',\n })\n continue\n }\n }\n\n // Create a ToolExecutionContext for this tool call with event emission\n const pendingEvents: Array<CustomEvent | StreamChunk> = []\n const context = {\n toolCallId: toolCall.id,\n context: userContext,\n abortSignal,\n emitCustomEvent: (\n eventName: string,\n value: Record<string, any>,\n options?: EmitCustomEventOptions,\n ) => {\n if (createCustomEventChunk) {\n pendingEvents.push(\n createCustomEventChunk(\n eventName,\n {\n ...value,\n toolCallId: toolCall.id,\n },\n options,\n ),\n )\n }\n },\n } as ToolExecutionContext<TContext>\n\n // CASE 1: Client-side tool (no execute function)\n if (!tool.execute) {\n // Check if tool needs approval\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n const resolution = approvalResolution(approvals, toolCall.id)\n\n // Check if approval decision exists\n if (resolution !== undefined) {\n const approved = isApproved(resolution)\n\n if (approved) {\n input = editedApprovalArgs(resolution) ?? input\n // Approved - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push(\n buildClientToolResult(\n toolCall.id,\n toolName,\n tool,\n clientResults.get(toolCall.id),\n input,\n ),\n )\n } else {\n // Approved but not executed yet - request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n resumeState?.deniedToolResults?.get(toolCall.id) ??\n deniedApprovalResult(resolution),\n input,\n state: 'output-error',\n })\n }\n } else {\n // Need approval first\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n input,\n approvalId,\n })\n }\n } else {\n // No approval needed - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push(\n buildClientToolResult(\n toolCall.id,\n toolName,\n tool,\n clientResults.get(toolCall.id),\n input,\n ),\n )\n } else {\n // Request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n }\n continue\n }\n\n // CASE 2: Server tool with approval required\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n const resolution = approvalResolution(approvals, toolCall.id)\n\n // Check if approval decision exists\n if (resolution !== undefined) {\n const approved = isApproved(resolution)\n\n if (approved) {\n input = editedApprovalArgs(resolution) ?? input\n // Apply middleware before-hook for approved tools\n if (middlewareHooks) {\n const decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n if (!decision.proceed) continue\n input = decision.input\n }\n\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n subagentInterrupts,\n )\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n resumeState?.deniedToolResults?.get(toolCall.id) ??\n deniedApprovalResult(resolution),\n input,\n state: 'output-error',\n })\n }\n } else {\n // Need approval\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n approvalId,\n })\n }\n continue\n }\n\n // CASE 3: Normal server tool - execute immediately\n if (middlewareHooks) {\n const decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n if (!decision.proceed) continue\n input = decision.input\n }\n\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n subagentInterrupts,\n )\n }\n\n return { results, needsApproval, needsClientExecution, subagentInterrupts }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tool-calls.js","names":[],"sources":["../../../../../src/activities/chat/tools/tool-calls.ts"],"sourcesContent":["import { normalizeToolResult } from '../../../utilities/tool-result'\nimport { tanstackMetadata } from '../../../utilities/merge-metadata'\nimport { isProviderExecutedToolCall } from '../../../utilities/provider-executed'\nimport { mergeStreams } from '../../../utilities/merge-streams'\nimport type { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk'\nimport {\n StandardSchemaValidationError,\n isStandardSchema,\n parseWithStandardSchema,\n validateWithStandardSchema,\n} from './schema-converter'\nimport type { ToolApprovalResolution } from '../../../interrupts'\nimport type {\n AnyTool,\n ContentPart,\n CustomEvent,\n EmitCustomEventOptions,\n Interrupt,\n ModelMessage,\n RunFinishedEvent,\n StreamChunk,\n TextOptions,\n Tool,\n ToolCall,\n ToolCallArgsEvent,\n ToolCallEndEvent,\n ToolCallStartEvent,\n ToolExecutionContext,\n ToolInputResponse,\n ToolResultOutcome,\n ToolOutputState,\n} from '../../../types'\nimport type {\n AfterToolCallInfo,\n BeforeToolCallDecision,\n} from '../middleware/types'\nimport type { McpResourceReadResult } from '../mcp/types'\nimport type {\n ContextFromTool,\n DefinedContext,\n MergeContext,\n UnionToIntersection,\n} from '../runtime-context-types'\n\nfunction safeJsonParse(value: string): unknown {\n try {\n return JSON.parse(value)\n } catch {\n return value\n }\n}\n\n/** Marks the synthetic tool that runs a subagent. */\nexport const SUBAGENT_TOOL = Symbol.for('tanstack.ai.subagentTool')\n\n/** Set on the tool context of a subagent tool. Streams a child chunk live. */\nexport const EMIT_STREAM_CHUNK = Symbol.for('tanstack.ai.emitStreamChunk')\n\n/** What a subagent tool's `execute` returns. */\nexport interface SubagentToolOutcome {\n subagentRunId: string\n text: string\n error?: string\n /** Set when the child stopped for outside input. The tool call stays open. */\n interrupts?: Array<Interrupt>\n}\n\nfunction isSubagentTool(tool: AnyTool): boolean {\n return (tool as { [SUBAGENT_TOOL]?: true })[SUBAGENT_TOOL] === true\n}\n\n/**\n * MCP Apps metadata attached to a server tool at discovery (see\n * `@tanstack/ai-mcp` discovery + `MCPManager.discover()`).\n *\n * - `uiResourceUri` / `serverId` are stamped by ai-mcp at tool discovery.\n * - `readResource` is bound by `MCPManager.discover()` (the one site that has\n * both the tool and its originating source) so the resource can be eagerly\n * read at the emit site. Under `chat()`-managed MCP lifecycle\n * (`connection:'close'`), the MCP source is not disposed until the run\n * drains, so `readResource` is still live at this emit point. Note: a caller\n * who closes the MCP source early (outside `chat()`'s managed lifecycle)\n * degrades fail-soft — `readResource` may reject, the widget is absent, but\n * the tool result still flows to the model.\n * `@tanstack/ai` never imports `@tanstack/ai-mcp`; this travels structurally\n * on the tool.\n */\ninterface McpToolAppMeta {\n uiResourceUri?: string\n serverId?: string\n /** Server-native (unprefixed) MCP tool name — used as the renderer's toolName. */\n serverToolName?: string\n readResource?: (uri: string) => Promise<McpResourceReadResult>\n}\n\nfunction readMcpAppMeta(tool: AnyTool): McpToolAppMeta | undefined {\n const meta = (tool.metadata as { mcp?: McpToolAppMeta } | undefined)?.mcp\n return meta\n}\n\n/**\n * Eagerly read a tool's linked `ui://` resource (MCP Apps) and emit a\n * `ui-resource` CUSTOM event so the client can render the widget. The model\n * still receives the normal text tool-result; the widget rides alongside and\n * never enters model input.\n *\n * Fail-soft: any read error logs a warning and emits nothing — it never throws,\n * so the normal tool-result still flows and a broken widget cannot break the run.\n */\nasync function emitUiResourceIfLinked<TContext>(\n tool: AnyTool,\n context: ToolExecutionContext<TContext>,\n): Promise<void> {\n const mcp = readMcpAppMeta(tool)\n const uiUri = mcp?.uiResourceUri\n if (!uiUri || !mcp.readResource) return\n\n // The try covers ONLY the fallible read — keep `emitCustomEvent` out of it so\n // an exception from the emit path can't be mislabeled as a read failure.\n let matched: McpResourceReadResult['contents'][number] | undefined\n try {\n const res = await mcp.readResource(uiUri)\n // Emit ONLY the content whose uri matches the requested `uiUri`. A source\n // can return unrelated contents; falling back to `contents[0]` would risk\n // rendering a widget that doesn't correspond to the linked resource. This\n // is a display widget — a mismatched resource is worse than none, so if no\n // content matches we fail-soft (warn + return) rather than emit.\n matched = res.contents.find((c) => c.uri === uiUri)\n } catch (err) {\n // fail-soft — the text tool-result already flows; a broken widget must\n // not break the run.\n console.warn(`[mcp-apps] failed to read ui resource ${uiUri}:`, err)\n return\n }\n if (!matched) {\n console.warn(\n `[mcp-apps] ui resource ${uiUri} returned no content matching that uri; not emitting`,\n )\n return\n }\n // NOTE: `toolCallId` is intentionally NOT set here — it is stamped onto\n // every emitted event by the `executeToolCalls` context wrapper, so the\n // UIResourceEvent.value.toolCallId / UIResourcePart.toolCallId contract is\n // still satisfied downstream.\n context.emitCustomEvent('ui-resource', {\n resource: {\n uri: matched.uri,\n mimeType: matched.mimeType ?? 'text/html',\n text: matched.text,\n blob: matched.blob,\n },\n serverId: mcp.serverId,\n toolName: mcp.serverToolName ?? tool.name,\n meta: undefined,\n })\n}\n\n/**\n * Optional middleware hooks for tool execution.\n * When provided, these callbacks are invoked before/after each tool execution.\n */\nexport interface ToolExecutionMiddlewareHooks {\n onBeforeToolCall?: (\n toolCall: ToolCall,\n tool: Tool | undefined,\n args: unknown,\n ) => Promise<BeforeToolCallDecision>\n onAfterToolCall?: (info: AfterToolCallInfo) => Promise<void>\n}\n\n/**\n * Error thrown when middleware decides to abort the chat run during tool execution.\n */\nexport class MiddlewareAbortError extends Error {\n constructor(reason: string) {\n super(reason)\n this.name = 'MiddlewareAbortError'\n }\n}\n\n// The leaf context-inference primitives (ContextFromTool, MergeContext,\n// UnionToIntersection, DefinedContext) are shared with the chat activity\n// options layer — see ../runtime-context-types.\ntype RequiredContextFromToolUnion<T> = T extends unknown\n ? undefined extends ContextFromTool<T>\n ? never\n : ContextFromTool<T>\n : never\n\ntype ContextFromToolUnion<T> = [\n UnionToIntersection<DefinedContext<ContextFromTool<T>>>,\n] extends [never]\n ? unknown\n : [RequiredContextFromToolUnion<T>] extends [never]\n ? UnionToIntersection<DefinedContext<ContextFromTool<T>>> | undefined\n : UnionToIntersection<DefinedContext<ContextFromTool<T>>>\n\ntype ContextFromTools<TTools> = TTools extends readonly [\n infer THead,\n ...infer TTail,\n]\n ? MergeContext<ContextFromTool<THead>, ContextFromTools<TTail>>\n : TTools extends ReadonlyArray<infer TTool>\n ? ContextFromToolUnion<TTool>\n : unknown\n\ntype ExecuteToolsContextArgs<TContext> = undefined extends TContext\n ? [userContext?: TContext]\n : [userContext: TContext]\n\n/**\n * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.\n *\n * Responsibilities:\n * - Accumulates streaming tool call events (ID, name, arguments)\n * - Validates tool calls (filters out incomplete ones)\n * - Executes tool `execute` functions with parsed arguments\n * - Emits `TOOL_CALL_END` events for client visibility\n * - Returns tool result messages for conversation history\n *\n * This class is used internally by the AI.chat() method to handle the automatic\n * tool execution loop. It can also be used independently for custom tool execution logic.\n *\n * @example\n * ```typescript\n * const manager = new ToolCallManager(tools);\n *\n * // During streaming, accumulate tool calls\n * for await (const chunk of stream) {\n * if (chunk.type === 'TOOL_CALL_START') {\n * manager.addToolCallStartEvent(chunk);\n * } else if (chunk.type === 'TOOL_CALL_ARGS') {\n * manager.addToolCallArgsEvent(chunk);\n * }\n * }\n *\n * // After stream completes, execute tools\n * if (manager.hasToolCalls()) {\n * const toolResults = yield* manager.executeTools(finishEvent);\n * messages = [...messages, ...toolResults];\n * manager.clear();\n * }\n * ```\n */\nexport class ToolCallManager<\n TToolsOrContext = ReadonlyArray<AnyTool>,\n TContext = TToolsOrContext extends ReadonlyArray<AnyTool>\n ? ContextFromTools<TToolsOrContext>\n : TToolsOrContext,\n> {\n private readonly toolCallsMap = new Map<number, ToolCall>()\n private readonly tools: TToolsOrContext extends ReadonlyArray<AnyTool>\n ? TToolsOrContext\n : ReadonlyArray<AnyTool>\n\n constructor(\n tools: TToolsOrContext extends ReadonlyArray<AnyTool>\n ? TToolsOrContext\n : ReadonlyArray<AnyTool>,\n ) {\n this.tools = tools\n }\n\n /**\n * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)\n */\n addToolCallStartEvent(event: ToolCallStartEvent): void {\n // AG-UI's TOOL_CALL_START carries no index, and a non-first-party or\n // malformed producer can send a second START for a toolCallId that is\n // already tracked. Without this guard, a repeat with the same index\n // overwrites the slot (wiping any TOOL_CALL_ARGS already accumulated),\n // and a repeat with a missing/different index inserts a duplicate row\n // that getToolCalls() returns twice, running the tool twice.\n for (const toolCall of this.toolCallsMap.values()) {\n if (toolCall.id === event.toolCallId) return\n }\n const index = (event as AdapterYieldChunk).index ?? this.toolCallsMap.size\n const name = event.toolCallName ?? event.toolName\n this.toolCallsMap.set(index, {\n id: event.toolCallId,\n type: 'function',\n function: {\n name,\n arguments: '',\n },\n ...(event.metadata !== undefined && { metadata: event.metadata }),\n })\n }\n\n /**\n * Add a TOOL_CALL_ARGS event to accumulate arguments (AG-UI)\n */\n addToolCallArgsEvent(event: ToolCallArgsEvent): void {\n const extra = event as AdapterYieldChunk\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n if (typeof extra.args === 'string' && extra.args !== '') {\n toolCall.function.arguments = extra.args\n } else {\n toolCall.function.arguments += event.delta\n }\n break\n }\n }\n }\n\n /**\n * Complete a tool call with its final input\n * Called when TOOL_CALL_END is received\n */\n completeToolCall(event: ToolCallEndEvent): void {\n for (const toolCall of this.toolCallsMap.values()) {\n if (toolCall.id !== event.toolCallId) continue\n if (event.input === undefined) return\n const normalized =\n event.input && typeof event.input === 'object' ? event.input : {}\n toolCall.function.arguments = JSON.stringify(normalized)\n return\n }\n }\n\n /**\n * Check if there are any complete tool calls to execute\n */\n hasToolCalls(): boolean {\n return this.getToolCalls().length > 0\n }\n\n /**\n * Get all complete tool calls (filtered for valid ID and name)\n */\n getToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCallsMap.values()).filter(\n (tc) => tc.id && tc.function.name && tc.function.name.trim().length > 0,\n )\n }\n\n /**\n * Execute all tool calls and return tool result messages\n * Yields TOOL_CALL_END events for streaming\n * @param finishEvent - RUN_FINISHED event from the stream\n */\n async *executeTools(\n finishEvent: RunFinishedEvent,\n ...contextArgs: ExecuteToolsContextArgs<TContext>\n ): AsyncGenerator<AdapterYieldChunk, Array<ModelMessage>, void> {\n const toolCallsArray = this.getToolCalls()\n const toolResults: Array<ModelMessage> = []\n const hasRuntimeContext = contextArgs.length > 0\n const userContext = contextArgs[0]\n\n for (const toolCall of toolCallsArray) {\n const tool = this.tools.find((t) => t.name === toolCall.function.name)\n\n let toolResultContent: string | Array<ContentPart>\n let toolResultState: ToolOutputState | undefined\n // Holds the parsed/validated execution output before serialization.\n // Stays `undefined` when the tool has no `execute` (client-only\n // tools) or when execution throws.\n let toolOutput: unknown\n if (tool?.execute) {\n try {\n // Parse arguments (normalize null/non-object to {} for empty tool_use blocks)\n let args: unknown\n try {\n const argsString = toolCall.function.arguments.trim() || '{}'\n const parsed = JSON.parse(argsString)\n args = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n throw new Error(\n `Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`,\n )\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n args = parseWithStandardSchema(tool.inputSchema, args)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Input validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n // Execute the tool\n const executionContext = {\n toolCallId: toolCall.id,\n context: userContext,\n emitCustomEvent: () => {},\n } as ToolExecutionContext<TContext>\n let result = hasRuntimeContext\n ? await tool.execute(args, executionContext)\n : await tool.execute(args)\n\n // Validate output against outputSchema if provided (for Standard\n // Schema compliant schemas). Unlike the previous implementation we\n // intentionally validate `undefined`/`null` results too, so a tool\n // whose schema forbids them surfaces a validation error instead of\n // silently passing — the schema itself decides whether they're valid.\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n try {\n result = parseWithStandardSchema(tool.outputSchema, result)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n toolOutput = result\n toolResultContent = normalizeToolResult(result)\n } catch (error: unknown) {\n // If tool execution fails, add error message\n const message =\n error instanceof Error ? error.message : 'Unknown error'\n toolResultContent = `Error executing tool: ${message}`\n toolResultState = 'output-error'\n }\n } else {\n // Tool doesn't have execute function, add placeholder\n toolResultContent = `Tool ${toolCall.function.name} does not have an execute function`\n }\n\n // Emit TOOL_CALL_END event\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolCallName: toolCall.function.name,\n toolName: toolCall.function.name,\n model: (() => {\n const model = tanstackMetadata(finishEvent)?.model\n return typeof model === 'string' ? model : undefined\n })(),\n timestamp: Date.now(),\n // Typed parsed output (undefined for failed exec / client-only tools).\n ...(toolOutput !== undefined ? { output: toolOutput } : {}),\n result: toolResultContent,\n ...(toolResultState !== undefined && { state: toolResultState }),\n }\n\n // Add tool result message\n toolResults.push({\n role: 'tool',\n content: toolResultContent,\n toolCallId: toolCall.id,\n })\n }\n\n return toolResults\n }\n\n /**\n * Clear the tool calls map for the next iteration\n */\n clear(): void {\n this.toolCallsMap.clear()\n }\n}\n\nexport interface ToolResult {\n toolCallId: string\n toolName: string\n result: any\n state?: 'output-available' | 'output-error'\n /** Set when the user or middleware cancelled or denied the tool call; state is output-error. */\n outcome?: ToolResultOutcome\n /** Duration of tool execution in milliseconds (only for server-executed tools) */\n duration?: number\n /**\n * Parsed tool input (after JSON parse + optional Standard Schema validation).\n * Parsed tool input after JSON parse + optional Standard Schema validation.\n */\n input?: unknown\n /**\n * Parsed tool output before wire serialization. Surfaced on engine-emitted\n * `TOOL_CALL_END` events so consumers can read typed `output` without\n * re-parsing `result`. Undefined on error paths and when execution is skipped.\n */\n output?: unknown\n}\n\nexport interface ApprovalRequest {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n}\n\nexport interface ClientToolRequest {\n toolCallId: string\n toolName: string\n input: any\n}\n\n/** Form or sampling input that paused a server tool. */\nexport interface McpInputRequest {\n toolCallId: string\n toolName: string\n kind: 'form' | 'sampling'\n request: unknown\n}\n\ninterface McpInputRequiredThrow {\n name: 'MCPInputRequiredError'\n kind: 'form' | 'sampling'\n request: unknown\n}\n\nfunction isMcpInputRequired(value: unknown): value is McpInputRequiredThrow {\n if (typeof value !== 'object' || value === null) return false\n if (!('name' in value) || value.name !== 'MCPInputRequiredError') {\n return false\n }\n if (!('kind' in value)) return false\n const kindIsFormOrSampling =\n value.kind === 'form' || value.kind === 'sampling'\n if (!kindIsFormOrSampling) return false\n return 'request' in value\n}\n\nexport interface ToolResumeExecutionState {\n clientToolErrors?: ReadonlyMap<string, string>\n deniedToolResults?: ReadonlyMap<string, unknown>\n cancelledToolCallIds?: ReadonlySet<string>\n /** Answers to `mcp_input` interrupts, by tool call id. */\n inputResponses?: ReadonlyMap<string, ToolInputResponse>\n}\n\nfunction approvalResolution(\n approvals: ReadonlyMap<string, ToolApprovalResolution>,\n toolCallId: string,\n): ToolApprovalResolution | undefined {\n return approvals.get(toolCallId) ?? approvals.get(`approval_${toolCallId}`)\n}\n\nfunction isApproved(resolution: ToolApprovalResolution): boolean {\n return typeof resolution === 'boolean' ? resolution : resolution.approved\n}\n\nfunction editedApprovalArgs(\n resolution: ToolApprovalResolution,\n): unknown | undefined {\n return typeof resolution === 'object' && resolution.approved\n ? resolution.editedArgs\n : undefined\n}\n\nfunction deniedApprovalResult(resolution: ToolApprovalResolution): unknown {\n return typeof resolution === 'object' && !resolution.approved\n ? (resolution.payload ?? { error: 'User declined tool execution' })\n : { error: 'User declined tool execution' }\n}\n\ninterface ExecuteToolCallsResult {\n /** Tool results ready to send to LLM */\n results: Array<ToolResult>\n /** Tools that need user approval before execution */\n needsApproval: Array<ApprovalRequest>\n /** Tools that need client-side execution */\n needsClientExecution: Array<ClientToolRequest>\n /** Server tools that paused for MCP form or sampling input */\n inputRequired: Array<McpInputRequest>\n /** Interrupts raised by subagents that run as tools */\n subagentInterrupts: Array<Interrupt>\n}\n\n/**\n * Helper that runs a tool execution promise while polling for pending custom events.\n * Yields any custom events that are emitted during execution, then returns the\n * execution result.\n */\nasync function* executeWithEventPolling<T>(\n executionPromise: Promise<T>,\n pendingEvents: Array<CustomEvent | StreamChunk>,\n): AsyncGenerator<CustomEvent | StreamChunk, T, void> {\n // Use an object to track mutable state across the async boundary\n const state = { done: false, result: undefined as T }\n const executionWithFlag = executionPromise.then((r) => {\n state.done = true\n state.result = r\n return r\n })\n\n while (!state.done) {\n // Wait for either the execution to complete or a short timeout\n await Promise.race([\n executionWithFlag,\n new Promise((resolve) => setTimeout(resolve, 10)),\n ])\n\n // Flush any pending events\n let event: CustomEvent | StreamChunk | undefined\n while ((event = pendingEvents.shift()) !== undefined) {\n yield event\n }\n }\n\n // Final flush in case events were emitted right at completion\n let event: CustomEvent | StreamChunk | undefined\n while ((event = pendingEvents.shift()) !== undefined) {\n yield event\n }\n\n return state.result\n}\n\n/**\n * Apply a middleware onBeforeToolCall decision.\n * Returns the (possibly transformed) input if execution should proceed,\n * or undefined if the tool call was skipped (result already pushed).\n * Throws MiddlewareAbortError if the decision is 'abort'.\n */\nasync function applyBeforeToolCallDecision(\n toolCall: ToolCall,\n tool: Tool,\n input: unknown,\n toolName: string,\n middlewareHooks: ToolExecutionMiddlewareHooks,\n results: Array<ToolResult>,\n): Promise<{ proceed: true; input: unknown } | { proceed: false }> {\n if (!middlewareHooks.onBeforeToolCall) {\n return { proceed: true, input }\n }\n\n const decision = await middlewareHooks.onBeforeToolCall(toolCall, tool, input)\n if (!decision) {\n return { proceed: true, input }\n }\n\n if (decision.type === 'abort') {\n throw new MiddlewareAbortError(decision.reason || 'Aborted by middleware')\n }\n\n if (decision.type === 'skip') {\n const skipResult = decision.result\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof skipResult === 'string'\n ? safeJsonParse(skipResult)\n : (skipResult ?? null),\n duration: 0,\n })\n if (middlewareHooks.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration: 0,\n result: skipResult,\n })\n }\n return { proceed: false }\n }\n\n return { proceed: true, input: decision.args }\n}\n\n/**\n * Execute a server-side tool with event polling, output validation, and middleware hooks.\n * Yields CustomEvent chunks during execution and pushes the result to the results array.\n */\nexport async function* executeServerTool<TContext = unknown>(\n toolCall: ToolCall,\n tool: AnyTool,\n toolName: string,\n input: unknown,\n context: ToolExecutionContext<TContext>,\n pendingEvents: Array<CustomEvent | StreamChunk>,\n results: Array<ToolResult>,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n inputRequired?: Array<McpInputRequest>,\n subagentInterrupts?: Array<Interrupt>,\n): AsyncGenerator<CustomEvent | StreamChunk, void, void> {\n const startTime = Date.now()\n try {\n if (!tool.execute) {\n throw new Error(`Tool ${toolName} has no execute() implementation`)\n }\n const subagent = isSubagentTool(tool)\n if (subagent) {\n Object.assign(context, {\n [EMIT_STREAM_CHUNK]: (chunk: StreamChunk) => pendingEvents.push(chunk),\n })\n }\n const executionPromise = Promise.resolve(tool.execute(input, context))\n let result = yield* executeWithEventPolling(executionPromise, pendingEvents)\n const duration = Date.now() - startTime\n\n if (subagent) {\n const outcome = result as SubagentToolOutcome\n if (outcome.interrupts?.length) {\n // The child waits for outside input. Leave the call open so the\n // resume run executes it again and continues the same child.\n subagentInterrupts?.push(...outcome.interrupts)\n return\n }\n const modelResult = outcome.error\n ? { subagentRunId: outcome.subagentRunId, error: outcome.error }\n : { subagentRunId: outcome.subagentRunId, result: outcome.text }\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: modelResult,\n input,\n output: modelResult,\n duration,\n ...(outcome.error ? { state: 'output-error' as const } : {}),\n })\n await middlewareHooks?.onAfterToolCall?.({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n duration,\n ...(outcome.error\n ? { ok: false as const, error: new Error(outcome.error) }\n : { ok: true as const, result: modelResult }),\n })\n return\n }\n\n // MCP Apps: if this tool links a ui:// resource, eagerly read it and queue\n // a `ui-resource` CUSTOM event. The MCP source stays live until the run\n // drains (MCPManager's `connection:'close'` policy disposes on completion),\n // so `readResource` is callable here. Fail-soft: a read error warns and\n // emits nothing — the text result still flows.\n await emitUiResourceIfLinked(tool, context)\n\n // Flush remaining events (including any queued ui-resource event)\n let pendingEvent: CustomEvent | StreamChunk | undefined\n while ((pendingEvent = pendingEvents.shift()) !== undefined) {\n yield pendingEvent\n }\n\n // Validate output against outputSchema if provided. Validates\n // `undefined`/`null` too — the schema decides whether they're valid.\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n const finalResult =\n typeof result === 'string' ? safeJsonParse(result) : (result ?? null)\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: finalResult,\n input,\n output: finalResult,\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration,\n result: finalResult,\n })\n }\n } catch (error: unknown) {\n const duration = Date.now() - startTime\n\n // Flush remaining events\n let pendingEvent: CustomEvent | StreamChunk | undefined\n while ((pendingEvent = pendingEvents.shift()) !== undefined) {\n yield pendingEvent\n }\n\n if (error instanceof MiddlewareAbortError) {\n throw error\n }\n\n // Same shape as MCPInputRequiredError. Pause instead of a tool error.\n if (isMcpInputRequired(error)) {\n if (!inputRequired) throw error\n inputRequired.push({\n toolCallId: toolCall.id,\n toolName,\n kind: error.kind,\n request: error.request,\n })\n return\n }\n\n const message = error instanceof Error ? error.message : 'Unknown error'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: message },\n input,\n state: 'output-error',\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: false,\n duration,\n error,\n })\n }\n }\n}\n\nasync function buildClientToolResult(\n toolCallId: string,\n toolName: string,\n tool: AnyTool,\n rawResult: unknown,\n input?: unknown,\n errorText?: string,\n): Promise<ToolResult> {\n if (errorText !== undefined) {\n return {\n toolCallId,\n toolName,\n result: { error: errorText },\n input,\n state: 'output-error',\n }\n }\n\n try {\n let result = rawResult\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n const validation = await validateWithStandardSchema<unknown>(\n tool.outputSchema,\n result,\n )\n if (!validation.success) {\n throw new StandardSchemaValidationError(validation.issues)\n }\n result = validation.data\n }\n\n const parsed =\n typeof result === 'string' ? safeJsonParse(result) : (result ?? null)\n return {\n toolCallId,\n toolName,\n result: parsed,\n input,\n output: parsed,\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Validation failed'\n return {\n toolCallId,\n toolName,\n result: { error: message },\n input,\n state: 'output-error',\n }\n }\n}\n\n/**\n * Execute tool calls based on their configuration.\n * Yields CustomEvent chunks during tool execution for real-time progress updates.\n *\n * Handles three cases:\n * 1. Client tools (no execute) - request client to execute\n * 2. Server tools with approval - check approval before executing\n * 3. Normal server tools - execute immediately\n *\n * @param toolCalls - Tool calls from the LLM\n * @param tools - Available tools with their configurations\n * @param approvals - Map keyed by toolCallId (or `approval_${toolCallId}`) → ToolApprovalResolution\n * @param clientResults - Map of client-side execution results (toolCallId -> result)\n * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)\n * @param toolExecution - `'parallel'` (default) prepares every call in call\n * order, then starts the server tools together. `'sequential'` runs one\n * call at a time. Results come back in call order either way.\n */\nexport async function* executeToolCalls<TContext = unknown>(\n toolCalls: Array<ToolCall>,\n tools: ReadonlyArray<AnyTool>,\n approvals: Map<string, ToolApprovalResolution> = new Map(),\n clientResults: Map<string, any> = new Map(),\n createCustomEventChunk?: (\n eventName: string,\n value: Record<string, any>,\n options?: EmitCustomEventOptions,\n ) => CustomEvent,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n userContext?: TContext,\n abortSignal?: AbortSignal,\n resumeState?: ToolResumeExecutionState,\n toolExecution: NonNullable<TextOptions['toolExecution']> = 'parallel',\n): AsyncGenerator<CustomEvent | StreamChunk, ExecuteToolCallsResult, void> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\n const inputRequired: Array<McpInputRequest> = []\n\n // Create tool lookup map\n const toolMap = new Map<string, AnyTool>()\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n const runsInOrder = toolExecution === 'sequential'\n // Parallel mode collects the server runs here. `mergeStreams` starts them\n // together after the loop.\n const runs: Array<AsyncGenerator<CustomEvent | StreamChunk, void, void>> = []\n // Errors thrown by a run or a before-hook. They are rethrown only after every\n // started tool has finished, so no tool outlives the batch.\n const failures: Array<unknown> = []\n // Each call's subagent interrupts, so they come back in call order.\n const interruptsByCall = new Map<string, Array<Interrupt>>()\n\n // A tool that has not started when the run aborts never starts. It gets an\n // error result, so every call of the batch still has a result.\n async function* runServerTool(\n toolCall: ToolCall,\n tool: AnyTool,\n toolName: string,\n input: unknown,\n context: ToolExecutionContext<TContext>,\n pendingEvents: Array<CustomEvent | StreamChunk>,\n ): AsyncGenerator<CustomEvent | StreamChunk, void, void> {\n try {\n if (abortSignal?.aborted) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'Operation aborted' },\n input,\n state: 'output-error',\n duration: 0,\n })\n await middlewareHooks?.onAfterToolCall?.({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: false,\n duration: 0,\n error: new Error('Operation aborted'),\n })\n return\n }\n const interrupts: Array<Interrupt> = []\n interruptsByCall.set(toolCall.id, interrupts)\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n inputRequired,\n interrupts,\n )\n } catch (error) {\n failures.push(error)\n }\n }\n\n // Batch gating: when any tool in the batch still needs an approval decision,\n // defer all execution so side effects don't happen before the user decides.\n const hasPendingApprovals = toolCalls.some((tc) => {\n const t = toolMap.get(tc.function.name)\n return (\n t?.needsApproval &&\n approvalResolution(approvals, tc.id) === undefined &&\n !resumeState?.cancelledToolCallIds?.has(tc.id)\n )\n })\n\n for (const toolCall of toolCalls) {\n // Provider-executed tools (Anthropic web_search / web_fetch) already ran\n // inside the provider response and carry their result on the call's\n // metadata. They have no execute() and must not become client requests.\n if (isProviderExecutedToolCall(toolCall)) continue\n\n const tool = toolMap.get(toolCall.function.name)\n const toolName = toolCall.function.name\n\n if (!tool) {\n // Unknown tool - return error\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: `Unknown tool: ${toolName}` },\n state: 'output-error',\n })\n continue\n }\n\n // Skip non-pending tools while approvals are outstanding\n if (hasPendingApprovals) {\n const isPendingApproval =\n tool.needsApproval &&\n approvalResolution(approvals, toolCall.id) === undefined\n const isPlainClientRequest = !tool.needsApproval && !tool.execute\n if (!isPendingApproval && !isPlainClientRequest) {\n continue\n }\n }\n\n if (resumeState?.cancelledToolCallIds?.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'Tool execution cancelled' },\n state: 'output-error',\n outcome: 'cancelled',\n })\n continue\n }\n\n // Parse arguments\n let input: unknown = {}\n const argsStr = toolCall.function.arguments.trim() || '{}'\n if (argsStr) {\n try {\n const parsed = JSON.parse(argsStr)\n // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)\n input = parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Failed to parse tool arguments as JSON: ${argsStr}`,\n },\n input,\n state: 'output-error',\n })\n continue\n }\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n input = parseWithStandardSchema(tool.inputSchema, input)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Input validation failed for tool ${tool.name}: ${message}`,\n },\n // raw parse may have failed validation — still attach best-effort input\n input,\n state: 'output-error',\n })\n continue\n }\n }\n\n // Create a ToolExecutionContext for this tool call with event emission\n const pendingEvents: Array<CustomEvent | StreamChunk> = []\n const inputResponse = resumeState?.inputResponses?.get(toolCall.id)\n const context = {\n toolCallId: toolCall.id,\n context: userContext,\n abortSignal,\n ...(inputResponse !== undefined ? { inputResponse } : {}),\n emitCustomEvent: (\n eventName: string,\n value: Record<string, any>,\n options?: EmitCustomEventOptions,\n ) => {\n if (createCustomEventChunk) {\n pendingEvents.push(\n createCustomEventChunk(\n eventName,\n {\n ...value,\n toolCallId: toolCall.id,\n },\n options,\n ),\n )\n }\n },\n } as ToolExecutionContext<TContext>\n\n // CASE 1: Client-side tool (no execute function)\n if (!tool.execute) {\n // Check if tool needs approval\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n const resolution = approvalResolution(approvals, toolCall.id)\n\n // Check if approval decision exists\n if (resolution !== undefined) {\n const approved = isApproved(resolution)\n\n if (approved) {\n input = editedApprovalArgs(resolution) ?? input\n // Approved - check if client has executed\n const clientError = resumeState?.clientToolErrors?.get(toolCall.id)\n if (clientResults.has(toolCall.id) || clientError !== undefined) {\n results.push(\n await buildClientToolResult(\n toolCall.id,\n toolName,\n tool,\n clientResults.get(toolCall.id),\n input,\n clientError,\n ),\n )\n } else {\n // Approved but not executed yet - request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n resumeState?.deniedToolResults?.get(toolCall.id) ??\n deniedApprovalResult(resolution),\n input,\n state: 'output-error',\n outcome: 'denied',\n })\n }\n } else {\n // Need approval first\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n input,\n approvalId,\n })\n }\n } else {\n // No approval needed - check if client has executed\n const clientError = resumeState?.clientToolErrors?.get(toolCall.id)\n if (clientResults.has(toolCall.id) || clientError !== undefined) {\n results.push(\n await buildClientToolResult(\n toolCall.id,\n toolName,\n tool,\n clientResults.get(toolCall.id),\n input,\n clientError,\n ),\n )\n } else {\n // Request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n }\n continue\n }\n\n // CASE 2: Server tool with approval required\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n const resolution = approvalResolution(approvals, toolCall.id)\n\n if (resolution === undefined) {\n // Need approval\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n approvalId,\n })\n continue\n }\n if (!isApproved(resolution)) {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n resumeState?.deniedToolResults?.get(toolCall.id) ??\n deniedApprovalResult(resolution),\n input,\n state: 'output-error',\n outcome: 'denied',\n })\n continue\n }\n // Approved: run it like any other server tool below.\n input = editedApprovalArgs(resolution) ?? input\n }\n\n // CASE 3: Server tool, approved or with no approval\n if (middlewareHooks) {\n let decision: Awaited<ReturnType<typeof applyBeforeToolCallDecision>>\n try {\n decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n } catch (error) {\n // The calls prepared so far still run, as they would one at a time.\n failures.push(error)\n break\n }\n if (!decision.proceed) continue\n input = decision.input\n }\n\n const run = runServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n )\n if (!runsInOrder) {\n runs.push(run)\n continue\n }\n yield* run\n if (failures.length > 0) break\n }\n\n yield* mergeStreams(runs)\n if (failures.length > 0) throw failures[0]\n\n // Parallel tools finish in any order. The model gets the results, input\n // requests, and interrupts in the order it made the calls.\n const callOrder = new Map(toolCalls.map((tc, index) => [tc.id, index]))\n const byCallOrder = (a: { toolCallId: string }, b: { toolCallId: string }) =>\n (callOrder.get(a.toolCallId) ?? 0) - (callOrder.get(b.toolCallId) ?? 0)\n results.sort(byCallOrder)\n inputRequired.sort(byCallOrder)\n\n return {\n results,\n needsApproval,\n needsClientExecution,\n inputRequired,\n subagentInterrupts: toolCalls.flatMap(\n (tc) => interruptsByCall.get(tc.id) ?? [],\n ),\n }\n}\n"],"mappings":"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"}
@@ -52,6 +52,7 @@ export interface ServerTool<TInput extends SchemaInput | undefined = undefined,
52
52
  outputSchema?: TOutput;
53
53
  needsApproval?: TNeedsApproval;
54
54
  approvalSchema?: TApprovalSchema;
55
+ execution?: 'task';
55
56
  }
56
57
  /**
57
58
  * Marker type for client-side tools
@@ -64,6 +65,7 @@ export interface ClientTool<TInput extends SchemaInput | undefined = undefined,
64
65
  outputSchema?: TOutput;
65
66
  needsApproval?: TNeedsApproval;
66
67
  approvalSchema?: TApprovalSchema;
68
+ execution?: 'task';
67
69
  lazy?: boolean;
68
70
  metadata?: Record<string, unknown>;
69
71
  execute?: ToolExecuteFunction<TInput, TOutput, TContext>;
@@ -81,6 +83,7 @@ export interface ToolDefinitionInstance<TInput extends SchemaInput | undefined =
81
83
  outputSchema: TOutput;
82
84
  needsApproval?: TNeedsApproval;
83
85
  approvalSchema: TApprovalSchema;
86
+ execution?: 'task';
84
87
  readonly [toolApprovalCapability]?: {
85
88
  needsApproval: TNeedsApproval;
86
89
  approvalSchema: TApprovalSchema;
@@ -122,6 +125,7 @@ export type ToolDefinitionConfig<TInput extends SchemaInput | undefined = undefi
122
125
  outputSchema?: TOutput;
123
126
  lazy?: boolean;
124
127
  metadata?: Record<string, unknown>;
128
+ execution?: 'task';
125
129
  } & ApprovalConfig<TNeedsApproval, TApprovalSchema>;
126
130
  /**
127
131
  * Tool definition builder that allows creating server or client tools from a shared definition
@@ -60,6 +60,7 @@ function toolDefinition(config) {
60
60
  const outputSchema = config.outputSchema;
61
61
  const approvalSchema = config.approvalSchema;
62
62
  const needsApproval = config.needsApproval;
63
+ const execution = config.execution;
63
64
  return {
64
65
  __toolSide: "definition",
65
66
  ...config,
@@ -67,6 +68,7 @@ function toolDefinition(config) {
67
68
  outputSchema,
68
69
  approvalSchema,
69
70
  needsApproval,
71
+ execution,
70
72
  server(execute) {
71
73
  return {
72
74
  __toolSide: "server",
@@ -75,6 +77,7 @@ function toolDefinition(config) {
75
77
  outputSchema,
76
78
  approvalSchema,
77
79
  needsApproval,
80
+ execution,
78
81
  execute
79
82
  };
80
83
  },
@@ -86,6 +89,7 @@ function toolDefinition(config) {
86
89
  outputSchema,
87
90
  approvalSchema,
88
91
  needsApproval,
92
+ execution,
89
93
  ...execute !== void 0 && { execute }
90
94
  };
91
95
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tool-definition.js","names":[],"sources":["../../../../../src/activities/chat/tools/tool-definition.ts"],"sourcesContent":["import type {\n InferSchemaType,\n JSONSchema,\n SchemaInput,\n Tool,\n ToolExecuteFunction,\n} from '../../../types'\nimport type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from '@standard-schema/spec'\n\n/**\n * Type-level brand key for {@link ToolApprovalCapabilityMarker}. Only ever used\n * in type positions, but it must stay exported: a `unique symbol` referenced by\n * an exported interface has to be nameable in the emitted declarations.\n *\n * @public\n */\nexport declare const toolApprovalCapability: unique symbol\n\nexport interface ToolApprovalCapabilityMarker<\n TNeedsApproval extends boolean,\n TApprovalSchema,\n> {\n readonly [toolApprovalCapability]?: {\n needsApproval: TNeedsApproval\n approvalSchema: TApprovalSchema\n }\n}\n\nexport type ApprovalSchemaConfig =\n | SchemaInput\n | { approve: SchemaInput; reject?: SchemaInput }\n | { approve?: SchemaInput; reject: SchemaInput }\n\ntype ApprovalConfig<\n TNeedsApproval extends boolean,\n TApprovalSchema extends ApprovalSchemaConfig | undefined,\n> = TNeedsApproval extends true\n ? { needsApproval: TNeedsApproval; approvalSchema?: TApprovalSchema }\n : { needsApproval?: TNeedsApproval; approvalSchema?: never }\n\nexport type ApprovalCapabilityOf<TTool> =\n TTool extends ToolApprovalCapabilityMarker<infer TNeeds, unknown>\n ? TNeeds\n : false\n\nexport type ApprovalSchemaOf<TTool> =\n TTool extends ToolApprovalCapabilityMarker<boolean, infer TSchema>\n ? TSchema\n : undefined\n\nexport declare const noSchema: unique symbol\nexport type NoSchema = typeof noSchema\n\nexport type InputSchemaOf<TTool> = TTool extends {\n inputSchema: infer TInput\n}\n ? TInput extends undefined\n ? NoSchema\n : TInput\n : NoSchema\n\nexport type OutputSchemaOf<TTool> = TTool extends {\n outputSchema: infer TOutput\n}\n ? TOutput extends undefined\n ? NoSchema\n : TOutput\n : NoSchema\n\ntype BuiltToolSchemaFields<\n TInput extends SchemaInput | undefined,\n TOutput extends SchemaInput | undefined,\n TApprovalSchema extends ApprovalSchemaConfig | undefined,\n> = {\n inputSchema: TInput\n outputSchema: TOutput\n approvalSchema: TApprovalSchema\n}\n\n/**\n * Marker type for server-side tools\n */\nexport interface ServerTool<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n TContext = unknown,\n TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n>\n extends\n Tool<TInput, TOutput, TName, TContext>,\n ToolApprovalCapabilityMarker<TNeedsApproval, TApprovalSchema> {\n __toolSide: 'server'\n inputSchema?: TInput\n outputSchema?: TOutput\n needsApproval?: TNeedsApproval\n approvalSchema?: TApprovalSchema\n}\n\n/**\n * Marker type for client-side tools\n */\nexport interface ClientTool<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n TContext = unknown,\n // Captured as a literal (`true` / `false`) so downstream types — notably\n // the tool-call part's `approval` field — can be gated on it. Defaults to\n // `false` when the tool config omits `needsApproval`.\n TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n> extends ToolApprovalCapabilityMarker<TNeedsApproval, TApprovalSchema> {\n __toolSide: 'client'\n name: TName\n description: string\n // Note: `inputSchema` / `outputSchema` stay as bare optionals (not\n // widened to `| undefined`). They participate in inference via\n // `InferToolInput` / `InferToolOutput` — widening with `| undefined`\n // breaks the `infer TInput extends StandardJSONSchemaV1<...>` chain\n // because `undefined` doesn't extend the schema constraint.\n inputSchema?: TInput\n outputSchema?: TOutput\n needsApproval?: TNeedsApproval\n approvalSchema?: TApprovalSchema\n lazy?: boolean\n metadata?: Record<string, unknown>\n execute?: ToolExecuteFunction<TInput, TOutput, TContext>\n}\n\n/** Broad server-tool shape for heterogeneous internal collections. */\nexport type AnyServerTool = Omit<\n ServerTool<any, any, string, any, boolean, any>,\n 'execute'\n> & {\n execute?: ((args: any, context?: any) => any) | undefined\n}\n\n/**\n * Tool definition that can be used directly or instantiated for server/client\n */\nexport interface ToolDefinitionInstance<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n TContext = unknown,\n TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n> extends Tool<TInput, TOutput, TName, TContext> {\n __toolSide: 'definition'\n // Narrow the base `needsApproval?: boolean` to the captured literal so it\n // survives into `ToolCallPartForTool`'s approval gate.\n inputSchema: TInput\n outputSchema: TOutput\n needsApproval?: TNeedsApproval\n approvalSchema: TApprovalSchema\n readonly [toolApprovalCapability]?: {\n needsApproval: TNeedsApproval\n approvalSchema: TApprovalSchema\n }\n}\n\n/**\n * Union type for any kind of client-side tool (client tool or definition)\n */\nexport type AnyClientTool =\n | (Omit<ClientTool<any, any, string, any, boolean, any>, 'execute'> & {\n execute?: ((args: any, context?: any) => any) | undefined\n })\n | (Omit<\n ToolDefinitionInstance<any, any, string, any, boolean, any>,\n 'execute'\n > & {\n execute?: ((args: any, context?: any) => any) | undefined\n })\n\n/**\n * Extract the tool name as a literal type\n */\nexport type InferToolName<T> = T extends { name: infer N } ? N : never\n\n/**\n * Extract the input type from a tool (inferred from Standard JSON Schema, or `unknown` for plain JSONSchema)\n */\nexport type InferToolInput<T> = T extends { inputSchema?: infer TInput }\n ? TInput extends JSONSchema\n ? unknown\n : InferSchemaType<TInput>\n : unknown\n\n/**\n * Extract the output type from a tool (inferred from Standard JSON Schema, or `unknown` for plain JSONSchema)\n */\nexport type InferToolOutput<T> = T extends { outputSchema?: infer TOutput }\n ? TOutput extends StandardJSONSchemaV1<any, any>\n ? InferSchemaType<TOutput>\n : TOutput extends StandardSchemaV1<any, any>\n ? InferSchemaType<TOutput>\n : TOutput extends JSONSchema\n ? unknown\n : InferSchemaType<TOutput>\n : unknown\n\n/**\n * Tool definition configuration\n */\nexport type ToolDefinitionConfig<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n> = {\n name: TName\n description: string\n inputSchema?: TInput\n outputSchema?: TOutput\n lazy?: boolean\n metadata?: Record<string, unknown>\n} & ApprovalConfig<TNeedsApproval, TApprovalSchema>\n\n/**\n * Tool definition builder that allows creating server or client tools from a shared definition\n */\nexport interface ToolDefinition<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n> extends ToolDefinitionInstance<\n TInput,\n TOutput,\n TName,\n unknown,\n TNeedsApproval,\n TApprovalSchema\n> {\n /**\n * Create a server-side tool with execute function\n */\n server: <TContext = unknown>(\n execute: ToolExecuteFunction<TInput, TOutput, TContext>,\n ) => ServerTool<\n TInput,\n TOutput,\n TName,\n TContext,\n TNeedsApproval,\n TApprovalSchema\n > &\n BuiltToolSchemaFields<TInput, TOutput, TApprovalSchema>\n\n /**\n * Create a client-side tool with optional execute function.\n * Carries the definition's `needsApproval` literal through to the client\n * tool so the tool-call part's `approval` field stays gated on it.\n */\n client: <TContext = unknown>(\n execute?: ToolExecuteFunction<TInput, TOutput, TContext>,\n ) => ClientTool<\n TInput,\n TOutput,\n TName,\n TContext,\n TNeedsApproval,\n TApprovalSchema\n > &\n BuiltToolSchemaFields<TInput, TOutput, TApprovalSchema>\n}\n\n/**\n * Create an isomorphic tool definition that can be used directly or instantiated for server/client\n *\n * The definition contains all tool metadata (name, description, schemas) and can be:\n * 1. Used directly in chat() on the server (as a tool definition without execute)\n * 2. Instantiated as a server tool with .server()\n * 3. Instantiated as a client tool with .client()\n *\n * Supports any Standard JSON Schema compliant library (Zod v4+, ArkType, Valibot, etc.)\n * or plain JSON Schema objects.\n *\n * @example\n * ```typescript\n * import { toolDefinition } from '@tanstack/ai';\n * import { z } from 'zod';\n *\n * // Using Zod (natively supports Standard JSON Schema)\n * const addToCartTool = toolDefinition({\n * name: 'addToCart',\n * description: 'Add a guitar to the shopping cart (requires approval)',\n * needsApproval: true,\n * inputSchema: z.object({\n * guitarId: z.string(),\n * quantity: z.number(),\n * }),\n * outputSchema: z.object({\n * success: z.boolean(),\n * cartId: z.string(),\n * totalItems: z.number(),\n * }),\n * });\n *\n * // Use directly in chat (server-side, no execute function)\n * chat({\n * tools: [addToCartTool],\n * // ...\n * });\n *\n * // Or create server-side implementation\n * const addToCartServer = addToCartTool.server(async (args) => {\n * // args is typed as { guitarId: string; quantity: number }\n * return {\n * success: true,\n * cartId: 'CART_' + Date.now(),\n * totalItems: args.quantity,\n * };\n * });\n *\n * // Or create client-side implementation\n * const addToCartClient = addToCartTool.client(async (args) => {\n * // Client-specific logic (e.g., localStorage)\n * return { success: true, cartId: 'local', totalItems: 1 };\n * });\n * ```\n */\nexport function toolDefinition<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n // `const` forces the literal (`true` / `false`) to be captured from the\n // config's optional `needsApproval` — without it TS widens to `boolean`,\n // which collapses the approval gate in `ToolCallPartForTool`.\n const TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n>(\n config: ToolDefinitionConfig<\n TInput,\n TOutput,\n TName,\n TNeedsApproval,\n TApprovalSchema\n >,\n): ToolDefinition<TInput, TOutput, TName, TNeedsApproval, TApprovalSchema> {\n if (config.approvalSchema !== undefined && config.needsApproval !== true) {\n throw new TypeError('approvalSchema requires needsApproval: true.')\n }\n const inputSchema = config.inputSchema as TInput\n const outputSchema = config.outputSchema as TOutput\n const approvalSchema = config.approvalSchema as TApprovalSchema\n const needsApproval = config.needsApproval as TNeedsApproval | undefined\n\n const definition: ToolDefinition<\n TInput,\n TOutput,\n TName,\n TNeedsApproval,\n TApprovalSchema\n > = {\n __toolSide: 'definition',\n ...config,\n inputSchema,\n outputSchema,\n approvalSchema,\n needsApproval,\n server<TContext = unknown>(\n execute: ToolExecuteFunction<TInput, TOutput, TContext>,\n ): ServerTool<\n TInput,\n TOutput,\n TName,\n TContext,\n TNeedsApproval,\n TApprovalSchema\n > &\n BuiltToolSchemaFields<TInput, TOutput, TApprovalSchema> {\n return {\n __toolSide: 'server',\n ...config,\n inputSchema,\n outputSchema,\n approvalSchema,\n needsApproval,\n execute,\n }\n },\n\n client<TContext = unknown>(\n execute?: ToolExecuteFunction<TInput, TOutput, TContext>,\n ): ClientTool<\n TInput,\n TOutput,\n TName,\n TContext,\n TNeedsApproval,\n TApprovalSchema\n > &\n BuiltToolSchemaFields<TInput, TOutput, TApprovalSchema> {\n return {\n __toolSide: 'client',\n ...config,\n inputSchema,\n outputSchema,\n approvalSchema,\n needsApproval,\n ...(execute !== undefined && { execute }),\n }\n },\n }\n\n return definition\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0UA,SAAgB,eAUd,QAOyE;CACzE,IAAI,OAAO,mBAAmB,KAAA,KAAa,OAAO,kBAAkB,MAClE,MAAM,IAAI,UAAU,8CAA8C;CAEpE,MAAM,cAAc,OAAO;CAC3B,MAAM,eAAe,OAAO;CAC5B,MAAM,iBAAiB,OAAO;CAC9B,MAAM,gBAAgB,OAAO;CA4D7B,OAAO;EAnDL,YAAY;EACZ,GAAG;EACH;EACA;EACA;EACA;EACA,OACE,SASwD;GACxD,OAAO;IACL,YAAY;IACZ,GAAG;IACH;IACA;IACA;IACA;IACA;GACF;EACF;EAEA,OACE,SASwD;GACxD,OAAO;IACL,YAAY;IACZ,GAAG;IACH;IACA;IACA;IACA;IACA,GAAI,YAAY,KAAA,KAAa,EAAE,QAAQ;GACzC;EACF;CAGK;AACT"}
1
+ {"version":3,"file":"tool-definition.js","names":[],"sources":["../../../../../src/activities/chat/tools/tool-definition.ts"],"sourcesContent":["import type {\n InferSchemaType,\n JSONSchema,\n SchemaInput,\n Tool,\n ToolExecuteFunction,\n} from '../../../types'\nimport type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from '@standard-schema/spec'\n\n/**\n * Type-level brand key for {@link ToolApprovalCapabilityMarker}. Only ever used\n * in type positions, but it must stay exported: a `unique symbol` referenced by\n * an exported interface has to be nameable in the emitted declarations.\n *\n * @public\n */\nexport declare const toolApprovalCapability: unique symbol\n\nexport interface ToolApprovalCapabilityMarker<\n TNeedsApproval extends boolean,\n TApprovalSchema,\n> {\n readonly [toolApprovalCapability]?: {\n needsApproval: TNeedsApproval\n approvalSchema: TApprovalSchema\n }\n}\n\nexport type ApprovalSchemaConfig =\n | SchemaInput\n | { approve: SchemaInput; reject?: SchemaInput }\n | { approve?: SchemaInput; reject: SchemaInput }\n\ntype ApprovalConfig<\n TNeedsApproval extends boolean,\n TApprovalSchema extends ApprovalSchemaConfig | undefined,\n> = TNeedsApproval extends true\n ? { needsApproval: TNeedsApproval; approvalSchema?: TApprovalSchema }\n : { needsApproval?: TNeedsApproval; approvalSchema?: never }\n\nexport type ApprovalCapabilityOf<TTool> =\n TTool extends ToolApprovalCapabilityMarker<infer TNeeds, unknown>\n ? TNeeds\n : false\n\nexport type ApprovalSchemaOf<TTool> =\n TTool extends ToolApprovalCapabilityMarker<boolean, infer TSchema>\n ? TSchema\n : undefined\n\nexport declare const noSchema: unique symbol\nexport type NoSchema = typeof noSchema\n\nexport type InputSchemaOf<TTool> = TTool extends {\n inputSchema: infer TInput\n}\n ? TInput extends undefined\n ? NoSchema\n : TInput\n : NoSchema\n\nexport type OutputSchemaOf<TTool> = TTool extends {\n outputSchema: infer TOutput\n}\n ? TOutput extends undefined\n ? NoSchema\n : TOutput\n : NoSchema\n\ntype BuiltToolSchemaFields<\n TInput extends SchemaInput | undefined,\n TOutput extends SchemaInput | undefined,\n TApprovalSchema extends ApprovalSchemaConfig | undefined,\n> = {\n inputSchema: TInput\n outputSchema: TOutput\n approvalSchema: TApprovalSchema\n}\n\n/**\n * Marker type for server-side tools\n */\nexport interface ServerTool<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n TContext = unknown,\n TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n>\n extends\n Tool<TInput, TOutput, TName, TContext>,\n ToolApprovalCapabilityMarker<TNeedsApproval, TApprovalSchema> {\n __toolSide: 'server'\n inputSchema?: TInput\n outputSchema?: TOutput\n needsApproval?: TNeedsApproval\n approvalSchema?: TApprovalSchema\n execution?: 'task'\n}\n\n/**\n * Marker type for client-side tools\n */\nexport interface ClientTool<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n TContext = unknown,\n // Captured as a literal (`true` / `false`) so downstream types — notably\n // the tool-call part's `approval` field — can be gated on it. Defaults to\n // `false` when the tool config omits `needsApproval`.\n TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n> extends ToolApprovalCapabilityMarker<TNeedsApproval, TApprovalSchema> {\n __toolSide: 'client'\n name: TName\n description: string\n // Note: `inputSchema` / `outputSchema` stay as bare optionals (not\n // widened to `| undefined`). They participate in inference via\n // `InferToolInput` / `InferToolOutput` — widening with `| undefined`\n // breaks the `infer TInput extends StandardJSONSchemaV1<...>` chain\n // because `undefined` doesn't extend the schema constraint.\n inputSchema?: TInput\n outputSchema?: TOutput\n needsApproval?: TNeedsApproval\n approvalSchema?: TApprovalSchema\n execution?: 'task'\n lazy?: boolean\n metadata?: Record<string, unknown>\n execute?: ToolExecuteFunction<TInput, TOutput, TContext>\n}\n\n/** Broad server-tool shape for heterogeneous internal collections. */\nexport type AnyServerTool = Omit<\n ServerTool<any, any, string, any, boolean, any>,\n 'execute'\n> & {\n execute?: ((args: any, context?: any) => any) | undefined\n}\n\n/**\n * Tool definition that can be used directly or instantiated for server/client\n */\nexport interface ToolDefinitionInstance<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n TContext = unknown,\n TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n> extends Tool<TInput, TOutput, TName, TContext> {\n __toolSide: 'definition'\n // Narrow the base `needsApproval?: boolean` to the captured literal so it\n // survives into `ToolCallPartForTool`'s approval gate.\n inputSchema: TInput\n outputSchema: TOutput\n needsApproval?: TNeedsApproval\n approvalSchema: TApprovalSchema\n execution?: 'task'\n readonly [toolApprovalCapability]?: {\n needsApproval: TNeedsApproval\n approvalSchema: TApprovalSchema\n }\n}\n\n/**\n * Union type for any kind of client-side tool (client tool or definition)\n */\nexport type AnyClientTool =\n | (Omit<ClientTool<any, any, string, any, boolean, any>, 'execute'> & {\n execute?: ((args: any, context?: any) => any) | undefined\n })\n | (Omit<\n ToolDefinitionInstance<any, any, string, any, boolean, any>,\n 'execute'\n > & {\n execute?: ((args: any, context?: any) => any) | undefined\n })\n\n/**\n * Extract the tool name as a literal type\n */\nexport type InferToolName<T> = T extends { name: infer N } ? N : never\n\n/**\n * Extract the input type from a tool (inferred from Standard JSON Schema, or `unknown` for plain JSONSchema)\n */\nexport type InferToolInput<T> = T extends { inputSchema?: infer TInput }\n ? TInput extends JSONSchema\n ? unknown\n : InferSchemaType<TInput>\n : unknown\n\n/**\n * Extract the output type from a tool (inferred from Standard JSON Schema, or `unknown` for plain JSONSchema)\n */\nexport type InferToolOutput<T> = T extends { outputSchema?: infer TOutput }\n ? TOutput extends StandardJSONSchemaV1<any, any>\n ? InferSchemaType<TOutput>\n : TOutput extends StandardSchemaV1<any, any>\n ? InferSchemaType<TOutput>\n : TOutput extends JSONSchema\n ? unknown\n : InferSchemaType<TOutput>\n : unknown\n\n/**\n * Tool definition configuration\n */\nexport type ToolDefinitionConfig<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n> = {\n name: TName\n description: string\n inputSchema?: TInput\n outputSchema?: TOutput\n lazy?: boolean\n metadata?: Record<string, unknown>\n execution?: 'task'\n} & ApprovalConfig<TNeedsApproval, TApprovalSchema>\n\n/**\n * Tool definition builder that allows creating server or client tools from a shared definition\n */\nexport interface ToolDefinition<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n> extends ToolDefinitionInstance<\n TInput,\n TOutput,\n TName,\n unknown,\n TNeedsApproval,\n TApprovalSchema\n> {\n /**\n * Create a server-side tool with execute function\n */\n server: <TContext = unknown>(\n execute: ToolExecuteFunction<TInput, TOutput, TContext>,\n ) => ServerTool<\n TInput,\n TOutput,\n TName,\n TContext,\n TNeedsApproval,\n TApprovalSchema\n > &\n BuiltToolSchemaFields<TInput, TOutput, TApprovalSchema>\n\n /**\n * Create a client-side tool with optional execute function.\n * Carries the definition's `needsApproval` literal through to the client\n * tool so the tool-call part's `approval` field stays gated on it.\n */\n client: <TContext = unknown>(\n execute?: ToolExecuteFunction<TInput, TOutput, TContext>,\n ) => ClientTool<\n TInput,\n TOutput,\n TName,\n TContext,\n TNeedsApproval,\n TApprovalSchema\n > &\n BuiltToolSchemaFields<TInput, TOutput, TApprovalSchema>\n}\n\n/**\n * Create an isomorphic tool definition that can be used directly or instantiated for server/client\n *\n * The definition contains all tool metadata (name, description, schemas) and can be:\n * 1. Used directly in chat() on the server (as a tool definition without execute)\n * 2. Instantiated as a server tool with .server()\n * 3. Instantiated as a client tool with .client()\n *\n * Supports any Standard JSON Schema compliant library (Zod v4+, ArkType, Valibot, etc.)\n * or plain JSON Schema objects.\n *\n * @example\n * ```typescript\n * import { toolDefinition } from '@tanstack/ai';\n * import { z } from 'zod';\n *\n * // Using Zod (natively supports Standard JSON Schema)\n * const addToCartTool = toolDefinition({\n * name: 'addToCart',\n * description: 'Add a guitar to the shopping cart (requires approval)',\n * needsApproval: true,\n * inputSchema: z.object({\n * guitarId: z.string(),\n * quantity: z.number(),\n * }),\n * outputSchema: z.object({\n * success: z.boolean(),\n * cartId: z.string(),\n * totalItems: z.number(),\n * }),\n * });\n *\n * // Use directly in chat (server-side, no execute function)\n * chat({\n * tools: [addToCartTool],\n * // ...\n * });\n *\n * // Or create server-side implementation\n * const addToCartServer = addToCartTool.server(async (args) => {\n * // args is typed as { guitarId: string; quantity: number }\n * return {\n * success: true,\n * cartId: 'CART_' + Date.now(),\n * totalItems: args.quantity,\n * };\n * });\n *\n * // Or create client-side implementation\n * const addToCartClient = addToCartTool.client(async (args) => {\n * // Client-specific logic (e.g., localStorage)\n * return { success: true, cartId: 'local', totalItems: 1 };\n * });\n * ```\n */\nexport function toolDefinition<\n TInput extends SchemaInput | undefined = undefined,\n TOutput extends SchemaInput | undefined = undefined,\n TName extends string = string,\n // `const` forces the literal (`true` / `false`) to be captured from the\n // config's optional `needsApproval` — without it TS widens to `boolean`,\n // which collapses the approval gate in `ToolCallPartForTool`.\n const TNeedsApproval extends boolean = false,\n TApprovalSchema extends ApprovalSchemaConfig | undefined = undefined,\n>(\n config: ToolDefinitionConfig<\n TInput,\n TOutput,\n TName,\n TNeedsApproval,\n TApprovalSchema\n >,\n): ToolDefinition<TInput, TOutput, TName, TNeedsApproval, TApprovalSchema> {\n if (config.approvalSchema !== undefined && config.needsApproval !== true) {\n throw new TypeError('approvalSchema requires needsApproval: true.')\n }\n const inputSchema = config.inputSchema as TInput\n const outputSchema = config.outputSchema as TOutput\n const approvalSchema = config.approvalSchema as TApprovalSchema\n const needsApproval = config.needsApproval as TNeedsApproval | undefined\n const execution = config.execution\n\n const definition: ToolDefinition<\n TInput,\n TOutput,\n TName,\n TNeedsApproval,\n TApprovalSchema\n > = {\n __toolSide: 'definition',\n ...config,\n inputSchema,\n outputSchema,\n approvalSchema,\n needsApproval,\n execution,\n server<TContext = unknown>(\n execute: ToolExecuteFunction<TInput, TOutput, TContext>,\n ): ServerTool<\n TInput,\n TOutput,\n TName,\n TContext,\n TNeedsApproval,\n TApprovalSchema\n > &\n BuiltToolSchemaFields<TInput, TOutput, TApprovalSchema> {\n return {\n __toolSide: 'server',\n ...config,\n inputSchema,\n outputSchema,\n approvalSchema,\n needsApproval,\n execution,\n execute,\n }\n },\n\n client<TContext = unknown>(\n execute?: ToolExecuteFunction<TInput, TOutput, TContext>,\n ): ClientTool<\n TInput,\n TOutput,\n TName,\n TContext,\n TNeedsApproval,\n TApprovalSchema\n > &\n BuiltToolSchemaFields<TInput, TOutput, TApprovalSchema> {\n return {\n __toolSide: 'client',\n ...config,\n inputSchema,\n outputSchema,\n approvalSchema,\n needsApproval,\n execution,\n ...(execute !== undefined && { execute }),\n }\n },\n }\n\n return definition\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8UA,SAAgB,eAUd,QAOyE;CACzE,IAAI,OAAO,mBAAmB,KAAA,KAAa,OAAO,kBAAkB,MAClE,MAAM,IAAI,UAAU,8CAA8C;CAEpE,MAAM,cAAc,OAAO;CAC3B,MAAM,eAAe,OAAO;CAC5B,MAAM,iBAAiB,OAAO;CAC9B,MAAM,gBAAgB,OAAO;CAC7B,MAAM,YAAY,OAAO;CA+DzB,OAAO;EAtDL,YAAY;EACZ,GAAG;EACH;EACA;EACA;EACA;EACA;EACA,OACE,SASwD;GACxD,OAAO;IACL,YAAY;IACZ,GAAG;IACH;IACA;IACA;IACA;IACA;IACA;GACF;EACF;EAEA,OACE,SASwD;GACxD,OAAO;IACL,YAAY;IACZ,GAAG;IACH;IACA;IACA;IACA;IACA;IACA,GAAI,YAAY,KAAA,KAAa,EAAE,QAAQ;GACzC;EACF;CAGK;AACT"}
@@ -105,6 +105,10 @@ export interface EvaluateAdapterResult {
105
105
  model: string;
106
106
  answers: Record<string, WireAnswer>;
107
107
  usage: TokenUsage;
108
+ /** Provider response id, for example to look the request up later. */
109
+ id?: string;
110
+ /** Upstream provider that served the request, when a router reports it. */
111
+ provider?: string;
108
112
  }
109
113
  /**
110
114
  * Evaluate adapter interface with pre-resolved generics.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","names":[],"sources":["../../../../src/activities/evaluate/adapter.ts"],"sourcesContent":["import type { InternalLogger } from '../../logger/internal-logger'\nimport type { TokenUsage } from '../../types'\n\n/**\n * Configuration for evaluate adapter instances.\n */\nexport interface EvaluateAdapterConfig {\n apiKey?: string\n baseUrl?: string\n timeout?: number\n headers?: Record<string, string>\n}\n\n/**\n * Shared JSON value for `state` and question `instructions`.\n * A JSON array is one value, not a batch.\n */\nexport type EvaluateJsonValue = string | object | Array<unknown>\n\n/** Content the model judges. A JSON array is one state, not a batch. */\nexport type EvaluateState = EvaluateJsonValue\n\n/** Question text. Matches TypeSafe: string, object, or array. */\nexport type EvaluateInstructions = EvaluateJsonValue\n\n/**\n * TypeSafe choice question on the adapter wire.\n *\n * Generic parameters:\n * - TOptions: option key to description (or `null` when the key is enough)\n */\nexport interface WireChoiceQuestion<\n TOptions extends Record<string, string | null> = Record<\n string,\n string | null\n >,\n> {\n type: 'choice'\n instructions: EvaluateInstructions\n criteria: TOptions\n}\n\n/**\n * TypeSafe score question on the adapter wire.\n *\n * Generic parameters:\n * - TLevels: ordered level labels, at least two\n */\nexport interface WireScoreQuestion<\n TLevels extends ReadonlyArray<string> = ReadonlyArray<string>,\n> {\n type: 'score'\n instructions: EvaluateInstructions\n criteria: TLevels\n}\n\n/**\n * TypeSafe yes/no question on the adapter wire.\n * Public helpers call this `boolean`. The wire type is `noul`.\n */\nexport interface WireNoulQuestion {\n type: 'noul'\n instructions: EvaluateInstructions\n criteria?: {\n true?: string\n false?: string\n }\n}\n\n/** Question payload adapters send to the provider. */\nexport type WireQuestion =\n | WireChoiceQuestion\n | WireScoreQuestion\n | WireNoulQuestion\n\n/** TypeSafe choice answer. Adapters do not invent a public `.value`. */\nexport interface WireChoiceAnswer {\n type: 'choice'\n choice: string\n probabilities: Record<string, number>\n confidence: number\n}\n\n/** TypeSafe score answer. `score` is the raw fraction. */\nexport interface WireScoreAnswer {\n type: 'score'\n score: number\n legend: Record<string, string>\n probabilities: Record<string, number>\n confidence: number\n}\n\n/** TypeSafe yes/no answer. `noul` is P(true). */\nexport interface WireNoulAnswer {\n type: 'noul'\n noul: number\n}\n\n/** Provider payload for one question. The activity maps this to a unified answer. */\nexport type WireAnswer = WireChoiceAnswer | WireScoreAnswer | WireNoulAnswer\n\n/**\n * Options passed to {@link EvaluateAdapter.evaluate}.\n */\nexport interface EvaluateOptions<\n TProviderOptions extends object = Record<string, unknown>,\n> {\n model: string\n /** Shared state every question judges. A JSON array is one state, not a batch. */\n state: EvaluateState\n /** TypeSafe wire questions, keyed by the caller's question ids. */\n questions: Record<string, WireQuestion>\n /** Provider-specific options forwarded by `decide()`. */\n modelOptions?: TProviderOptions\n /** Forwarded to the provider request for cancellation. */\n abortSignal?: AbortSignal\n /**\n * Internal logger threaded from `decide()`. Adapters must call\n * `logger.request()` before the provider call and `logger.errors()` in catch\n * blocks.\n */\n logger: InternalLogger\n}\n\n/**\n * Provider-level evaluate result. Adapters return the wire payload plus usage.\n * The activity maps answers to the unified public shape.\n */\nexport interface EvaluateAdapterResult {\n /** Resolved model id from the provider. */\n model: string\n answers: Record<string, WireAnswer>\n usage: TokenUsage\n}\n\n/**\n * Evaluate adapter interface with pre-resolved generics.\n *\n * An adapter is created by a provider function: `provider('model')` → `adapter`.\n * All type resolution happens at the provider call site, not in this interface.\n *\n * Generic parameters:\n * - TModel: The specific model name (e.g. `'jev-latest'`)\n * - TProviderOptions: Provider-specific options (already resolved)\n */\nexport interface EvaluateAdapter<\n TModel extends string = string,\n TProviderOptions extends object = Record<string, unknown>,\n> {\n /** Discriminator for adapter kind */\n readonly kind: 'evaluate'\n /** Adapter name identifier */\n readonly name: string\n /** The model this adapter is configured for */\n readonly model: TModel\n\n /**\n * @internal Type-only properties for inference. Not assigned at runtime.\n */\n '~types': {\n providerOptions: TProviderOptions\n }\n\n /**\n * Evaluate typed questions against `state`. Return the provider payload.\n * Do not invent unified `.value` fields. The activity maps wire answers.\n */\n evaluate: (\n options: EvaluateOptions<TProviderOptions>,\n ) => Promise<EvaluateAdapterResult>\n}\n\n/**\n * An EvaluateAdapter with any/unknown type parameters.\n * Useful as a constraint in generic functions and interfaces.\n */\nexport type AnyEvaluateAdapter = EvaluateAdapter<any, any>\n\n/**\n * Abstract base class for evaluate adapters.\n * Extend this class to implement an evaluate adapter for a specific provider.\n *\n * Generic parameters match EvaluateAdapter. The provider function resolves them.\n */\nexport abstract class BaseEvaluateAdapter<\n TModel extends string = string,\n TProviderOptions extends object = Record<string, unknown>,\n> implements EvaluateAdapter<TModel, TProviderOptions> {\n readonly kind = 'evaluate' as const\n abstract readonly name: string\n readonly model: TModel\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: TProviderOptions\n }\n\n protected config: EvaluateAdapterConfig\n\n constructor(config: EvaluateAdapterConfig = {}, model: TModel) {\n this.config = config\n this.model = model\n }\n\n abstract evaluate(\n options: EvaluateOptions<TProviderOptions>,\n ): Promise<EvaluateAdapterResult>\n\n protected generateId(): string {\n return `${this.name}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n }\n}\n"],"mappings":";;;;;;;AAwLA,IAAsB,sBAAtB,MAGuD;CACrD,OAAgB;CAEhB;CAOA;CAEA,YAAY,SAAgC,CAAC,GAAG,OAAe;EAC7D,KAAK,SAAS;EACd,KAAK,QAAQ;CACf;CAMA,aAA+B;EAC7B,OAAO,GAAG,KAAK,KAAK,GAAG,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;CAC5E;AACF"}
1
+ {"version":3,"file":"adapter.js","names":[],"sources":["../../../../src/activities/evaluate/adapter.ts"],"sourcesContent":["import type { InternalLogger } from '../../logger/internal-logger'\nimport type { TokenUsage } from '../../types'\n\n/**\n * Configuration for evaluate adapter instances.\n */\nexport interface EvaluateAdapterConfig {\n apiKey?: string\n baseUrl?: string\n timeout?: number\n headers?: Record<string, string>\n}\n\n/**\n * Shared JSON value for `state` and question `instructions`.\n * A JSON array is one value, not a batch.\n */\nexport type EvaluateJsonValue = string | object | Array<unknown>\n\n/** Content the model judges. A JSON array is one state, not a batch. */\nexport type EvaluateState = EvaluateJsonValue\n\n/** Question text. Matches TypeSafe: string, object, or array. */\nexport type EvaluateInstructions = EvaluateJsonValue\n\n/**\n * TypeSafe choice question on the adapter wire.\n *\n * Generic parameters:\n * - TOptions: option key to description (or `null` when the key is enough)\n */\nexport interface WireChoiceQuestion<\n TOptions extends Record<string, string | null> = Record<\n string,\n string | null\n >,\n> {\n type: 'choice'\n instructions: EvaluateInstructions\n criteria: TOptions\n}\n\n/**\n * TypeSafe score question on the adapter wire.\n *\n * Generic parameters:\n * - TLevels: ordered level labels, at least two\n */\nexport interface WireScoreQuestion<\n TLevels extends ReadonlyArray<string> = ReadonlyArray<string>,\n> {\n type: 'score'\n instructions: EvaluateInstructions\n criteria: TLevels\n}\n\n/**\n * TypeSafe yes/no question on the adapter wire.\n * Public helpers call this `boolean`. The wire type is `noul`.\n */\nexport interface WireNoulQuestion {\n type: 'noul'\n instructions: EvaluateInstructions\n criteria?: {\n true?: string\n false?: string\n }\n}\n\n/** Question payload adapters send to the provider. */\nexport type WireQuestion =\n | WireChoiceQuestion\n | WireScoreQuestion\n | WireNoulQuestion\n\n/** TypeSafe choice answer. Adapters do not invent a public `.value`. */\nexport interface WireChoiceAnswer {\n type: 'choice'\n choice: string\n probabilities: Record<string, number>\n confidence: number\n}\n\n/** TypeSafe score answer. `score` is the raw fraction. */\nexport interface WireScoreAnswer {\n type: 'score'\n score: number\n legend: Record<string, string>\n probabilities: Record<string, number>\n confidence: number\n}\n\n/** TypeSafe yes/no answer. `noul` is P(true). */\nexport interface WireNoulAnswer {\n type: 'noul'\n noul: number\n}\n\n/** Provider payload for one question. The activity maps this to a unified answer. */\nexport type WireAnswer = WireChoiceAnswer | WireScoreAnswer | WireNoulAnswer\n\n/**\n * Options passed to {@link EvaluateAdapter.evaluate}.\n */\nexport interface EvaluateOptions<\n TProviderOptions extends object = Record<string, unknown>,\n> {\n model: string\n /** Shared state every question judges. A JSON array is one state, not a batch. */\n state: EvaluateState\n /** TypeSafe wire questions, keyed by the caller's question ids. */\n questions: Record<string, WireQuestion>\n /** Provider-specific options forwarded by `decide()`. */\n modelOptions?: TProviderOptions\n /** Forwarded to the provider request for cancellation. */\n abortSignal?: AbortSignal\n /**\n * Internal logger threaded from `decide()`. Adapters must call\n * `logger.request()` before the provider call and `logger.errors()` in catch\n * blocks.\n */\n logger: InternalLogger\n}\n\n/**\n * Provider-level evaluate result. Adapters return the wire payload plus usage.\n * The activity maps answers to the unified public shape.\n */\nexport interface EvaluateAdapterResult {\n /** Resolved model id from the provider. */\n model: string\n answers: Record<string, WireAnswer>\n usage: TokenUsage\n /** Provider response id, for example to look the request up later. */\n id?: string\n /** Upstream provider that served the request, when a router reports it. */\n provider?: string\n}\n\n/**\n * Evaluate adapter interface with pre-resolved generics.\n *\n * An adapter is created by a provider function: `provider('model')` → `adapter`.\n * All type resolution happens at the provider call site, not in this interface.\n *\n * Generic parameters:\n * - TModel: The specific model name (e.g. `'jev-latest'`)\n * - TProviderOptions: Provider-specific options (already resolved)\n */\nexport interface EvaluateAdapter<\n TModel extends string = string,\n TProviderOptions extends object = Record<string, unknown>,\n> {\n /** Discriminator for adapter kind */\n readonly kind: 'evaluate'\n /** Adapter name identifier */\n readonly name: string\n /** The model this adapter is configured for */\n readonly model: TModel\n\n /**\n * @internal Type-only properties for inference. Not assigned at runtime.\n */\n '~types': {\n providerOptions: TProviderOptions\n }\n\n /**\n * Evaluate typed questions against `state`. Return the provider payload.\n * Do not invent unified `.value` fields. The activity maps wire answers.\n */\n evaluate: (\n options: EvaluateOptions<TProviderOptions>,\n ) => Promise<EvaluateAdapterResult>\n}\n\n/**\n * An EvaluateAdapter with any/unknown type parameters.\n * Useful as a constraint in generic functions and interfaces.\n */\nexport type AnyEvaluateAdapter = EvaluateAdapter<any, any>\n\n/**\n * Abstract base class for evaluate adapters.\n * Extend this class to implement an evaluate adapter for a specific provider.\n *\n * Generic parameters match EvaluateAdapter. The provider function resolves them.\n */\nexport abstract class BaseEvaluateAdapter<\n TModel extends string = string,\n TProviderOptions extends object = Record<string, unknown>,\n> implements EvaluateAdapter<TModel, TProviderOptions> {\n readonly kind = 'evaluate' as const\n abstract readonly name: string\n readonly model: TModel\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: TProviderOptions\n }\n\n protected config: EvaluateAdapterConfig\n\n constructor(config: EvaluateAdapterConfig = {}, model: TModel) {\n this.config = config\n this.model = model\n }\n\n abstract evaluate(\n options: EvaluateOptions<TProviderOptions>,\n ): Promise<EvaluateAdapterResult>\n\n protected generateId(): string {\n return `${this.name}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n }\n}\n"],"mappings":";;;;;;;AA4LA,IAAsB,sBAAtB,MAGuD;CACrD,OAAgB;CAEhB;CAOA;CAEA,YAAY,SAAgC,CAAC,GAAG,OAAe;EAC7D,KAAK,SAAS;EACd,KAAK,QAAQ;CACf;CAMA,aAA+B;EAC7B,OAAO,GAAG,KAAK,KAAK,GAAG,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;CAC5E;AACF"}
@@ -51,6 +51,10 @@ export interface EvaluateResultMeta {
51
51
  /** Resolved model id from the provider. */
52
52
  model: string;
53
53
  usage: TokenUsage;
54
+ /** Provider response id, when the adapter returns one. */
55
+ id?: string;
56
+ /** Upstream provider that served the request, when the adapter returns one. */
57
+ provider?: string;
54
58
  }
55
59
  /**
56
60
  * Map a helper question (or wire question) to its public answer type.
@@ -292,7 +292,9 @@ async function decide(options) {
292
292
  });
293
293
  return withMeta(answers, {
294
294
  model: result.model,
295
- usage: result.usage
295
+ usage: result.usage,
296
+ ...result.id !== void 0 && { id: result.id },
297
+ ...result.provider !== void 0 && { provider: result.provider }
296
298
  });
297
299
  } catch (error) {
298
300
  const duration = Date.now() - startTime;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/activities/evaluate/index.ts"],"sourcesContent":["/**\n * Evaluate Activity\n *\n * Asks typed questions about a shared state and returns values your code can\n * branch on. This is a self-contained module with implementation, types, and\n * JSDoc.\n */\n\nimport { aiEventClient } from '@tanstack/ai-event-client'\nimport { resolveDebugOption } from '../../logger/resolve'\nimport { isAbortShapedError } from '../error-payload'\nimport {\n createGenerationContext,\n runGenerationAbort,\n runGenerationError,\n runGenerationFinish,\n runGenerationStart,\n runGenerationUsage,\n} from '../middleware/run'\nimport type { InternalLogger } from '../../logger/internal-logger'\nimport type { DebugOption } from '../../logger/types'\nimport type { TokenUsage } from '../../types'\nimport type { GenerationMiddleware } from '../middleware/types'\nimport type {\n EvaluateAdapter,\n EvaluateInstructions,\n EvaluateState,\n WireAnswer,\n WireChoiceAnswer,\n WireNoulAnswer,\n WireQuestion,\n WireScoreAnswer,\n WireScoreQuestion,\n} from './adapter'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'evaluate' as const\n\n/** Question key reserved for `result.meta`. */\nconst RESERVED_QUESTION_KEY = 'meta' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/** Extract provider options from an EvaluateAdapter via ~types */\nexport type EvaluateProviderOptions<TAdapter> = TAdapter extends {\n '~types': { providerOptions: infer P extends object }\n}\n ? P\n : object\n\n// ===========================\n// Unified answers\n// ===========================\n\n/**\n * Public choice answer. `.value` is the selected option key.\n */\nexport interface ChoiceAnswer<TValue extends string = string> {\n type: 'choice'\n value: TValue\n /** P(selected option). */\n probability: number\n confidence: number\n probabilities: Record<TValue, number>\n}\n\n/**\n * Public score answer. `.value` is the nearest level label.\n * `.score` is the raw TypeSafe fraction.\n */\nexport interface ScoreAnswer<TLevel extends string = string> {\n type: 'score'\n value: TLevel\n /** P(nearest level). */\n probability: number\n confidence: number\n score: number\n legend: Record<string, string>\n probabilities: Record<string, number>\n}\n\n/**\n * Public yes/no answer. `.value` is `true` when P(true) is 0.5 or more.\n * There is no `.confidence`.\n */\nexport interface BooleanAnswer {\n type: 'boolean'\n value: boolean\n /** P(true), from the wire `noul` field. */\n probability: number\n}\n\nexport interface EvaluateResultMeta {\n /** Resolved model id from the provider. */\n model: string\n usage: TokenUsage\n}\n\n/**\n * Map a helper question (or wire question) to its public answer type.\n */\nexport type InferEvaluateAnswer<TQuestion> = TQuestion extends {\n type: 'choice'\n criteria: infer TCriteria\n}\n ? TCriteria extends Record<string, string | null>\n ? ChoiceAnswer<Extract<keyof TCriteria, string>>\n : ChoiceAnswer\n : TQuestion extends { type: 'score'; criteria: infer TLevels }\n ? TLevels extends ReadonlyArray<string>\n ? ScoreAnswer<TLevels[number] & string>\n : ScoreAnswer\n : TQuestion extends { type: 'noul' }\n ? BooleanAnswer\n : never\n\n/**\n * Result of `decide()`. Each question key is a top-level answer.\n * `meta` holds the resolved model id and usage.\n */\nexport type EvaluateResult<TQuestions extends Record<string, WireQuestion>> = {\n [K in keyof TQuestions as K extends typeof RESERVED_QUESTION_KEY\n ? never\n : K]: InferEvaluateAnswer<TQuestions[K]>\n} & {\n meta: EvaluateResultMeta\n}\n\n// ===========================\n// Activity Options Types\n// ===========================\n\n/**\n * Options for the evaluate activity. The model is extracted from the\n * adapter's model property.\n *\n * @template TAdapter - The evaluate adapter type\n * @template TQuestions - The questions object passed to `decide`\n */\nexport interface EvaluateActivityOptions<\n TAdapter extends EvaluateAdapter<string, EvaluateProviderOptions<TAdapter>>,\n TQuestions extends Record<string, WireQuestion>,\n> {\n /** The evaluate adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** Shared state every question judges. A JSON array is one state, not a batch. */\n state: EvaluateState\n /**\n * Questions built with `choice`, `score`, and `boolean`.\n * The key `meta` is reserved.\n */\n questions: TQuestions\n /** Provider-specific options */\n modelOptions?: EvaluateProviderOptions<TAdapter>\n /** Forwarded to the provider request for cancellation. */\n abortSignal?: AbortSignal\n /**\n * Observe-only middleware notified on start, usage, success, abort, and\n * error. Pass `otelMiddleware()` to emit OpenTelemetry spans, or implement\n * the `GenerationMiddleware` contract for a custom backend.\n */\n middleware?: Array<GenerationMiddleware>\n /**\n * Enable debug logging. Pass `true` to enable all categories, `false` to\n * silence everything including errors, or a `DebugConfig` object for granular\n * control and/or a custom `Logger`.\n */\n debug?: DebugOption\n}\n\n// ===========================\n// Helper Functions\n// ===========================\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\nfunction isAbortError(error: unknown, signal?: AbortSignal): boolean {\n // Prefer the error's own identity over the signal state. A genuine\n // cancellation throws an abort-shaped error (DOM `AbortError`, the OpenRouter\n // SDK's `RequestAbortedError`, ...). Classifying on `signal.aborted` alone\n // would misroute a real failure to the abort hook whenever a shared signal\n // happens to already be aborted, hiding it from `onError` observers.\n if (isAbortShapedError(error)) return true\n // Fall back to signal state only for non-Error throws we can't otherwise\n // identify; a real Error with a non-abort name is never an abort.\n return error instanceof Error ? false : signal?.aborted === true\n}\n\nfunction questionKeys(questions: Record<string, WireQuestion>) {\n return Object.keys(questions)\n}\n\nfunction assertQuestions(questions: Record<string, WireQuestion>) {\n const keys = questionKeys(questions)\n if (keys.length === 0) {\n throw new Error('decide() requires at least one question')\n }\n if (Object.hasOwn(questions, RESERVED_QUESTION_KEY)) {\n throw new Error('decide() reserves the question key \"meta\"')\n }\n return keys\n}\n\nfunction mapChoiceAnswer(wire: WireChoiceAnswer, key: string) {\n const probability = wire.probabilities[wire.choice]\n if (typeof probability !== 'number') {\n throw new Error(\n `decide(): missing probability for choice \"${wire.choice}\" on \"${key}\"`,\n )\n }\n return {\n type: 'choice' as const,\n value: wire.choice,\n probability,\n confidence: wire.confidence,\n probabilities: wire.probabilities,\n }\n}\n\nfunction mapScoreAnswer(\n question: WireScoreQuestion,\n wire: WireScoreAnswer,\n key: string,\n) {\n const levels = question.criteria\n if (levels.length < 2) {\n throw new Error(\n `decide(): score question \"${key}\" needs at least two levels`,\n )\n }\n const lastIndex = levels.length - 1\n const rounded = Math.round(wire.score)\n const nearestIndex =\n rounded < 0 ? 0 : rounded > lastIndex ? lastIndex : rounded\n const value = levels[nearestIndex]\n if (value === undefined) {\n throw new Error(\n `decide(): score question \"${key}\" has no level at index ${nearestIndex}`,\n )\n }\n const probability = wire.probabilities[String(nearestIndex)]\n if (typeof probability !== 'number') {\n throw new Error(\n `decide(): missing probability for score level ${nearestIndex} on \"${key}\"`,\n )\n }\n return {\n type: 'score' as const,\n value,\n probability,\n confidence: wire.confidence,\n score: wire.score,\n legend: wire.legend,\n probabilities: wire.probabilities,\n }\n}\n\nfunction mapBooleanAnswer(wire: WireNoulAnswer) {\n return {\n type: 'boolean' as const,\n value: wire.noul >= 0.5,\n probability: wire.noul,\n }\n}\n\nfunction mapWireAnswer(question: WireQuestion, wire: WireAnswer, key: string) {\n switch (question.type) {\n case 'choice': {\n if (wire.type !== 'choice') {\n throw new Error(\n `decide(): expected choice answer for \"${key}\", got ${wire.type}`,\n )\n }\n return mapChoiceAnswer(wire, key)\n }\n case 'score': {\n if (wire.type !== 'score') {\n throw new Error(\n `decide(): expected score answer for \"${key}\", got ${wire.type}`,\n )\n }\n return mapScoreAnswer(question, wire, key)\n }\n case 'noul': {\n if (wire.type !== 'noul') {\n throw new Error(\n `decide(): expected noul answer for \"${key}\", got ${wire.type}`,\n )\n }\n return mapBooleanAnswer(wire)\n }\n }\n}\n\nfunction mapAnswers<TQuestions extends Record<string, WireQuestion>>(\n questions: TQuestions,\n wireAnswers: Record<string, WireAnswer>,\n) {\n const answers = {} as {\n [K in keyof TQuestions]: InferEvaluateAnswer<TQuestions[K]>\n }\n const keys = Object.keys(questions) as Array<keyof TQuestions>\n for (const key of keys) {\n const question = questions[key]\n const wire = wireAnswers[String(key)]\n if (question === undefined) {\n throw new Error(`decide(): missing question \"${String(key)}\"`)\n }\n if (wire === undefined) {\n throw new Error(`decide(): missing answer for question \"${String(key)}\"`)\n }\n answers[key] = mapWireAnswer(\n question,\n wire,\n String(key),\n ) as InferEvaluateAnswer<TQuestions[typeof key]>\n }\n return answers\n}\n\nfunction withMeta<TAnswers extends object>(\n answers: TAnswers,\n meta: EvaluateResultMeta,\n) {\n return { ...answers, meta }\n}\n\n// ===========================\n// Question helpers\n// ===========================\n\n/**\n * Build a choice question. The model picks one key from `options`.\n *\n * Option keys become the union on `.value`. Use `null` when a key needs no\n * extra description. On the wire, `options` is sent as TypeSafe `criteria`.\n *\n * @param options.instructions What the model should decide.\n * @param options.options Map of option key to description, or `null`.\n *\n * @example\n * ```ts\n * const queue = choice({\n * instructions: 'Which team should handle this ticket?',\n * options: {\n * billing: 'Payments, invoices, refunds',\n * tech: 'Bugs, outages, integrations',\n * sales: 'Pricing, upgrades, new accounts',\n * },\n * })\n * ```\n */\nexport function choice<\n const TOptions extends Record<string, string | null>,\n>(options: { instructions: EvaluateInstructions; options: TOptions }) {\n return {\n type: 'choice' as const,\n instructions: options.instructions,\n criteria: options.options,\n }\n}\n\n/**\n * Build a score question. The model rates `state` on ordered `levels`.\n *\n * You must pass at least two levels. `.value` is the nearest level label.\n * The raw fraction stays on `.score`. On the wire, `levels` is sent as\n * TypeSafe `criteria`.\n *\n * @param options.instructions What the model should rate.\n * @param options.levels Ordered labels, lowest first. At least two.\n *\n * @example\n * ```ts\n * const urgency = score({\n * instructions: 'How urgent is this ticket?',\n * levels: ['low', 'medium', 'high'],\n * })\n * ```\n */\nexport function score<const TLevels extends ReadonlyArray<string>>(options: {\n instructions: EvaluateInstructions\n levels: TLevels\n}) {\n if (options.levels.length < 2) {\n throw new Error('score() requires at least two levels')\n }\n return {\n type: 'score' as const,\n instructions: options.instructions,\n criteria: options.levels,\n }\n}\n\n/**\n * Build a yes/no question.\n *\n * `.value` is `true` when P(true) is 0.5 or more. There is no `.confidence`.\n * On the wire, the type is TypeSafe `noul`.\n *\n * @param options.instructions The yes/no question to judge.\n * @param options.criteria Optional descriptions of yes and no.\n *\n * @example\n * ```ts\n * const refund = boolean({\n * instructions: 'Is the customer asking for a refund?',\n * })\n * ```\n */\nexport function boolean(options: {\n instructions: EvaluateInstructions\n criteria?: {\n true?: string\n false?: string\n }\n}) {\n if (options.criteria === undefined) {\n return {\n type: 'noul' as const,\n instructions: options.instructions,\n }\n }\n return {\n type: 'noul' as const,\n instructions: options.instructions,\n criteria: options.criteria,\n }\n}\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Ask typed questions about `state` and get answers your code can branch on.\n *\n * You have state (a ticket, a record, a log) and you need typed answers, not\n * prose. Pass questions built with `choice`, `score`, and `boolean`. Then\n * branch on `result.queue.value` in ordinary TypeScript.\n *\n * The question key `meta` is reserved. Throws if `questions` is empty or uses\n * that key.\n *\n * @param options.adapter Evaluate adapter created with a model.\n * @param options.state Shared state every question judges.\n * @param options.questions Questions built with `choice`, `score`, `boolean`.\n * @param options.modelOptions Provider-specific options.\n * @param options.abortSignal Cancels the in-flight request.\n * @param options.middleware Observe-only generation middleware.\n * @param options.debug Debug logging option.\n *\n * @example Route a support ticket\n * ```ts\n * import { decide, choice, score, boolean } from '@tanstack/ai'\n * import { typesafeDecider } from '@tanstack/ai-typesafe'\n *\n * const result = await decide({\n * adapter: typesafeDecider('jev-latest'),\n * state: ticket,\n * questions: {\n * queue: choice({\n * instructions: 'Which team should handle this ticket?',\n * options: {\n * billing: 'Payments, invoices, refunds',\n * tech: 'Bugs, outages, integrations',\n * sales: 'Pricing, upgrades, new accounts',\n * },\n * }),\n * urgency: score({\n * instructions: 'How urgent is this ticket?',\n * levels: ['low', 'medium', 'high'],\n * }),\n * refund: boolean({\n * instructions: 'Is the customer asking for a refund?',\n * }),\n * },\n * })\n *\n * result.queue.value\n * result.meta.model\n * result.meta.usage\n * ```\n */\nexport async function decide<\n TAdapter extends EvaluateAdapter<string, EvaluateProviderOptions<TAdapter>>,\n TQuestions extends Record<string, WireQuestion>,\n>(options: EvaluateActivityOptions<TAdapter, TQuestions>) {\n const {\n adapter,\n state,\n questions,\n modelOptions,\n abortSignal,\n middleware,\n debug,\n } = options\n const model = adapter.model\n const keys = assertQuestions(questions)\n const requestId = createId('evaluate')\n const startTime = Date.now()\n const logger: InternalLogger = resolveDebugOption(debug)\n\n const mwCtx = createGenerationContext({\n requestId,\n activity: 'evaluate',\n provider: adapter.name,\n model,\n modelOptions,\n createId,\n })\n\n await runGenerationStart(middleware, mwCtx)\n\n aiEventClient.emit('evaluate:request:started', {\n requestId,\n provider: adapter.name,\n model,\n questionCount: keys.length,\n timestamp: startTime,\n })\n\n logger.request(`activity=evaluate provider=${adapter.name}`, {\n provider: adapter.name,\n model,\n questionCount: keys.length,\n })\n\n try {\n const result = await adapter.evaluate({\n model,\n state,\n questions,\n modelOptions,\n abortSignal,\n logger,\n })\n\n const answers = mapAnswers(questions, result.answers)\n const duration = Date.now() - startTime\n\n aiEventClient.emit('evaluate:request:completed', {\n requestId,\n provider: adapter.name,\n model: result.model,\n questionCount: keys.length,\n duration,\n timestamp: Date.now(),\n })\n\n aiEventClient.emit('evaluate:usage', {\n requestId,\n model: result.model,\n usage: result.usage,\n timestamp: Date.now(),\n })\n\n logger.output(`activity=evaluate answers=${keys.length}`, {\n answerCount: keys.length,\n })\n\n await runGenerationUsage(middleware, mwCtx, result.usage)\n await runGenerationFinish(middleware, mwCtx, {\n duration,\n usage: result.usage,\n })\n\n return withMeta(answers, {\n model: result.model,\n usage: result.usage,\n })\n } catch (error) {\n const duration = Date.now() - startTime\n if (isAbortError(error, abortSignal)) {\n await runGenerationAbort(middleware, mwCtx, {\n reason: error instanceof Error ? error.message : undefined,\n duration,\n })\n } else {\n await runGenerationError(middleware, mwCtx, { error, duration })\n }\n logger.errors('evaluate activity failed', { error, source: 'evaluate' })\n throw error\n }\n}\n\n// Re-export adapter types\nexport type {\n EvaluateAdapter,\n EvaluateAdapterConfig,\n AnyEvaluateAdapter,\n EvaluateOptions,\n EvaluateAdapterResult,\n EvaluateState,\n EvaluateInstructions,\n EvaluateJsonValue,\n WireQuestion,\n WireAnswer,\n WireChoiceQuestion,\n WireScoreQuestion,\n WireNoulQuestion,\n WireChoiceAnswer,\n WireScoreAnswer,\n WireNoulAnswer,\n} from './adapter'\nexport { BaseEvaluateAdapter } from './adapter'\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/activities/evaluate/index.ts"],"sourcesContent":["/**\n * Evaluate Activity\n *\n * Asks typed questions about a shared state and returns values your code can\n * branch on. This is a self-contained module with implementation, types, and\n * JSDoc.\n */\n\nimport { aiEventClient } from '@tanstack/ai-event-client'\nimport { resolveDebugOption } from '../../logger/resolve'\nimport { isAbortShapedError } from '../error-payload'\nimport {\n createGenerationContext,\n runGenerationAbort,\n runGenerationError,\n runGenerationFinish,\n runGenerationStart,\n runGenerationUsage,\n} from '../middleware/run'\nimport type { InternalLogger } from '../../logger/internal-logger'\nimport type { DebugOption } from '../../logger/types'\nimport type { TokenUsage } from '../../types'\nimport type { GenerationMiddleware } from '../middleware/types'\nimport type {\n EvaluateAdapter,\n EvaluateInstructions,\n EvaluateState,\n WireAnswer,\n WireChoiceAnswer,\n WireNoulAnswer,\n WireQuestion,\n WireScoreAnswer,\n WireScoreQuestion,\n} from './adapter'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'evaluate' as const\n\n/** Question key reserved for `result.meta`. */\nconst RESERVED_QUESTION_KEY = 'meta' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/** Extract provider options from an EvaluateAdapter via ~types */\nexport type EvaluateProviderOptions<TAdapter> = TAdapter extends {\n '~types': { providerOptions: infer P extends object }\n}\n ? P\n : object\n\n// ===========================\n// Unified answers\n// ===========================\n\n/**\n * Public choice answer. `.value` is the selected option key.\n */\nexport interface ChoiceAnswer<TValue extends string = string> {\n type: 'choice'\n value: TValue\n /** P(selected option). */\n probability: number\n confidence: number\n probabilities: Record<TValue, number>\n}\n\n/**\n * Public score answer. `.value` is the nearest level label.\n * `.score` is the raw TypeSafe fraction.\n */\nexport interface ScoreAnswer<TLevel extends string = string> {\n type: 'score'\n value: TLevel\n /** P(nearest level). */\n probability: number\n confidence: number\n score: number\n legend: Record<string, string>\n probabilities: Record<string, number>\n}\n\n/**\n * Public yes/no answer. `.value` is `true` when P(true) is 0.5 or more.\n * There is no `.confidence`.\n */\nexport interface BooleanAnswer {\n type: 'boolean'\n value: boolean\n /** P(true), from the wire `noul` field. */\n probability: number\n}\n\nexport interface EvaluateResultMeta {\n /** Resolved model id from the provider. */\n model: string\n usage: TokenUsage\n /** Provider response id, when the adapter returns one. */\n id?: string\n /** Upstream provider that served the request, when the adapter returns one. */\n provider?: string\n}\n\n/**\n * Map a helper question (or wire question) to its public answer type.\n */\nexport type InferEvaluateAnswer<TQuestion> = TQuestion extends {\n type: 'choice'\n criteria: infer TCriteria\n}\n ? TCriteria extends Record<string, string | null>\n ? ChoiceAnswer<Extract<keyof TCriteria, string>>\n : ChoiceAnswer\n : TQuestion extends { type: 'score'; criteria: infer TLevels }\n ? TLevels extends ReadonlyArray<string>\n ? ScoreAnswer<TLevels[number] & string>\n : ScoreAnswer\n : TQuestion extends { type: 'noul' }\n ? BooleanAnswer\n : never\n\n/**\n * Result of `decide()`. Each question key is a top-level answer.\n * `meta` holds the resolved model id and usage.\n */\nexport type EvaluateResult<TQuestions extends Record<string, WireQuestion>> = {\n [K in keyof TQuestions as K extends typeof RESERVED_QUESTION_KEY\n ? never\n : K]: InferEvaluateAnswer<TQuestions[K]>\n} & {\n meta: EvaluateResultMeta\n}\n\n// ===========================\n// Activity Options Types\n// ===========================\n\n/**\n * Options for the evaluate activity. The model is extracted from the\n * adapter's model property.\n *\n * @template TAdapter - The evaluate adapter type\n * @template TQuestions - The questions object passed to `decide`\n */\nexport interface EvaluateActivityOptions<\n TAdapter extends EvaluateAdapter<string, EvaluateProviderOptions<TAdapter>>,\n TQuestions extends Record<string, WireQuestion>,\n> {\n /** The evaluate adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** Shared state every question judges. A JSON array is one state, not a batch. */\n state: EvaluateState\n /**\n * Questions built with `choice`, `score`, and `boolean`.\n * The key `meta` is reserved.\n */\n questions: TQuestions\n /** Provider-specific options */\n modelOptions?: EvaluateProviderOptions<TAdapter>\n /** Forwarded to the provider request for cancellation. */\n abortSignal?: AbortSignal\n /**\n * Observe-only middleware notified on start, usage, success, abort, and\n * error. Pass `otelMiddleware()` to emit OpenTelemetry spans, or implement\n * the `GenerationMiddleware` contract for a custom backend.\n */\n middleware?: Array<GenerationMiddleware>\n /**\n * Enable debug logging. Pass `true` to enable all categories, `false` to\n * silence everything including errors, or a `DebugConfig` object for granular\n * control and/or a custom `Logger`.\n */\n debug?: DebugOption\n}\n\n// ===========================\n// Helper Functions\n// ===========================\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\nfunction isAbortError(error: unknown, signal?: AbortSignal): boolean {\n // Prefer the error's own identity over the signal state. A genuine\n // cancellation throws an abort-shaped error (DOM `AbortError`, the OpenRouter\n // SDK's `RequestAbortedError`, ...). Classifying on `signal.aborted` alone\n // would misroute a real failure to the abort hook whenever a shared signal\n // happens to already be aborted, hiding it from `onError` observers.\n if (isAbortShapedError(error)) return true\n // Fall back to signal state only for non-Error throws we can't otherwise\n // identify; a real Error with a non-abort name is never an abort.\n return error instanceof Error ? false : signal?.aborted === true\n}\n\nfunction questionKeys(questions: Record<string, WireQuestion>) {\n return Object.keys(questions)\n}\n\nfunction assertQuestions(questions: Record<string, WireQuestion>) {\n const keys = questionKeys(questions)\n if (keys.length === 0) {\n throw new Error('decide() requires at least one question')\n }\n if (Object.hasOwn(questions, RESERVED_QUESTION_KEY)) {\n throw new Error('decide() reserves the question key \"meta\"')\n }\n return keys\n}\n\nfunction mapChoiceAnswer(wire: WireChoiceAnswer, key: string) {\n const probability = wire.probabilities[wire.choice]\n if (typeof probability !== 'number') {\n throw new Error(\n `decide(): missing probability for choice \"${wire.choice}\" on \"${key}\"`,\n )\n }\n return {\n type: 'choice' as const,\n value: wire.choice,\n probability,\n confidence: wire.confidence,\n probabilities: wire.probabilities,\n }\n}\n\nfunction mapScoreAnswer(\n question: WireScoreQuestion,\n wire: WireScoreAnswer,\n key: string,\n) {\n const levels = question.criteria\n if (levels.length < 2) {\n throw new Error(\n `decide(): score question \"${key}\" needs at least two levels`,\n )\n }\n const lastIndex = levels.length - 1\n const rounded = Math.round(wire.score)\n const nearestIndex =\n rounded < 0 ? 0 : rounded > lastIndex ? lastIndex : rounded\n const value = levels[nearestIndex]\n if (value === undefined) {\n throw new Error(\n `decide(): score question \"${key}\" has no level at index ${nearestIndex}`,\n )\n }\n const probability = wire.probabilities[String(nearestIndex)]\n if (typeof probability !== 'number') {\n throw new Error(\n `decide(): missing probability for score level ${nearestIndex} on \"${key}\"`,\n )\n }\n return {\n type: 'score' as const,\n value,\n probability,\n confidence: wire.confidence,\n score: wire.score,\n legend: wire.legend,\n probabilities: wire.probabilities,\n }\n}\n\nfunction mapBooleanAnswer(wire: WireNoulAnswer) {\n return {\n type: 'boolean' as const,\n value: wire.noul >= 0.5,\n probability: wire.noul,\n }\n}\n\nfunction mapWireAnswer(question: WireQuestion, wire: WireAnswer, key: string) {\n switch (question.type) {\n case 'choice': {\n if (wire.type !== 'choice') {\n throw new Error(\n `decide(): expected choice answer for \"${key}\", got ${wire.type}`,\n )\n }\n return mapChoiceAnswer(wire, key)\n }\n case 'score': {\n if (wire.type !== 'score') {\n throw new Error(\n `decide(): expected score answer for \"${key}\", got ${wire.type}`,\n )\n }\n return mapScoreAnswer(question, wire, key)\n }\n case 'noul': {\n if (wire.type !== 'noul') {\n throw new Error(\n `decide(): expected noul answer for \"${key}\", got ${wire.type}`,\n )\n }\n return mapBooleanAnswer(wire)\n }\n }\n}\n\nfunction mapAnswers<TQuestions extends Record<string, WireQuestion>>(\n questions: TQuestions,\n wireAnswers: Record<string, WireAnswer>,\n) {\n const answers = {} as {\n [K in keyof TQuestions]: InferEvaluateAnswer<TQuestions[K]>\n }\n const keys = Object.keys(questions) as Array<keyof TQuestions>\n for (const key of keys) {\n const question = questions[key]\n const wire = wireAnswers[String(key)]\n if (question === undefined) {\n throw new Error(`decide(): missing question \"${String(key)}\"`)\n }\n if (wire === undefined) {\n throw new Error(`decide(): missing answer for question \"${String(key)}\"`)\n }\n answers[key] = mapWireAnswer(\n question,\n wire,\n String(key),\n ) as InferEvaluateAnswer<TQuestions[typeof key]>\n }\n return answers\n}\n\nfunction withMeta<TAnswers extends object>(\n answers: TAnswers,\n meta: EvaluateResultMeta,\n) {\n return { ...answers, meta }\n}\n\n// ===========================\n// Question helpers\n// ===========================\n\n/**\n * Build a choice question. The model picks one key from `options`.\n *\n * Option keys become the union on `.value`. Use `null` when a key needs no\n * extra description. On the wire, `options` is sent as TypeSafe `criteria`.\n *\n * @param options.instructions What the model should decide.\n * @param options.options Map of option key to description, or `null`.\n *\n * @example\n * ```ts\n * const queue = choice({\n * instructions: 'Which team should handle this ticket?',\n * options: {\n * billing: 'Payments, invoices, refunds',\n * tech: 'Bugs, outages, integrations',\n * sales: 'Pricing, upgrades, new accounts',\n * },\n * })\n * ```\n */\nexport function choice<\n const TOptions extends Record<string, string | null>,\n>(options: { instructions: EvaluateInstructions; options: TOptions }) {\n return {\n type: 'choice' as const,\n instructions: options.instructions,\n criteria: options.options,\n }\n}\n\n/**\n * Build a score question. The model rates `state` on ordered `levels`.\n *\n * You must pass at least two levels. `.value` is the nearest level label.\n * The raw fraction stays on `.score`. On the wire, `levels` is sent as\n * TypeSafe `criteria`.\n *\n * @param options.instructions What the model should rate.\n * @param options.levels Ordered labels, lowest first. At least two.\n *\n * @example\n * ```ts\n * const urgency = score({\n * instructions: 'How urgent is this ticket?',\n * levels: ['low', 'medium', 'high'],\n * })\n * ```\n */\nexport function score<const TLevels extends ReadonlyArray<string>>(options: {\n instructions: EvaluateInstructions\n levels: TLevels\n}) {\n if (options.levels.length < 2) {\n throw new Error('score() requires at least two levels')\n }\n return {\n type: 'score' as const,\n instructions: options.instructions,\n criteria: options.levels,\n }\n}\n\n/**\n * Build a yes/no question.\n *\n * `.value` is `true` when P(true) is 0.5 or more. There is no `.confidence`.\n * On the wire, the type is TypeSafe `noul`.\n *\n * @param options.instructions The yes/no question to judge.\n * @param options.criteria Optional descriptions of yes and no.\n *\n * @example\n * ```ts\n * const refund = boolean({\n * instructions: 'Is the customer asking for a refund?',\n * })\n * ```\n */\nexport function boolean(options: {\n instructions: EvaluateInstructions\n criteria?: {\n true?: string\n false?: string\n }\n}) {\n if (options.criteria === undefined) {\n return {\n type: 'noul' as const,\n instructions: options.instructions,\n }\n }\n return {\n type: 'noul' as const,\n instructions: options.instructions,\n criteria: options.criteria,\n }\n}\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Ask typed questions about `state` and get answers your code can branch on.\n *\n * You have state (a ticket, a record, a log) and you need typed answers, not\n * prose. Pass questions built with `choice`, `score`, and `boolean`. Then\n * branch on `result.queue.value` in ordinary TypeScript.\n *\n * The question key `meta` is reserved. Throws if `questions` is empty or uses\n * that key.\n *\n * @param options.adapter Evaluate adapter created with a model.\n * @param options.state Shared state every question judges.\n * @param options.questions Questions built with `choice`, `score`, `boolean`.\n * @param options.modelOptions Provider-specific options.\n * @param options.abortSignal Cancels the in-flight request.\n * @param options.middleware Observe-only generation middleware.\n * @param options.debug Debug logging option.\n *\n * @example Route a support ticket\n * ```ts\n * import { decide, choice, score, boolean } from '@tanstack/ai'\n * import { typesafeDecider } from '@tanstack/ai-typesafe'\n *\n * const result = await decide({\n * adapter: typesafeDecider('jev-latest'),\n * state: ticket,\n * questions: {\n * queue: choice({\n * instructions: 'Which team should handle this ticket?',\n * options: {\n * billing: 'Payments, invoices, refunds',\n * tech: 'Bugs, outages, integrations',\n * sales: 'Pricing, upgrades, new accounts',\n * },\n * }),\n * urgency: score({\n * instructions: 'How urgent is this ticket?',\n * levels: ['low', 'medium', 'high'],\n * }),\n * refund: boolean({\n * instructions: 'Is the customer asking for a refund?',\n * }),\n * },\n * })\n *\n * result.queue.value\n * result.meta.model\n * result.meta.usage\n * ```\n */\nexport async function decide<\n TAdapter extends EvaluateAdapter<string, EvaluateProviderOptions<TAdapter>>,\n TQuestions extends Record<string, WireQuestion>,\n>(options: EvaluateActivityOptions<TAdapter, TQuestions>) {\n const {\n adapter,\n state,\n questions,\n modelOptions,\n abortSignal,\n middleware,\n debug,\n } = options\n const model = adapter.model\n const keys = assertQuestions(questions)\n const requestId = createId('evaluate')\n const startTime = Date.now()\n const logger: InternalLogger = resolveDebugOption(debug)\n\n const mwCtx = createGenerationContext({\n requestId,\n activity: 'evaluate',\n provider: adapter.name,\n model,\n modelOptions,\n createId,\n })\n\n await runGenerationStart(middleware, mwCtx)\n\n aiEventClient.emit('evaluate:request:started', {\n requestId,\n provider: adapter.name,\n model,\n questionCount: keys.length,\n timestamp: startTime,\n })\n\n logger.request(`activity=evaluate provider=${adapter.name}`, {\n provider: adapter.name,\n model,\n questionCount: keys.length,\n })\n\n try {\n const result = await adapter.evaluate({\n model,\n state,\n questions,\n modelOptions,\n abortSignal,\n logger,\n })\n\n const answers = mapAnswers(questions, result.answers)\n const duration = Date.now() - startTime\n\n aiEventClient.emit('evaluate:request:completed', {\n requestId,\n provider: adapter.name,\n model: result.model,\n questionCount: keys.length,\n duration,\n timestamp: Date.now(),\n })\n\n aiEventClient.emit('evaluate:usage', {\n requestId,\n model: result.model,\n usage: result.usage,\n timestamp: Date.now(),\n })\n\n logger.output(`activity=evaluate answers=${keys.length}`, {\n answerCount: keys.length,\n })\n\n await runGenerationUsage(middleware, mwCtx, result.usage)\n await runGenerationFinish(middleware, mwCtx, {\n duration,\n usage: result.usage,\n })\n\n return withMeta(answers, {\n model: result.model,\n usage: result.usage,\n ...(result.id !== undefined && { id: result.id }),\n ...(result.provider !== undefined && { provider: result.provider }),\n })\n } catch (error) {\n const duration = Date.now() - startTime\n if (isAbortError(error, abortSignal)) {\n await runGenerationAbort(middleware, mwCtx, {\n reason: error instanceof Error ? error.message : undefined,\n duration,\n })\n } else {\n await runGenerationError(middleware, mwCtx, { error, duration })\n }\n logger.errors('evaluate activity failed', { error, source: 'evaluate' })\n throw error\n }\n}\n\n// Re-export adapter types\nexport type {\n EvaluateAdapter,\n EvaluateAdapterConfig,\n AnyEvaluateAdapter,\n EvaluateOptions,\n EvaluateAdapterResult,\n EvaluateState,\n EvaluateInstructions,\n EvaluateJsonValue,\n WireQuestion,\n WireAnswer,\n WireChoiceQuestion,\n WireScoreQuestion,\n WireNoulQuestion,\n WireChoiceAnswer,\n WireScoreAnswer,\n WireNoulAnswer,\n} from './adapter'\nexport { BaseEvaluateAdapter } from './adapter'\n"],"mappings":"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"}
@@ -152,7 +152,7 @@ export interface ListVoicesActivityOptions<TAdapter extends TTSAdapter<string, T
152
152
  * import { elevenlabsSpeech } from '@tanstack/ai-elevenlabs'
153
153
  *
154
154
  * const { voices } = await listVoices({
155
- * adapter: elevenlabsSpeech('eleven_v3'),
155
+ * adapter: elevenlabsSpeech('eleven_v4'),
156
156
  * origins: ['generated', 'cloned'],
157
157
  * })
158
158
  * ```
@@ -208,7 +208,7 @@ async function runGenerateSpeech(options) {
208
208
  * import { elevenlabsSpeech } from '@tanstack/ai-elevenlabs'
209
209
  *
210
210
  * const { voices } = await listVoices({
211
- * adapter: elevenlabsSpeech('eleven_v3'),
211
+ * adapter: elevenlabsSpeech('eleven_v4'),
212
212
  * origins: ['generated', 'cloned'],
213
213
  * })
214
214
  * ```