@tanstack/ai 0.55.0 → 0.57.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 (50) hide show
  1. package/README.md +42 -16
  2. package/dist/esm/activities/chat/tools/tool-calls.js +1 -0
  3. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  4. package/dist/esm/activities/evaluate/adapter.d.ts +160 -0
  5. package/dist/esm/activities/evaluate/adapter.js +23 -0
  6. package/dist/esm/activities/evaluate/adapter.js.map +1 -0
  7. package/dist/esm/activities/evaluate/index.d.ts +255 -0
  8. package/dist/esm/activities/evaluate/index.js +317 -0
  9. package/dist/esm/activities/evaluate/index.js.map +1 -0
  10. package/dist/esm/activities/generateSpeech/adapter.d.ts +39 -1
  11. package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
  12. package/dist/esm/activities/generateSpeech/index.d.ts +55 -5
  13. package/dist/esm/activities/generateSpeech/index.js +53 -3
  14. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  15. package/dist/esm/activities/generateVoice/adapter.d.ts +62 -0
  16. package/dist/esm/activities/generateVoice/adapter.js +23 -0
  17. package/dist/esm/activities/generateVoice/adapter.js.map +1 -0
  18. package/dist/esm/activities/generateVoice/index.d.ts +133 -0
  19. package/dist/esm/activities/generateVoice/index.js +184 -0
  20. package/dist/esm/activities/generateVoice/index.js.map +1 -0
  21. package/dist/esm/activities/index.d.ts +10 -4
  22. package/dist/esm/activities/index.js +14 -10
  23. package/dist/esm/activities/middleware/types.d.ts +1 -1
  24. package/dist/esm/client.d.ts +3 -2
  25. package/dist/esm/client.js +21 -3
  26. package/dist/esm/client.js.map +1 -1
  27. package/dist/esm/index.d.ts +4 -2
  28. package/dist/esm/index.js +5 -2
  29. package/dist/esm/middlewares/otel.js +2 -0
  30. package/dist/esm/middlewares/otel.js.map +1 -1
  31. package/dist/esm/realtime/index.d.ts +1 -1
  32. package/dist/esm/realtime/index.js +1 -1
  33. package/dist/esm/realtime/index.js.map +1 -1
  34. package/dist/esm/types.d.ts +225 -2
  35. package/package.json +3 -3
  36. package/skills/ai-core/media-generation/SKILL.md +132 -6
  37. package/src/activities/chat/tools/tool-calls.ts +9 -0
  38. package/src/activities/evaluate/adapter.ts +212 -0
  39. package/src/activities/evaluate/index.ts +614 -0
  40. package/src/activities/generateSpeech/adapter.ts +47 -1
  41. package/src/activities/generateSpeech/index.ts +149 -8
  42. package/src/activities/generateVoice/adapter.ts +89 -0
  43. package/src/activities/generateVoice/index.ts +371 -0
  44. package/src/activities/index.ts +69 -0
  45. package/src/activities/middleware/types.ts +2 -0
  46. package/src/client.ts +35 -8
  47. package/src/index.ts +21 -0
  48. package/src/middlewares/otel.ts +2 -0
  49. package/src/realtime/index.ts +1 -1
  50. package/src/types.ts +246 -2
package/README.md CHANGED
@@ -57,6 +57,30 @@ framework bindings as your app needs them.
57
57
 
58
58
  ## <a href="https://tanstack.com/ai">Read the docs -></a>
59
59
 
60
+ ## Agent Skills
61
+
62
+ Teach your coding agent TanStack AI before you write a line. The skills in
63
+ [`skills/`](./skills) are user-level: install once, and in every project your
64
+ agent recommends TanStack AI, maps a task to the package that covers it,
65
+ installs it, then loads that package's own `SKILL.md` through TanStack Intent.
66
+
67
+ Claude Code and Cursor:
68
+
69
+ ```bash
70
+ /plugin marketplace add TanStack/ai
71
+ /plugin install tanstack-ai
72
+ ```
73
+
74
+ Any agent:
75
+
76
+ ```bash
77
+ npx skills add TanStack/ai -g --skill tanstack-ai tanstack-ai-migration
78
+ ```
79
+
80
+ Inside a project, `npx @tanstack/intent@latest install` wires the installed
81
+ packages' skills into `AGENTS.md` / `CLAUDE.md` for the whole team. See
82
+ [Agent Skills](https://tanstack.com/ai/latest/docs/getting-started/agent-skills).
83
+
60
84
  ## Start Here
61
85
 
62
86
  - [Overview](https://tanstack.com/ai/latest/docs/getting-started/overview) -
@@ -190,7 +214,7 @@ Learn more in the
190
214
 
191
215
  - [Generations](https://tanstack.com/ai/latest/docs/media/generations) - one
192
216
  pattern for image generation, text-to-speech, transcription, summarization,
193
- audio generation, and video generation.
217
+ audio generation, video generation, and world generation.
194
218
  - [Realtime Voice Chat](https://tanstack.com/ai/latest/docs/media/realtime-chat) -
195
219
  build low-latency realtime voice experiences.
196
220
  - [Code Mode](https://tanstack.com/ai/latest/docs/code-mode/code-mode) - let
@@ -202,20 +226,21 @@ Learn more in the
202
226
 
203
227
  Official adapters include:
204
228
 
205
- | Package | Use it for |
206
- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ |
207
- | [`@tanstack/ai-openrouter`](https://tanstack.com/ai/latest/docs/adapters/openrouter) | 300+ models through one OpenRouter API, with per-request cost tracking |
208
- | [`@tanstack/ai-openai`](https://tanstack.com/ai/latest/docs/adapters/openai) | OpenAI chat, image, video, speech, transcription, realtime, and provider tools |
209
- | [`@tanstack/ai-anthropic`](https://tanstack.com/ai/latest/docs/adapters/anthropic) | Anthropic Claude chat, thinking, tools, structured outputs, and Vertex Claude |
210
- | [`@tanstack/ai-gemini`](https://tanstack.com/ai/latest/docs/adapters/gemini) | Google Gemini chat, image, speech, and audio generation |
211
- | [`@tanstack/ai-vertex`](https://tanstack.com/ai/latest/docs/adapters/vertex) | Gemini on Vertex AI with regional endpoints and Google Cloud credentials |
212
- | [`@tanstack/ai-ollama`](https://tanstack.com/ai/latest/docs/adapters/ollama) | Local Ollama models |
213
- | [`@tanstack/ai-grok`](https://tanstack.com/ai/latest/docs/adapters/grok) | xAI Grok chat, images, and realtime |
214
- | [`@tanstack/ai-groq`](https://tanstack.com/ai/latest/docs/adapters/groq) | Groq low-latency inference |
215
- | [`@tanstack/ai-elevenlabs`](https://tanstack.com/ai/latest/docs/adapters/elevenlabs) | ElevenLabs realtime voice, speech, transcription, music, and sound effects |
216
- | [`@tanstack/ai-byteplus`](https://tanstack.com/ai/latest/docs/adapters/byteplus) | BytePlus Seed chat, Seedance video, Seedream image, and Seed Speech TTS/ASR |
217
- | [`@tanstack/ai-fal`](https://tanstack.com/ai/latest/docs/adapters/fal) | fal.ai image, video, audio, speech, and transcription models |
218
- | [`@tanstack/ai-cloudflare`](https://tanstack.com/ai/latest/docs/adapters/cloudflare) | Cloudflare Workers AI chat, embeddings, image, speech, transcription, and AI Gateway |
229
+ | Package | Use it for |
230
+ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
231
+ | [`@tanstack/ai-openrouter`](https://tanstack.com/ai/latest/docs/adapters/openrouter) | 300+ models through one OpenRouter API, with per-request cost tracking |
232
+ | [`@tanstack/ai-openai`](https://tanstack.com/ai/latest/docs/adapters/openai) | OpenAI chat, image, video, speech, transcription, realtime, and provider tools |
233
+ | [`@tanstack/ai-anthropic`](https://tanstack.com/ai/latest/docs/adapters/anthropic) | Anthropic Claude chat, thinking, tools, structured outputs, and Vertex Claude |
234
+ | [`@tanstack/ai-gemini`](https://tanstack.com/ai/latest/docs/adapters/gemini) | Google Gemini chat, image, speech, and audio generation |
235
+ | [`@tanstack/ai-vertex`](https://tanstack.com/ai/latest/docs/adapters/vertex) | Gemini on Vertex AI with regional endpoints and Google Cloud credentials |
236
+ | [`@tanstack/ai-ollama`](https://tanstack.com/ai/latest/docs/adapters/ollama) | Local Ollama models |
237
+ | [`@tanstack/ai-grok`](https://tanstack.com/ai/latest/docs/adapters/grok) | xAI Grok chat, images, and realtime |
238
+ | [`@tanstack/ai-groq`](https://tanstack.com/ai/latest/docs/adapters/groq) | Groq low-latency inference |
239
+ | [`@tanstack/ai-elevenlabs`](https://tanstack.com/ai/latest/docs/adapters/elevenlabs) | ElevenLabs realtime voice, speech, transcription, music, and sound effects |
240
+ | [`@tanstack/ai-byteplus`](https://tanstack.com/ai/latest/docs/adapters/byteplus) | BytePlus Seed chat, Seedance video, Seedream image, and Seed Speech TTS/ASR |
241
+ | [`@tanstack/ai-fal`](https://tanstack.com/ai/latest/docs/adapters/fal) | fal.ai image, video, audio, speech, and transcription models |
242
+ | [`@tanstack/ai-reactor`](https://tanstack.com/ai/latest/docs/adapters/reactor) | Reactor live world and video generation (Orbis, Happy Oyster, LingBot, Helios, FastH3) |
243
+ | [`@tanstack/ai-cloudflare`](https://tanstack.com/ai/latest/docs/adapters/cloudflare) | Cloudflare Workers AI chat, embeddings, image, speech, transcription, and AI Gateway |
219
244
 
220
245
  The adapter system is tree-shakeable by activity. Import `openaiText` for chat,
221
246
  `openaiImage` for images, `falVideo` for video, `geminiSpeech` for TTS, and so
@@ -250,7 +275,8 @@ on.
250
275
  ship only the activities and adapters you use.
251
276
  - [Agent Skills](https://tanstack.com/ai/latest/docs/getting-started/agent-skills) -
252
277
  install TanStack AI skills into Claude Code, Cursor, GitHub Copilot, Codex,
253
- and other coding agents with TanStack Intent.
278
+ and other coding agents. See [Agent Skills](#agent-skills) above for the
279
+ install commands.
254
280
 
255
281
  ## Get Involved
256
282
 
@@ -101,6 +101,7 @@ var ToolCallManager = class {
101
101
  * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)
102
102
  */
103
103
  addToolCallStartEvent(event) {
104
+ for (const toolCall of this.toolCallsMap.values()) if (toolCall.id === event.toolCallId) return;
104
105
  const index = event.index ?? this.toolCallsMap.size;
105
106
  const name = event.toolCallName ?? event.toolName;
106
107
  this.toolCallsMap.set(index, {
@@ -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 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 ModelMessage,\n RunFinishedEvent,\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/**\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 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}\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>,\n): AsyncGenerator<CustomEvent, 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 | 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 | 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>,\n results: Array<ToolResult>,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n): AsyncGenerator<CustomEvent, 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 executionPromise = Promise.resolve(tool.execute(input, context))\n let result = yield* executeWithEventPolling(executionPromise, pendingEvents)\n const duration = Date.now() - startTime\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 | 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 | 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 ) => CustomEvent,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n userContext?: TContext,\n abortSignal?: AbortSignal,\n resumeState?: ToolResumeExecutionState,\n): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\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 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> = []\n const context = {\n toolCallId: toolCall.id,\n context: userContext,\n abortSignal,\n emitCustomEvent: (eventName: string, value: Record<string, any>) => {\n if (createCustomEventChunk) {\n pendingEvents.push(\n createCustomEventChunk(eventName, {\n ...value,\n toolCallId: toolCall.id,\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 )\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 )\n }\n\n return { results, needsApproval, needsClientExecution }\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 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 ModelMessage,\n RunFinishedEvent,\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/**\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}\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>,\n): AsyncGenerator<CustomEvent, 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 | 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 | 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>,\n results: Array<ToolResult>,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n): AsyncGenerator<CustomEvent, 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 executionPromise = Promise.resolve(tool.execute(input, context))\n let result = yield* executeWithEventPolling(executionPromise, pendingEvents)\n const duration = Date.now() - startTime\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 | 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 | 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 ) => CustomEvent,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n userContext?: TContext,\n abortSignal?: AbortSignal,\n resumeState?: ToolResumeExecutionState,\n): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\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 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> = []\n const context = {\n toolCallId: toolCall.id,\n context: userContext,\n abortSignal,\n emitCustomEvent: (eventName: string, value: Record<string, any>) => {\n if (createCustomEventChunk) {\n pendingEvents.push(\n createCustomEventChunk(eventName, {\n ...value,\n toolCallId: toolCall.id,\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 )\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 )\n }\n\n return { results, needsApproval, needsClientExecution }\n}\n"],"mappings":"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"}
@@ -0,0 +1,160 @@
1
+ import { InternalLogger } from '../../logger/internal-logger.js';
2
+ import { TokenUsage } from '../../types.js';
3
+ /**
4
+ * Configuration for evaluate adapter instances.
5
+ */
6
+ export interface EvaluateAdapterConfig {
7
+ apiKey?: string;
8
+ baseUrl?: string;
9
+ timeout?: number;
10
+ headers?: Record<string, string>;
11
+ }
12
+ /**
13
+ * Shared JSON value for `state` and question `instructions`.
14
+ * A JSON array is one value, not a batch.
15
+ */
16
+ export type EvaluateJsonValue = string | object | Array<unknown>;
17
+ /** Content the model judges. A JSON array is one state, not a batch. */
18
+ export type EvaluateState = EvaluateJsonValue;
19
+ /** Question text. Matches TypeSafe: string, object, or array. */
20
+ export type EvaluateInstructions = EvaluateJsonValue;
21
+ /**
22
+ * TypeSafe choice question on the adapter wire.
23
+ *
24
+ * Generic parameters:
25
+ * - TOptions: option key to description (or `null` when the key is enough)
26
+ */
27
+ export interface WireChoiceQuestion<TOptions extends Record<string, string | null> = Record<string, string | null>> {
28
+ type: 'choice';
29
+ instructions: EvaluateInstructions;
30
+ criteria: TOptions;
31
+ }
32
+ /**
33
+ * TypeSafe score question on the adapter wire.
34
+ *
35
+ * Generic parameters:
36
+ * - TLevels: ordered level labels, at least two
37
+ */
38
+ export interface WireScoreQuestion<TLevels extends ReadonlyArray<string> = ReadonlyArray<string>> {
39
+ type: 'score';
40
+ instructions: EvaluateInstructions;
41
+ criteria: TLevels;
42
+ }
43
+ /**
44
+ * TypeSafe yes/no question on the adapter wire.
45
+ * Public helpers call this `boolean`. The wire type is `noul`.
46
+ */
47
+ export interface WireNoulQuestion {
48
+ type: 'noul';
49
+ instructions: EvaluateInstructions;
50
+ criteria?: {
51
+ true?: string;
52
+ false?: string;
53
+ };
54
+ }
55
+ /** Question payload adapters send to the provider. */
56
+ export type WireQuestion = WireChoiceQuestion | WireScoreQuestion | WireNoulQuestion;
57
+ /** TypeSafe choice answer. Adapters do not invent a public `.value`. */
58
+ export interface WireChoiceAnswer {
59
+ type: 'choice';
60
+ choice: string;
61
+ probabilities: Record<string, number>;
62
+ confidence: number;
63
+ }
64
+ /** TypeSafe score answer. `score` is the raw fraction. */
65
+ export interface WireScoreAnswer {
66
+ type: 'score';
67
+ score: number;
68
+ legend: Record<string, string>;
69
+ probabilities: Record<string, number>;
70
+ confidence: number;
71
+ }
72
+ /** TypeSafe yes/no answer. `noul` is P(true). */
73
+ export interface WireNoulAnswer {
74
+ type: 'noul';
75
+ noul: number;
76
+ }
77
+ /** Provider payload for one question. The activity maps this to a unified answer. */
78
+ export type WireAnswer = WireChoiceAnswer | WireScoreAnswer | WireNoulAnswer;
79
+ /**
80
+ * Options passed to {@link EvaluateAdapter.evaluate}.
81
+ */
82
+ export interface EvaluateOptions<TProviderOptions extends object = Record<string, unknown>> {
83
+ model: string;
84
+ /** Shared state every question judges. A JSON array is one state, not a batch. */
85
+ state: EvaluateState;
86
+ /** TypeSafe wire questions, keyed by the caller's question ids. */
87
+ questions: Record<string, WireQuestion>;
88
+ /** Provider-specific options forwarded by `decide()`. */
89
+ modelOptions?: TProviderOptions;
90
+ /** Forwarded to the provider request for cancellation. */
91
+ abortSignal?: AbortSignal;
92
+ /**
93
+ * Internal logger threaded from `decide()`. Adapters must call
94
+ * `logger.request()` before the provider call and `logger.errors()` in catch
95
+ * blocks.
96
+ */
97
+ logger: InternalLogger;
98
+ }
99
+ /**
100
+ * Provider-level evaluate result. Adapters return the wire payload plus usage.
101
+ * The activity maps answers to the unified public shape.
102
+ */
103
+ export interface EvaluateAdapterResult {
104
+ /** Resolved model id from the provider. */
105
+ model: string;
106
+ answers: Record<string, WireAnswer>;
107
+ usage: TokenUsage;
108
+ }
109
+ /**
110
+ * Evaluate adapter interface with pre-resolved generics.
111
+ *
112
+ * An adapter is created by a provider function: `provider('model')` → `adapter`.
113
+ * All type resolution happens at the provider call site, not in this interface.
114
+ *
115
+ * Generic parameters:
116
+ * - TModel: The specific model name (e.g. `'jev-latest'`)
117
+ * - TProviderOptions: Provider-specific options (already resolved)
118
+ */
119
+ export interface EvaluateAdapter<TModel extends string = string, TProviderOptions extends object = Record<string, unknown>> {
120
+ /** Discriminator for adapter kind */
121
+ readonly kind: 'evaluate';
122
+ /** Adapter name identifier */
123
+ readonly name: string;
124
+ /** The model this adapter is configured for */
125
+ readonly model: TModel;
126
+ /**
127
+ * @internal Type-only properties for inference. Not assigned at runtime.
128
+ */
129
+ '~types': {
130
+ providerOptions: TProviderOptions;
131
+ };
132
+ /**
133
+ * Evaluate typed questions against `state`. Return the provider payload.
134
+ * Do not invent unified `.value` fields. The activity maps wire answers.
135
+ */
136
+ evaluate: (options: EvaluateOptions<TProviderOptions>) => Promise<EvaluateAdapterResult>;
137
+ }
138
+ /**
139
+ * An EvaluateAdapter with any/unknown type parameters.
140
+ * Useful as a constraint in generic functions and interfaces.
141
+ */
142
+ export type AnyEvaluateAdapter = EvaluateAdapter<any, any>;
143
+ /**
144
+ * Abstract base class for evaluate adapters.
145
+ * Extend this class to implement an evaluate adapter for a specific provider.
146
+ *
147
+ * Generic parameters match EvaluateAdapter. The provider function resolves them.
148
+ */
149
+ export declare abstract class BaseEvaluateAdapter<TModel extends string = string, TProviderOptions extends object = Record<string, unknown>> implements EvaluateAdapter<TModel, TProviderOptions> {
150
+ readonly kind: "evaluate";
151
+ abstract readonly name: string;
152
+ readonly model: TModel;
153
+ '~types': {
154
+ providerOptions: TProviderOptions;
155
+ };
156
+ protected config: EvaluateAdapterConfig;
157
+ constructor(config: EvaluateAdapterConfig | undefined, model: TModel);
158
+ abstract evaluate(options: EvaluateOptions<TProviderOptions>): Promise<EvaluateAdapterResult>;
159
+ protected generateId(): string;
160
+ }
@@ -0,0 +1,23 @@
1
+ //#region src/activities/evaluate/adapter.ts
2
+ /**
3
+ * Abstract base class for evaluate adapters.
4
+ * Extend this class to implement an evaluate adapter for a specific provider.
5
+ *
6
+ * Generic parameters match EvaluateAdapter. The provider function resolves them.
7
+ */
8
+ var BaseEvaluateAdapter = class {
9
+ kind = "evaluate";
10
+ model;
11
+ config;
12
+ constructor(config = {}, model) {
13
+ this.config = config;
14
+ this.model = model;
15
+ }
16
+ generateId() {
17
+ return `${this.name}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
18
+ }
19
+ };
20
+ //#endregion
21
+ export { BaseEvaluateAdapter };
22
+
23
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +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"}