@tanstack/ai 0.44.1 → 0.45.1
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.
- package/README.md +10 -1
- package/dist/esm/activities/chat/adapter.d.ts +13 -1
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.js +122 -69
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +24 -27
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.js +14 -13
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/approval-schema.js +11 -8
- package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.js +40 -31
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +3 -2
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -0
- package/dist/esm/adapter-internals.js +3 -1
- package/dist/esm/interrupt-resume.js +24 -20
- package/dist/esm/interrupt-resume.js.map +1 -1
- package/dist/esm/interrupts.js +2 -1
- package/dist/esm/interrupts.js.map +1 -1
- package/dist/esm/logger/console-logger.js +1 -3
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/resolve.js +2 -1
- package/dist/esm/logger/resolve.js.map +1 -1
- package/dist/esm/stream-durability.js +1 -1
- package/dist/esm/stream-durability.js.map +1 -1
- package/dist/esm/types.d.ts +19 -16
- package/dist/esm/utilities/chat-params.js +1 -3
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/media-prompt.js +1 -3
- package/dist/esm/utilities/media-prompt.js.map +1 -1
- package/dist/esm/utilities/structured-output-events.d.ts +17 -0
- package/dist/esm/utilities/structured-output-events.js +32 -0
- package/dist/esm/utilities/structured-output-events.js.map +1 -0
- package/dist/esm/utilities/structured-output-text.d.ts +7 -0
- package/dist/esm/utilities/structured-output-text.js +50 -0
- package/dist/esm/utilities/structured-output-text.js.map +1 -0
- package/package.json +2 -2
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +6 -2
- package/skills/ai-core/media-generation/SKILL.md +33 -19
- package/skills/ai-core/structured-outputs/SKILL.md +73 -1
- package/skills/ai-core/tool-calling/SKILL.md +1 -1
- package/src/activities/chat/adapter.ts +16 -1
- package/src/activities/chat/index.ts +155 -52
- package/src/activities/chat/stream/processor.ts +6 -3
- package/src/activities/chat/tools/tool-calls.ts +12 -4
- package/src/adapter-internals.ts +8 -0
- package/src/types.ts +27 -19
- package/src/utilities/structured-output-events.ts +44 -0
- package/src/utilities/structured-output-text.ts +63 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-schema.js","names":[],"sources":["../../../../../src/activities/chat/tools/approval-schema.ts"],"sourcesContent":["import {\n canonicalInterruptJson,\n digestInterruptJson,\n} from '../../../interrupt-serialization'\nimport { isStandardJSONSchema, isStandardSchema } from './schema-converter'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { JSONSchema, SchemaInput } from '../../../types'\nimport type { ApprovalSchemaConfig } from './tool-definition'\n\nexport interface NormalizedSchemaInput {\n source: SchemaInput\n validator?: StandardSchemaV1\n jsonSchema?: JSONSchema\n}\n\nexport interface NormalizedApprovalSchema {\n branches: {\n approve: NormalizedSchemaInput | null\n reject: NormalizedSchemaInput | null\n }\n responseSchema: JSONSchema\n responseSchemaHash: string\n approvalSchemaHash: string\n}\n\nconst jsonSchemaKeywords = new Set([\n '$schema',\n '$id',\n '$ref',\n '$defs',\n 'type',\n 'properties',\n 'required',\n 'additionalProperties',\n 'items',\n 'oneOf',\n 'anyOf',\n 'allOf',\n 'enum',\n 'const',\n 'format',\n 'minimum',\n 'maximum',\n 'minLength',\n 'maxLength',\n 'pattern',\n])\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n (Object.getPrototypeOf(value) === Object.prototype ||\n Object.getPrototypeOf(value) === null)\n )\n}\n\nfunction isRawJsonSchema(value: unknown): value is JSONSchema {\n return (\n isPlainRecord(value) &&\n Object.keys(value).some((key) => jsonSchemaKeywords.has(key))\n )\n}\n\nfunction isSchemaInput(value: unknown): value is SchemaInput {\n return (\n isStandardSchema(value) ||\n isStandardJSONSchema(value) ||\n isRawJsonSchema(value)\n )\n}\n\nfunction isApprovalBranchMap(\n value: unknown,\n): value is { approve?: SchemaInput; reject?: SchemaInput } {\n if (!isPlainRecord(value)) return false\n const keys = Object.keys(value)\n return (\n keys.length > 0 &&\n keys.every((key) => key === 'approve' || key === 'reject') &&\n keys.every((key) => isSchemaInput(value[key]))\n )\n}\n\nfunction toJsonSchema(value: Record<string, unknown>): JSONSchema {\n const result: JSONSchema = {}\n for (const [key, item] of Object.entries(value)) {\n result[key] = item\n }\n return result\n}\n\nfunction schemaToWire(schema: SchemaInput): NormalizedSchemaInput {\n if (isStandardSchema(schema)) {\n const jsonSchema = isStandardJSONSchema(schema)\n ? toJsonSchema(\n schema['~standard'].jsonSchema.input({\n target: 'draft-2020-12',\n }),\n )\n : undefined\n return {\n source: schema,\n validator: schema,\n ...(jsonSchema !== undefined && { jsonSchema }),\n }\n }\n if (isRawJsonSchema(schema)) {\n // The library does not compile or validate raw JSON Schema; it is carried\n // on the wire and hashed as-is. Validation is the application's job.\n return { source: schema, jsonSchema: schema }\n }\n throw new TypeError('Expected a supported SchemaInput.')\n}\n\nfunction decisionEnvelope(input: {\n approved: boolean\n payload: NormalizedSchemaInput | null\n inputSchema: NormalizedSchemaInput | null\n}): JSONSchema {\n const properties: Record<string, JSONSchema> = {\n approved: { const: input.approved },\n }\n const required = ['approved']\n if (input.approved && input.inputSchema) {\n properties['editedArgs'] = input.inputSchema.jsonSchema ?? {}\n }\n if (input.payload) {\n properties['payload'] = input.payload.jsonSchema ?? {}\n required.push('payload')\n }\n return {\n type: 'object',\n properties,\n required,\n additionalProperties: false,\n }\n}\n\nexport function normalizeApprovalSchema(\n approvalSchema: ApprovalSchemaConfig | undefined,\n inputSchema?: SchemaInput,\n): NormalizedApprovalSchema {\n const normalizedInput =\n inputSchema === undefined ? null : schemaToWire(inputSchema)\n let approve: NormalizedSchemaInput | null = null\n let reject: NormalizedSchemaInput | null = null\n\n if (approvalSchema !== undefined) {\n if (isStandardSchema(approvalSchema) || isRawJsonSchema(approvalSchema)) {\n approve = schemaToWire(approvalSchema)\n reject = approve\n } else if (isApprovalBranchMap(approvalSchema)) {\n approve =\n approvalSchema.approve === undefined\n ? null\n : schemaToWire(approvalSchema.approve)\n reject =\n approvalSchema.reject === undefined\n ? null\n : schemaToWire(approvalSchema.reject)\n } else {\n throw new TypeError(\n 'approvalSchema must be a SchemaInput or a nonempty map containing approve or reject.',\n )\n }\n }\n\n const responseSchema: JSONSchema = {\n oneOf: [\n decisionEnvelope({\n approved: true,\n payload: approve,\n inputSchema: normalizedInput,\n }),\n decisionEnvelope({\n approved: false,\n payload: reject,\n inputSchema: null,\n }),\n ],\n }\n const responseCanonical = canonicalInterruptJson(responseSchema)\n const approvalCanonical = canonicalInterruptJson({\n approve: approve?.jsonSchema ?? null,\n reject: reject?.jsonSchema ?? null,\n })\n\n return {\n branches: { approve, reject },\n responseSchema,\n responseSchemaHash: digestInterruptJson(responseCanonical),\n approvalSchemaHash: digestInterruptJson(approvalCanonical),\n }\n}\n\nexport function hashSchemaInput(schema: SchemaInput | undefined): string {\n if (schema === undefined) return digestInterruptJson('undefined')\n const normalized = schemaToWire(schema)\n const identity = normalized.jsonSchema ?? {\n standardValidator: 'unserialized',\n }\n return digestInterruptJson(canonicalInterruptJson(identity))\n}\n"],"mappings":";;;AAyBA,IAAM,qCAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,cAAc,OAAkD;CACvE,OACE,UAAU,QACV,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,KAAK,MACnB,OAAO,eAAe,KAAK,MAAM,OAAO,aACvC,OAAO,eAAe,KAAK,MAAM;AAEvC;AAEA,SAAS,gBAAgB,OAAqC;CAC5D,OACE,cAAc,KAAK,KACnB,OAAO,KAAK,KAAK,CAAC,CAAC,MAAM,QAAQ,mBAAmB,IAAI,GAAG,CAAC;AAEhE;AAEA,SAAS,cAAc,OAAsC;CAC3D,OACE,iBAAiB,KAAK,KACtB,qBAAqB,KAAK,KAC1B,gBAAgB,KAAK;AAEzB;AAEA,SAAS,oBACP,OAC0D;CAC1D,IAAI,CAAC,cAAc,KAAK,GAAG,OAAO;CAClC,MAAM,OAAO,OAAO,KAAK,KAAK;CAC9B,OACE,KAAK,SAAS,KACd,KAAK,OAAO,QAAQ,QAAQ,aAAa,QAAQ,QAAQ,KACzD,KAAK,OAAO,QAAQ,cAAc,MAAM,IAAI,CAAC;AAEjD;AAEA,SAAS,aAAa,OAA4C;CAChE,MAAM,SAAqB,CAAC;CAC5B,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,KAAK,GAC5C,OAAO,OAAO;CAEhB,OAAO;AACT;AAEA,SAAS,aAAa,QAA4C;CAChE,IAAI,iBAAiB,MAAM,GAAG;EAC5B,MAAM,aAAa,qBAAqB,MAAM,IAC1C,aACE,OAAO,YAAY,CAAC,WAAW,MAAM,EACnC,QAAQ,gBACV,CAAC,CACH,IACA,KAAA;EACJ,OAAO;GACL,QAAQ;GACR,WAAW;GACX,GAAI,eAAe,KAAA,KAAa,EAAE,WAAW;EAC/C;CACF;CACA,IAAI,gBAAgB,MAAM,GAGxB,OAAO;EAAE,QAAQ;EAAQ,YAAY;CAAO;CAE9C,MAAM,IAAI,UAAU,mCAAmC;AACzD;AAEA,SAAS,iBAAiB,OAIX;CACb,MAAM,aAAyC,EAC7C,UAAU,EAAE,OAAO,MAAM,SAAS,EACpC;CACA,MAAM,WAAW,CAAC,UAAU;CAC5B,IAAI,MAAM,YAAY,MAAM,aAC1B,WAAW,gBAAgB,MAAM,YAAY,cAAc,CAAC;CAE9D,IAAI,MAAM,SAAS;EACjB,WAAW,aAAa,MAAM,QAAQ,cAAc,CAAC;EACrD,SAAS,KAAK,SAAS;CACzB;CACA,OAAO;EACL,MAAM;EACN;EACA;EACA,sBAAsB;CACxB;AACF;AAEA,SAAgB,wBACd,gBACA,aAC0B;CAC1B,MAAM,kBACJ,gBAAgB,KAAA,IAAY,OAAO,aAAa,WAAW;CAC7D,IAAI,UAAwC;CAC5C,IAAI,SAAuC;CAE3C,IAAI,mBAAmB,KAAA,
|
|
1
|
+
{"version":3,"file":"approval-schema.js","names":[],"sources":["../../../../../src/activities/chat/tools/approval-schema.ts"],"sourcesContent":["import {\n canonicalInterruptJson,\n digestInterruptJson,\n} from '../../../interrupt-serialization'\nimport { isStandardJSONSchema, isStandardSchema } from './schema-converter'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { JSONSchema, SchemaInput } from '../../../types'\nimport type { ApprovalSchemaConfig } from './tool-definition'\n\nexport interface NormalizedSchemaInput {\n source: SchemaInput\n validator?: StandardSchemaV1\n jsonSchema?: JSONSchema\n}\n\nexport interface NormalizedApprovalSchema {\n branches: {\n approve: NormalizedSchemaInput | null\n reject: NormalizedSchemaInput | null\n }\n responseSchema: JSONSchema\n responseSchemaHash: string\n approvalSchemaHash: string\n}\n\nconst jsonSchemaKeywords = new Set([\n '$schema',\n '$id',\n '$ref',\n '$defs',\n 'type',\n 'properties',\n 'required',\n 'additionalProperties',\n 'items',\n 'oneOf',\n 'anyOf',\n 'allOf',\n 'enum',\n 'const',\n 'format',\n 'minimum',\n 'maximum',\n 'minLength',\n 'maxLength',\n 'pattern',\n])\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n (Object.getPrototypeOf(value) === Object.prototype ||\n Object.getPrototypeOf(value) === null)\n )\n}\n\nfunction isRawJsonSchema(value: unknown): value is JSONSchema {\n return (\n isPlainRecord(value) &&\n Object.keys(value).some((key) => jsonSchemaKeywords.has(key))\n )\n}\n\nfunction isSchemaInput(value: unknown): value is SchemaInput {\n return (\n isStandardSchema(value) ||\n isStandardJSONSchema(value) ||\n isRawJsonSchema(value)\n )\n}\n\nfunction isApprovalBranchMap(\n value: unknown,\n): value is { approve?: SchemaInput; reject?: SchemaInput } {\n if (!isPlainRecord(value)) return false\n const keys = Object.keys(value)\n return (\n keys.length > 0 &&\n keys.every((key) => key === 'approve' || key === 'reject') &&\n keys.every((key) => isSchemaInput(value[key]))\n )\n}\n\nfunction toJsonSchema(value: Record<string, unknown>): JSONSchema {\n const result: JSONSchema = {}\n for (const [key, item] of Object.entries(value)) {\n result[key] = item\n }\n return result\n}\n\nfunction schemaToWire(schema: SchemaInput): NormalizedSchemaInput {\n if (isStandardSchema(schema)) {\n const jsonSchema = isStandardJSONSchema(schema)\n ? toJsonSchema(\n schema['~standard'].jsonSchema.input({\n target: 'draft-2020-12',\n }),\n )\n : undefined\n return {\n source: schema,\n validator: schema,\n ...(jsonSchema !== undefined && { jsonSchema }),\n }\n }\n if (isRawJsonSchema(schema)) {\n // The library does not compile or validate raw JSON Schema; it is carried\n // on the wire and hashed as-is. Validation is the application's job.\n return { source: schema, jsonSchema: schema }\n }\n throw new TypeError('Expected a supported SchemaInput.')\n}\n\nfunction decisionEnvelope(input: {\n approved: boolean\n payload: NormalizedSchemaInput | null\n inputSchema: NormalizedSchemaInput | null\n}): JSONSchema {\n const properties: Record<string, JSONSchema> = {\n approved: { const: input.approved },\n }\n const required = ['approved']\n if (input.approved && input.inputSchema) {\n properties['editedArgs'] = input.inputSchema.jsonSchema ?? {}\n }\n if (input.payload) {\n properties['payload'] = input.payload.jsonSchema ?? {}\n required.push('payload')\n }\n return {\n type: 'object',\n properties,\n required,\n additionalProperties: false,\n }\n}\n\nexport function normalizeApprovalSchema(\n approvalSchema: ApprovalSchemaConfig | undefined,\n inputSchema?: SchemaInput,\n): NormalizedApprovalSchema {\n const normalizedInput =\n inputSchema === undefined ? null : schemaToWire(inputSchema)\n let approve: NormalizedSchemaInput | null = null\n let reject: NormalizedSchemaInput | null = null\n\n if (approvalSchema !== undefined) {\n if (isStandardSchema(approvalSchema) || isRawJsonSchema(approvalSchema)) {\n approve = schemaToWire(approvalSchema)\n reject = approve\n } else if (isApprovalBranchMap(approvalSchema)) {\n approve =\n approvalSchema.approve === undefined\n ? null\n : schemaToWire(approvalSchema.approve)\n reject =\n approvalSchema.reject === undefined\n ? null\n : schemaToWire(approvalSchema.reject)\n } else {\n throw new TypeError(\n 'approvalSchema must be a SchemaInput or a nonempty map containing approve or reject.',\n )\n }\n }\n\n const responseSchema: JSONSchema = {\n oneOf: [\n decisionEnvelope({\n approved: true,\n payload: approve,\n inputSchema: normalizedInput,\n }),\n decisionEnvelope({\n approved: false,\n payload: reject,\n inputSchema: null,\n }),\n ],\n }\n const responseCanonical = canonicalInterruptJson(responseSchema)\n const approvalCanonical = canonicalInterruptJson({\n approve: approve?.jsonSchema ?? null,\n reject: reject?.jsonSchema ?? null,\n })\n\n return {\n branches: { approve, reject },\n responseSchema,\n responseSchemaHash: digestInterruptJson(responseCanonical),\n approvalSchemaHash: digestInterruptJson(approvalCanonical),\n }\n}\n\nexport function hashSchemaInput(schema: SchemaInput | undefined): string {\n if (schema === undefined) return digestInterruptJson('undefined')\n const normalized = schemaToWire(schema)\n const identity = normalized.jsonSchema ?? {\n standardValidator: 'unserialized',\n }\n return digestInterruptJson(canonicalInterruptJson(identity))\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-tool-manager.js","names":[],"sources":["../../../../../src/activities/chat/tools/lazy-tool-manager.ts"],"sourcesContent":["import { convertSchemaToJsonSchema } from './schema-converter'\nimport { renderLazyCatalogEntry } from './lazy-tools'\nimport type { AnyTool, LazyToolsConfig, Tool } from '../../../types'\n\n/**\n * Name of the synthetic tool the LLM calls to discover lazy tools.\n *\n * Exported so callers building custom message-compaction / history-trimming\n * logic can reference the discovery tool by constant instead of hard-coding\n * the string (which is an internal contract that could change).\n */\nexport const DISCOVERY_TOOL_NAME = '__lazy__tool__discovery__'\n\n/**\n * Manages lazy tool discovery for the chat agent loop.\n *\n * Lazy tools are not sent to the LLM initially. Instead, a synthetic\n * \"discovery tool\" is provided that lets the LLM discover lazy tools\n * by name, receiving their full descriptions and schemas on demand.\n */\nexport class LazyToolManager {\n private readonly eagerTools: ReadonlyArray<Tool>\n private readonly lazyToolMap: Map<string, Tool>\n private readonly discoveredTools: Set<string>\n private hasNewDiscoveries: boolean\n private readonly discoveryTool: Tool | null\n private readonly lazyToolsConfig: LazyToolsConfig\n\n constructor(\n tools: ReadonlyArray<Tool>,\n messages: ReadonlyArray<{\n role: string\n content?: any\n toolCalls?: Array<{\n id: string\n type: string\n function: { name: string; arguments: string }\n }>\n toolCallId?: string\n }>,\n lazyToolsConfig: LazyToolsConfig = {},\n ) {\n this.lazyToolsConfig = lazyToolsConfig\n const eager: Array<Tool> = []\n this.lazyToolMap = new Map()\n this.discoveredTools = new Set()\n this.hasNewDiscoveries = false\n\n // Separate tools into eager and lazy\n for (const tool of tools) {\n if (tool.lazy) {\n this.lazyToolMap.set(tool.name, tool)\n } else {\n eager.push(tool)\n }\n }\n this.eagerTools = eager\n\n // If no lazy tools, no discovery tool needed\n if (this.lazyToolMap.size === 0) {\n this.discoveryTool = null\n return\n }\n\n // Scan message history to pre-populate discoveredTools\n this.scanMessageHistory(messages)\n\n // Create the synthetic discovery tool\n this.discoveryTool = this.createDiscoveryTool()\n }\n\n /**\n * Returns the set of tools that should be sent to the LLM:\n * eager tools + discovered lazy tools + discovery tool (if undiscovered tools remain).\n * Resets the hasNewDiscoveries flag.\n */\n getActiveTools(): Array<Tool> {\n this.hasNewDiscoveries = false\n\n const active: Array<Tool> = [...this.eagerTools]\n\n // Add discovered lazy tools\n for (const name of this.discoveredTools) {\n const tool = this.lazyToolMap.get(name)\n if (tool) {\n active.push(tool)\n }\n }\n\n // Add discovery tool if there are still undiscovered lazy tools\n if (\n this.discoveryTool &&\n this.discoveredTools.size < this.lazyToolMap.size\n ) {\n active.push(this.discoveryTool)\n }\n\n return active\n }\n\n /**\n * Returns the tools that should be available for *execution* this turn.\n *\n * This is the advertised set (`getActiveTools()`, passed in as `activeTools`)\n * plus the discovery tool when a pending call references it but it is no\n * longer advertised. Once every lazy tool has been discovered the discovery\n * tool is dropped from the advertised set, but a model may still re-request\n * discovery (long context / hallucination); keeping it executable lets that\n * call return the schemas again instead of failing with \"Unknown tool\".\n *\n * The advertised set is intentionally left unchanged — only execution lookup\n * is widened. Operates on the already-built `activeTools`: it must NOT call\n * `getActiveTools()`, which would reset `hasNewDiscoveries` before the\n * post-execution refresh check in the agent loop.\n */\n getExecutableTools(\n activeTools: ReadonlyArray<AnyTool>,\n pendingToolCallNames: ReadonlyArray<string>,\n ): ReadonlyArray<AnyTool> {\n if (\n this.discoveryTool &&\n pendingToolCallNames.includes(DISCOVERY_TOOL_NAME) &&\n !activeTools.some((t) => t.name === DISCOVERY_TOOL_NAME)\n ) {\n return [...activeTools, this.discoveryTool]\n }\n return activeTools\n }\n\n /**\n * Returns whether new tools have been discovered since the last getActiveTools() call.\n */\n hasNewlyDiscoveredTools(): boolean {\n return this.hasNewDiscoveries\n }\n\n /**\n * Returns true if the given name is a lazy tool that has not yet been discovered.\n */\n isUndiscoveredLazyTool(name: string): boolean {\n return this.lazyToolMap.has(name) && !this.discoveredTools.has(name)\n }\n\n /**\n * Returns a helpful error message for when an undiscovered lazy tool is called.\n */\n getUndiscoveredToolError(name: string): string {\n return `Error: Tool '${name}' must be discovered first. Call ${DISCOVERY_TOOL_NAME} with toolNames: ['${name}'] to discover it.`\n }\n\n /**\n * Scans message history to find previously discovered lazy tools.\n * Looks for assistant messages with discovery tool calls and their\n * corresponding tool result messages.\n */\n private scanMessageHistory(\n messages: ReadonlyArray<{\n role: string\n content?: any\n toolCalls?: Array<{\n id: string\n type: string\n function: { name: string; arguments: string }\n }>\n toolCallId?: string\n }>,\n ): void {\n // Collect tool call IDs for discovery tool invocations\n const discoveryCallIds = new Set<string>()\n\n for (const msg of messages) {\n if (msg.role === 'assistant' && msg.toolCalls) {\n for (const tc of msg.toolCalls) {\n if (tc.function.name === DISCOVERY_TOOL_NAME) {\n discoveryCallIds.add(tc.id)\n }\n }\n }\n }\n\n if (discoveryCallIds.size === 0) return\n\n // Find corresponding tool result messages\n for (const msg of messages) {\n if (\n msg.role === 'tool' &&\n msg.toolCallId &&\n discoveryCallIds.has(msg.toolCallId)\n ) {\n try {\n const content =\n typeof msg.content === 'string'\n ? msg.content\n : JSON.stringify(msg.content)\n const parsed = JSON.parse(content)\n if (parsed && Array.isArray(parsed.tools)) {\n for (const tool of parsed.tools) {\n if (\n tool &&\n typeof tool.name === 'string' &&\n this.lazyToolMap.has(tool.name)\n ) {\n this.discoveredTools.add(tool.name)\n }\n }\n }\n } catch {\n // Malformed JSON — skip gracefully\n }\n }\n }\n }\n\n /**\n * Creates the synthetic discovery tool that the LLM can call\n * to discover lazy tools' descriptions and schemas.\n */\n private createDiscoveryTool(): Tool {\n const undiscoveredNames = (): Array<string> => {\n const names: Array<string> = []\n for (const [name] of this.lazyToolMap) {\n if (!this.discoveredTools.has(name)) {\n names.push(name)\n }\n }\n return names\n }\n\n const lazyToolMap = this.lazyToolMap\n\n // Build the static description, rendering each entry per includeDescription.\n // With the default 'none' this is byte-identical to the legacy output.\n const include = this.lazyToolsConfig.includeDescription ?? 'none'\n const allLazyEntries = Array.from(this.lazyToolMap.values()).map((t) =>\n renderLazyCatalogEntry(t.name, t.description, include),\n )\n const description = `You have access to additional tools that can be discovered. Available tools: [${allLazyEntries.join(', ')}]. Call this tool with a list of tool names to discover their full descriptions and argument schemas before using them.`\n\n // Use the arrow function to capture `this` context\n const manager = this\n\n return {\n name: DISCOVERY_TOOL_NAME,\n description,\n inputSchema: {\n type: 'object',\n properties: {\n toolNames: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'List of tool names to discover. Each name must match one of the available tools.',\n },\n },\n required: ['toolNames'],\n },\n execute: (args: { toolNames: Array<string> }) => {\n const tools: Array<{\n name: string\n description: string\n inputSchema?: any\n }> = []\n const errors: Array<string> = []\n\n for (const name of args.toolNames) {\n const tool = lazyToolMap.get(name)\n if (tool) {\n // Only flag a refresh for genuinely new discoveries. Re-requesting\n // an already-discovered tool still returns its schema below (the\n // model asked for it), but must not trigger a redundant tool-list\n // refresh + continue in the agent loop.\n if (!manager.discoveredTools.has(name)) {\n manager.discoveredTools.add(name)\n manager.hasNewDiscoveries = true\n }\n const jsonSchema = tool.inputSchema\n ? convertSchemaToJsonSchema(tool.inputSchema)\n : undefined\n tools.push({\n name: tool.name,\n description: tool.description,\n ...(jsonSchema ? { inputSchema: jsonSchema } : {}),\n })\n } else {\n errors.push(\n `Unknown tool: '${name}'. Available tools: [${undiscoveredNames().join(', ')}]`,\n )\n }\n }\n\n const result: {\n tools: typeof tools\n errors?: Array<string>\n } = { tools }\n\n if (errors.length > 0) {\n result.errors = errors\n }\n\n return result\n },\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lazy-tool-manager.js","names":[],"sources":["../../../../../src/activities/chat/tools/lazy-tool-manager.ts"],"sourcesContent":["import { convertSchemaToJsonSchema } from './schema-converter'\nimport { renderLazyCatalogEntry } from './lazy-tools'\nimport type { AnyTool, LazyToolsConfig, Tool } from '../../../types'\n\n/**\n * Name of the synthetic tool the LLM calls to discover lazy tools.\n *\n * Exported so callers building custom message-compaction / history-trimming\n * logic can reference the discovery tool by constant instead of hard-coding\n * the string (which is an internal contract that could change).\n */\nexport const DISCOVERY_TOOL_NAME = '__lazy__tool__discovery__'\n\n/**\n * Manages lazy tool discovery for the chat agent loop.\n *\n * Lazy tools are not sent to the LLM initially. Instead, a synthetic\n * \"discovery tool\" is provided that lets the LLM discover lazy tools\n * by name, receiving their full descriptions and schemas on demand.\n */\nexport class LazyToolManager {\n private readonly eagerTools: ReadonlyArray<Tool>\n private readonly lazyToolMap: Map<string, Tool>\n private readonly discoveredTools: Set<string>\n private hasNewDiscoveries: boolean\n private readonly discoveryTool: Tool | null\n private readonly lazyToolsConfig: LazyToolsConfig\n\n constructor(\n tools: ReadonlyArray<Tool>,\n messages: ReadonlyArray<{\n role: string\n content?: any\n toolCalls?: Array<{\n id: string\n type: string\n function: { name: string; arguments: string }\n }>\n toolCallId?: string\n }>,\n lazyToolsConfig: LazyToolsConfig = {},\n ) {\n this.lazyToolsConfig = lazyToolsConfig\n const eager: Array<Tool> = []\n this.lazyToolMap = new Map()\n this.discoveredTools = new Set()\n this.hasNewDiscoveries = false\n\n // Separate tools into eager and lazy\n for (const tool of tools) {\n if (tool.lazy) {\n this.lazyToolMap.set(tool.name, tool)\n } else {\n eager.push(tool)\n }\n }\n this.eagerTools = eager\n\n // If no lazy tools, no discovery tool needed\n if (this.lazyToolMap.size === 0) {\n this.discoveryTool = null\n return\n }\n\n // Scan message history to pre-populate discoveredTools\n this.scanMessageHistory(messages)\n\n // Create the synthetic discovery tool\n this.discoveryTool = this.createDiscoveryTool()\n }\n\n /**\n * Returns the set of tools that should be sent to the LLM:\n * eager tools + discovered lazy tools + discovery tool (if undiscovered tools remain).\n * Resets the hasNewDiscoveries flag.\n */\n getActiveTools(): Array<Tool> {\n this.hasNewDiscoveries = false\n\n const active: Array<Tool> = [...this.eagerTools]\n\n // Add discovered lazy tools\n for (const name of this.discoveredTools) {\n const tool = this.lazyToolMap.get(name)\n if (tool) {\n active.push(tool)\n }\n }\n\n // Add discovery tool if there are still undiscovered lazy tools\n if (\n this.discoveryTool &&\n this.discoveredTools.size < this.lazyToolMap.size\n ) {\n active.push(this.discoveryTool)\n }\n\n return active\n }\n\n /**\n * Returns the tools that should be available for *execution* this turn.\n *\n * This is the advertised set (`getActiveTools()`, passed in as `activeTools`)\n * plus the discovery tool when a pending call references it but it is no\n * longer advertised. Once every lazy tool has been discovered the discovery\n * tool is dropped from the advertised set, but a model may still re-request\n * discovery (long context / hallucination); keeping it executable lets that\n * call return the schemas again instead of failing with \"Unknown tool\".\n *\n * The advertised set is intentionally left unchanged — only execution lookup\n * is widened. Operates on the already-built `activeTools`: it must NOT call\n * `getActiveTools()`, which would reset `hasNewDiscoveries` before the\n * post-execution refresh check in the agent loop.\n */\n getExecutableTools(\n activeTools: ReadonlyArray<AnyTool>,\n pendingToolCallNames: ReadonlyArray<string>,\n ): ReadonlyArray<AnyTool> {\n if (\n this.discoveryTool &&\n pendingToolCallNames.includes(DISCOVERY_TOOL_NAME) &&\n !activeTools.some((t) => t.name === DISCOVERY_TOOL_NAME)\n ) {\n return [...activeTools, this.discoveryTool]\n }\n return activeTools\n }\n\n /**\n * Returns whether new tools have been discovered since the last getActiveTools() call.\n */\n hasNewlyDiscoveredTools(): boolean {\n return this.hasNewDiscoveries\n }\n\n /**\n * Returns true if the given name is a lazy tool that has not yet been discovered.\n */\n isUndiscoveredLazyTool(name: string): boolean {\n return this.lazyToolMap.has(name) && !this.discoveredTools.has(name)\n }\n\n /**\n * Returns a helpful error message for when an undiscovered lazy tool is called.\n */\n getUndiscoveredToolError(name: string): string {\n return `Error: Tool '${name}' must be discovered first. Call ${DISCOVERY_TOOL_NAME} with toolNames: ['${name}'] to discover it.`\n }\n\n /**\n * Scans message history to find previously discovered lazy tools.\n * Looks for assistant messages with discovery tool calls and their\n * corresponding tool result messages.\n */\n private scanMessageHistory(\n messages: ReadonlyArray<{\n role: string\n content?: any\n toolCalls?: Array<{\n id: string\n type: string\n function: { name: string; arguments: string }\n }>\n toolCallId?: string\n }>,\n ): void {\n // Collect tool call IDs for discovery tool invocations\n const discoveryCallIds = new Set<string>()\n\n for (const msg of messages) {\n if (msg.role === 'assistant' && msg.toolCalls) {\n for (const tc of msg.toolCalls) {\n if (tc.function.name === DISCOVERY_TOOL_NAME) {\n discoveryCallIds.add(tc.id)\n }\n }\n }\n }\n\n if (discoveryCallIds.size === 0) return\n\n // Find corresponding tool result messages\n for (const msg of messages) {\n if (\n msg.role === 'tool' &&\n msg.toolCallId &&\n discoveryCallIds.has(msg.toolCallId)\n ) {\n try {\n const content =\n typeof msg.content === 'string'\n ? msg.content\n : JSON.stringify(msg.content)\n const parsed = JSON.parse(content)\n if (parsed && Array.isArray(parsed.tools)) {\n for (const tool of parsed.tools) {\n if (\n tool &&\n typeof tool.name === 'string' &&\n this.lazyToolMap.has(tool.name)\n ) {\n this.discoveredTools.add(tool.name)\n }\n }\n }\n } catch {\n // Malformed JSON — skip gracefully\n }\n }\n }\n }\n\n /**\n * Creates the synthetic discovery tool that the LLM can call\n * to discover lazy tools' descriptions and schemas.\n */\n private createDiscoveryTool(): Tool {\n const undiscoveredNames = (): Array<string> => {\n const names: Array<string> = []\n for (const [name] of this.lazyToolMap) {\n if (!this.discoveredTools.has(name)) {\n names.push(name)\n }\n }\n return names\n }\n\n const lazyToolMap = this.lazyToolMap\n\n // Build the static description, rendering each entry per includeDescription.\n // With the default 'none' this is byte-identical to the legacy output.\n const include = this.lazyToolsConfig.includeDescription ?? 'none'\n const allLazyEntries = Array.from(this.lazyToolMap.values()).map((t) =>\n renderLazyCatalogEntry(t.name, t.description, include),\n )\n const description = `You have access to additional tools that can be discovered. Available tools: [${allLazyEntries.join(', ')}]. Call this tool with a list of tool names to discover their full descriptions and argument schemas before using them.`\n\n // Use the arrow function to capture `this` context\n const manager = this\n\n return {\n name: DISCOVERY_TOOL_NAME,\n description,\n inputSchema: {\n type: 'object',\n properties: {\n toolNames: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'List of tool names to discover. Each name must match one of the available tools.',\n },\n },\n required: ['toolNames'],\n },\n execute: (args: { toolNames: Array<string> }) => {\n const tools: Array<{\n name: string\n description: string\n inputSchema?: any\n }> = []\n const errors: Array<string> = []\n\n for (const name of args.toolNames) {\n const tool = lazyToolMap.get(name)\n if (tool) {\n // Only flag a refresh for genuinely new discoveries. Re-requesting\n // an already-discovered tool still returns its schema below (the\n // model asked for it), but must not trigger a redundant tool-list\n // refresh + continue in the agent loop.\n if (!manager.discoveredTools.has(name)) {\n manager.discoveredTools.add(name)\n manager.hasNewDiscoveries = true\n }\n const jsonSchema = tool.inputSchema\n ? convertSchemaToJsonSchema(tool.inputSchema)\n : undefined\n tools.push({\n name: tool.name,\n description: tool.description,\n ...(jsonSchema ? { inputSchema: jsonSchema } : {}),\n })\n } else {\n errors.push(\n `Unknown tool: '${name}'. Available tools: [${undiscoveredNames().join(', ')}]`,\n )\n }\n }\n\n const result: {\n tools: typeof tools\n errors?: Array<string>\n } = { tools }\n\n if (errors.length > 0) {\n result.errors = errors\n }\n\n return result\n },\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,IAAa,sBAAsB;;;;;;;;AASnC,IAAa,kBAAb,MAA6B;CAC3B;CACA;CACA;CACA;CACA;CACA;CAEA,YACE,OACA,UAUA,kBAAmC,CAAC,GACpC;EACA,KAAK,kBAAkB;EACvB,MAAM,QAAqB,CAAC;EAC5B,KAAK,8BAAc,IAAI,IAAI;EAC3B,KAAK,kCAAkB,IAAI,IAAI;EAC/B,KAAK,oBAAoB;EAGzB,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,MACP,KAAK,YAAY,IAAI,KAAK,MAAM,IAAI;OAEpC,MAAM,KAAK,IAAI;EAGnB,KAAK,aAAa;EAGlB,IAAI,KAAK,YAAY,SAAS,GAAG;GAC/B,KAAK,gBAAgB;GACrB;EACF;EAGA,KAAK,mBAAmB,QAAQ;EAGhC,KAAK,gBAAgB,KAAK,oBAAoB;CAChD;;;;;;CAOA,iBAA8B;EAC5B,KAAK,oBAAoB;EAEzB,MAAM,SAAsB,CAAC,GAAG,KAAK,UAAU;EAG/C,KAAK,MAAM,QAAQ,KAAK,iBAAiB;GACvC,MAAM,OAAO,KAAK,YAAY,IAAI,IAAI;GACtC,IAAI,MACF,OAAO,KAAK,IAAI;EAEpB;EAGA,IACE,KAAK,iBACL,KAAK,gBAAgB,OAAO,KAAK,YAAY,MAE7C,OAAO,KAAK,KAAK,aAAa;EAGhC,OAAO;CACT;;;;;;;;;;;;;;;;CAiBA,mBACE,aACA,sBACwB;EACxB,IACE,KAAK,iBACL,qBAAqB,SAAA,2BAA4B,KACjD,CAAC,YAAY,MAAM,MAAM,EAAE,SAAA,2BAA4B,GAEvD,OAAO,CAAC,GAAG,aAAa,KAAK,aAAa;EAE5C,OAAO;CACT;;;;CAKA,0BAAmC;EACjC,OAAO,KAAK;CACd;;;;CAKA,uBAAuB,MAAuB;EAC5C,OAAO,KAAK,YAAY,IAAI,IAAI,KAAK,CAAC,KAAK,gBAAgB,IAAI,IAAI;CACrE;;;;CAKA,yBAAyB,MAAsB;EAC7C,OAAO,gBAAgB,KAAK,mCAAmC,oBAAoB,qBAAqB,KAAK;CAC/G;;;;;;CAOA,mBACE,UAUM;EAEN,MAAM,mCAAmB,IAAI,IAAY;EAEzC,KAAK,MAAM,OAAO,UAChB,IAAI,IAAI,SAAS,eAAe,IAAI,WAC7B;QAAA,MAAM,MAAM,IAAI,WACnB,IAAI,GAAG,SAAS,SAAA,6BACd,iBAAiB,IAAI,GAAG,EAAE;EAAA;EAMlC,IAAI,iBAAiB,SAAS,GAAG;EAGjC,KAAK,MAAM,OAAO,UAChB,IACE,IAAI,SAAS,UACb,IAAI,cACJ,iBAAiB,IAAI,IAAI,UAAU,GAEnC,IAAI;GACF,MAAM,UACJ,OAAO,IAAI,YAAY,WACnB,IAAI,UACJ,KAAK,UAAU,IAAI,OAAO;GAChC,MAAM,SAAS,KAAK,MAAM,OAAO;GACjC,IAAI,UAAU,MAAM,QAAQ,OAAO,KAAK,GACjC;SAAA,MAAM,QAAQ,OAAO,OACxB,IACE,QACA,OAAO,KAAK,SAAS,YACrB,KAAK,YAAY,IAAI,KAAK,IAAI,GAE9B,KAAK,gBAAgB,IAAI,KAAK,IAAI;GAAA;EAI1C,QAAQ,CAER;CAGN;;;;;CAMA,sBAAoC;EAClC,MAAM,0BAAyC;GAC7C,MAAM,QAAuB,CAAC;GAC9B,KAAK,MAAM,CAAC,SAAS,KAAK,aACxB,IAAI,CAAC,KAAK,gBAAgB,IAAI,IAAI,GAChC,MAAM,KAAK,IAAI;GAGnB,OAAO;EACT;EAEA,MAAM,cAAc,KAAK;EAIzB,MAAM,UAAU,KAAK,gBAAgB,sBAAsB;EAI3D,MAAM,cAAc,iFAHG,MAAM,KAAK,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,MAChE,uBAAuB,EAAE,MAAM,EAAE,aAAa,OAAO,CAE8C,CAAA,CAAe,KAAK,IAAI,EAAE;EAG/H,MAAM,UAAU;EAEhB,OAAO;GACL,MAAM;GACN;GACA,aAAa;IACX,MAAM;IACN,YAAY,EACV,WAAW;KACT,MAAM;KACN,OAAO,EAAE,MAAM,SAAS;KACxB,aACE;IACJ,EACF;IACA,UAAU,CAAC,WAAW;GACxB;GACA,UAAU,SAAuC;IAC/C,MAAM,QAID,CAAC;IACN,MAAM,SAAwB,CAAC;IAE/B,KAAK,MAAM,QAAQ,KAAK,WAAW;KACjC,MAAM,OAAO,YAAY,IAAI,IAAI;KACjC,IAAI,MAAM;MAKR,IAAI,CAAC,QAAQ,gBAAgB,IAAI,IAAI,GAAG;OACtC,QAAQ,gBAAgB,IAAI,IAAI;OAChC,QAAQ,oBAAoB;MAC9B;MACA,MAAM,aAAa,KAAK,cACpB,0BAA0B,KAAK,WAAW,IAC1C,KAAA;MACJ,MAAM,KAAK;OACT,MAAM,KAAK;OACX,aAAa,KAAK;OAClB,GAAI,aAAa,EAAE,aAAa,WAAW,IAAI,CAAC;MAClD,CAAC;KACH,OACE,OAAO,KACL,kBAAkB,KAAK,uBAAuB,kBAAkB,CAAC,CAAC,KAAK,IAAI,EAAE,EAC/E;IAEJ;IAEA,MAAM,SAGF,EAAE,MAAM;IAEZ,IAAI,OAAO,SAAS,GAClB,OAAO,SAAS;IAGlB,OAAO;GACT;EACF;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-converter.js","names":[],"sources":["../../../../../src/activities/chat/tools/schema-converter.ts"],"sourcesContent":["import type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from '@standard-schema/spec'\nimport type { NullWideningMap } from '@tanstack/ai-utils'\nimport type { JSONSchema, SchemaInput } from '../../../types'\n\n/**\n * Build a JSONSchema object from any plain key/value source. The `JSONSchema`\n * interface's `[key: string]: any` index signature makes every property\n * assignable through bracket access without a type cast — copying keys here\n * lets us narrow either `Record<string, unknown>` (returned by\n * `~standard.jsonSchema.input()`) or a `JSONSchema` (from the SchemaInput\n * pass-through arm) into the typed view used by the rest of this module.\n *\n * Accepts `object` so callers don't need a cast when narrowing from union\n * types like `SchemaInput`.\n */\nfunction toJsonSchema(obj: object): JSONSchema {\n const result: JSONSchema = {}\n for (const [key, value] of Object.entries(obj)) {\n if (key === '$schema') continue // not needed by LLM providers\n result[key] = value\n }\n return result\n}\n\n/**\n * Whether a value can carry a `~standard` property. Most schema libraries\n * (Zod, Valibot) return plain objects, but ArkType's `type()` returns a\n * *callable function* with `~standard` attached — so `typeof` must accept\n * both `'object'` and `'function'` or ArkType schemas are missed entirely\n * (issue #276).\n */\nfunction isPropertyCarrier(schema: unknown): schema is Record<string, unknown> {\n return (\n (typeof schema === 'object' || typeof schema === 'function') &&\n schema !== null\n )\n}\n\n/**\n * Check if a value is a Standard JSON Schema compliant schema.\n * Standard JSON Schema compliant libraries (Zod v4+, ArkType, Valibot with toStandardJsonSchema, etc.)\n * implement the '~standard' property with jsonSchema converter methods.\n */\nexport function isStandardJSONSchema(\n schema: unknown,\n): schema is StandardJSONSchemaV1 {\n if (!isPropertyCarrier(schema) || !('~standard' in schema)) return false\n\n const standard = schema['~standard']\n if (\n typeof standard !== 'object' ||\n standard === null ||\n !('version' in standard) ||\n standard.version !== 1 ||\n !('jsonSchema' in standard) ||\n typeof standard.jsonSchema !== 'object' ||\n standard.jsonSchema === null ||\n !('input' in standard.jsonSchema)\n ) {\n return false\n }\n\n return typeof standard.jsonSchema.input === 'function'\n}\n\n/**\n * Check if a value is a Standard Schema compliant schema (for validation).\n * Standard Schema compliant libraries implement the '~standard' property with a validate function.\n */\nexport function isStandardSchema(schema: unknown): schema is StandardSchemaV1 {\n return (\n isPropertyCarrier(schema) &&\n '~standard' in schema &&\n typeof schema['~standard'] === 'object' &&\n schema['~standard'] !== null &&\n 'version' in schema['~standard'] &&\n schema['~standard'].version === 1 &&\n 'validate' in schema['~standard'] &&\n typeof schema['~standard'].validate === 'function'\n )\n}\n\n/**\n * Result of {@link makeStructuredOutputCompatible}: the strict-ready schema plus\n * a {@link NullWideningMap} recording every position where a `null` was\n * synthesized, so the response can be un-widened before validation without\n * re-deriving (or guessing) which nulls were synthetic.\n */\ninterface StructuredOutputConversion {\n schema: JSONSchema\n nullWidening: NullWideningMap | undefined\n}\n\n/** Drop an empty map to `undefined` so leaf/no-op subtrees don't litter it. */\nfunction pruneMap(map: NullWideningMap): NullWideningMap | undefined {\n return Object.keys(map).length > 0 ? map : undefined\n}\n\n/**\n * Transform a JSON schema to be compatible with OpenAI's structured output requirements.\n * OpenAI requires:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for objects\n *\n * Alongside the transformed schema it returns a {@link NullWideningMap} marking\n * exactly the positions where `null` was added, so `undoNullWidening` can strip\n * those synthesized nulls (and only those) from the provider's response.\n *\n * @param schema - JSON schema to transform\n * @param originalRequired - Original required array (to know which fields were optional)\n * @returns Transformed schema + the null-widening map for the round trip\n */\nfunction makeStructuredOutputCompatible(\n schema: JSONSchema,\n originalRequired: Array<string> = [],\n): StructuredOutputConversion {\n const result: JSONSchema = { ...schema }\n const map: NullWideningMap = {}\n\n // Handle object types\n if (result.type === 'object' && result.properties) {\n const properties: Record<string, JSONSchema> = { ...result.properties }\n const allPropertyNames = Object.keys(properties)\n const propertyMaps: Record<string, NullWideningMap> = {}\n\n // Transform each property\n for (const propName of allPropertyNames) {\n const prop = properties[propName]\n if (!prop) continue\n const wasOptional = !originalRequired.includes(propName)\n // `null` synthesized AT this property (the field itself can come back null).\n let widenedHere = false\n // Map describing widened positions INSIDE this property.\n let childMap: NullWideningMap | undefined\n\n // Recursively transform nested objects/arrays\n if (prop.type === 'object' && prop.properties) {\n const nested = makeStructuredOutputCompatible(prop, prop.required || [])\n properties[propName] = wasOptional\n ? { ...nested.schema, type: ['object', 'null'] }\n : nested.schema\n widenedHere = wasOptional\n childMap = nested.nullWidening\n } else if (prop.type === 'array' && prop.items) {\n const items = Array.isArray(prop.items) ? prop.items[0] : prop.items\n const nestedItems = items\n ? makeStructuredOutputCompatible(items, items.required || [])\n : undefined\n properties[propName] = {\n ...prop,\n items: nestedItems ? nestedItems.schema : prop.items,\n ...(wasOptional ? { type: ['array', 'null'] } : {}),\n }\n widenedHere = wasOptional\n childMap = nestedItems?.nullWidening\n ? { items: nestedItems.nullWidening }\n : undefined\n } else if (wasOptional) {\n // Make optional fields nullable by adding null to the type. Mark\n // `widenedHere` only where we actually add `null`; a field already\n // typed nullable (`.nullish()`) is left as-is and keeps its null.\n if (prop.type && !Array.isArray(prop.type)) {\n properties[propName] = { ...prop, type: [prop.type, 'null'] }\n widenedHere = true\n } else if (Array.isArray(prop.type) && !prop.type.includes('null')) {\n properties[propName] = { ...prop, type: [...prop.type, 'null'] }\n widenedHere = true\n }\n }\n\n if (widenedHere || childMap) {\n propertyMaps[propName] = {\n ...(childMap ?? {}),\n ...(widenedHere ? { widened: true } : {}),\n }\n }\n }\n\n result.properties = properties\n // ALL properties must be required for OpenAI structured output\n result.required = allPropertyNames\n // additionalProperties must be false\n result.additionalProperties = false\n if (Object.keys(propertyMaps).length > 0) map.properties = propertyMaps\n }\n\n // Handle array types with object items\n if (result.type === 'array' && result.items) {\n const items = Array.isArray(result.items) ? result.items[0] : result.items\n if (items) {\n const nestedItems = makeStructuredOutputCompatible(\n items,\n items.required || [],\n )\n result.items = nestedItems.schema\n if (nestedItems.nullWidening) map.items = nestedItems.nullWidening\n }\n }\n\n return { schema: result, nullWidening: pruneMap(map) }\n}\n\n/**\n * Options for schema conversion\n */\nexport interface ConvertSchemaOptions {\n /**\n * When true, transforms the schema to be compatible with OpenAI's structured output requirements:\n * - All properties are added to the `required` array\n * - Optional fields get null added to their type union\n * - additionalProperties is set to false for all objects\n *\n * @default false\n */\n forStructuredOutput?: boolean\n}\n\n/**\n * Normalize any supported schema input to a typed, UN-widened `JSONSchema` —\n * the shared first half of conversion, before any structured-output widening.\n *\n * - Standard JSON Schemas are rebuilt structurally (dropping `$schema`, which\n * LLM providers ignore) and given the explicit `type`/`properties`/`required`\n * defaults object shapes need downstream.\n * - Plain `JSONSchema` inputs are rebuilt into the typed view; non-object inputs\n * are surfaced untouched (they can't be widened).\n * - Standard Schema validators lacking a `~standard.jsonSchema` converter throw\n * with actionable guidance, rather than shipping `{ '~standard': … }` to the\n * provider and producing an opaque downstream error.\n */\nfunction toTypedJsonSchema(schema: SchemaInput): JSONSchema | undefined {\n if (isStandardJSONSchema(schema)) {\n const jsonSchema = schema['~standard'].jsonSchema.input({\n target: 'draft-07',\n })\n const result: JSONSchema = toJsonSchema(jsonSchema)\n if ('properties' in result && !result.type) result.type = 'object'\n if (result.type === 'object' && !('properties' in result)) {\n result.properties = {}\n }\n if (result.type === 'object' && !('required' in result)) {\n result.required = []\n }\n return result\n }\n\n if (isStandardSchema(schema)) {\n throw new Error(\n 'Schema is a Standard Schema validator but does not expose a JSON Schema ' +\n 'converter on `~standard.jsonSchema`. Use Zod v4.2+, ArkType v2.1.28+, ' +\n 'or wrap a Valibot schema with `toStandardJsonSchema()` from ' +\n '`@valibot/to-json-schema` before passing it as `outputSchema`.',\n )\n }\n\n if (typeof schema !== 'object') return schema\n return toJsonSchema(schema)\n}\n\n/**\n * Converts a Standard JSON Schema compliant schema or plain JSONSchema to JSON Schema format\n * compatible with LLM providers.\n *\n * Supports any schema library that implements the Standard JSON Schema spec (v1):\n * - Zod v4+ (natively supports StandardJSONSchemaV1)\n * - ArkType (natively supports StandardJSONSchemaV1)\n * - Valibot (via `toStandardJsonSchema()` from `@valibot/to-json-schema`)\n *\n * If the input is already a plain JSONSchema object, it is returned as-is.\n *\n * @param schema - Standard JSON Schema compliant schema or plain JSONSchema object to convert\n * @param options - Conversion options\n * @returns JSON Schema object that can be sent to LLM providers\n *\n * @example\n * ```typescript\n * // Using Zod v4+ (natively supports Standard JSON Schema)\n * import * as z from 'zod';\n *\n * const zodSchema = z.object({\n * location: z.string().describe('City name'),\n * unit: z.enum(['celsius', 'fahrenheit']).optional()\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(zodSchema);\n *\n * @example\n * // Using ArkType (natively supports Standard JSON Schema)\n * import { type } from 'arktype';\n *\n * const arkSchema = type({\n * location: 'string',\n * unit: \"'celsius' | 'fahrenheit'\"\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(arkSchema);\n *\n * @example\n * // Using Valibot (via toStandardJsonSchema)\n * import * as v from 'valibot';\n * import { toStandardJsonSchema } from '@valibot/to-json-schema';\n *\n * const valibotSchema = toStandardJsonSchema(v.object({\n * location: v.string(),\n * unit: v.optional(v.picklist(['celsius', 'fahrenheit']))\n * }));\n *\n * const jsonSchema = convertSchemaToJsonSchema(valibotSchema);\n *\n * @example\n * // Using JSONSchema directly (passes through unchanged)\n * const rawSchema = {\n * type: 'object',\n * properties: { location: { type: 'string' } },\n * required: ['location']\n * };\n * const result = convertSchemaToJsonSchema(rawSchema);\n * ```\n */\nexport function convertSchemaToJsonSchema(\n schema: SchemaInput | undefined,\n options: ConvertSchemaOptions = {},\n): JSONSchema | undefined {\n if (!schema) return undefined\n\n const { forStructuredOutput = false } = options\n\n // Plain-JSONSchema passthrough: with no widening requested, return the schema\n // by reference so callers comparing via `===` keep identity. Only the widening\n // path needs the rebuilt, normalized view from `toTypedJsonSchema`.\n if (\n !forStructuredOutput &&\n !isStandardJSONSchema(schema) &&\n !isStandardSchema(schema)\n ) {\n return schema\n }\n\n const base = toTypedJsonSchema(schema)\n // Non-object inputs can't be widened; surface them untouched.\n if (!base || typeof base !== 'object') return base\n if (!forStructuredOutput) return base\n return makeStructuredOutputCompatible(base, base.required || []).schema\n}\n\n/**\n * Convert a schema for structured output AND capture the {@link NullWideningMap}\n * recording every `null` the strict-mode widening synthesized. The map lets the\n * caller undo that widening on the provider's response (via `undoNullWidening`)\n * before validating against the original schema — optional fields read back as\n * absent while genuine `.nullable()` nulls survive. The map is `undefined` when\n * the schema isn't a widenable object or when no field needed widening.\n */\nexport function convertSchemaForStructuredOutput(\n schema: SchemaInput | undefined,\n): {\n jsonSchema: JSONSchema | undefined\n nullWideningMap: NullWideningMap | undefined\n} {\n if (!schema) return { jsonSchema: undefined, nullWideningMap: undefined }\n const base = toTypedJsonSchema(schema)\n if (!base || typeof base !== 'object') {\n return { jsonSchema: base, nullWideningMap: undefined }\n }\n const { schema: jsonSchema, nullWidening } = makeStructuredOutputCompatible(\n base,\n base.required || [],\n )\n return { jsonSchema, nullWideningMap: nullWidening }\n}\n\n/**\n * Validates data against a Standard Schema compliant schema.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Validation result with success status, data or issues\n */\nexport async function validateWithStandardSchema<T>(\n schema: unknown,\n data: unknown,\n): Promise<\n | { success: true; data: T }\n | {\n success: false\n issues: Array<{ message: string; path?: Array<string> | undefined }>\n }\n> {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return { success: true, data: data as T }\n }\n\n const result = await schema['~standard'].validate(data)\n\n if (!result.issues) {\n return { success: true, data: result.value as T }\n }\n\n return {\n success: false,\n issues: result.issues.map((issue) => ({\n message: issue.message || 'Validation failed',\n path: issue.path?.map(String),\n })),\n }\n}\n\n/**\n * Error thrown when Standard Schema validation fails. Carries the original\n * `issues` array so consumers (middleware `onError`, callers catching from\n * `chat({ outputSchema })`) can programmatically inspect each failure.\n */\nexport class StandardSchemaValidationError extends Error {\n override readonly name = 'StandardSchemaValidationError'\n readonly issues: ReadonlyArray<StandardSchemaV1.Issue>\n\n constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n super(\n `Validation failed: ${issues\n .map((i) => i.message || 'Validation failed')\n .join(', ')}`,\n )\n this.issues = issues\n }\n}\n\n/**\n * Synchronously validates data against a Standard Schema compliant schema.\n * Note: Some Standard Schema implementations may only support async validation.\n * In those cases, this function will throw.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Parsed/validated data\n * @throws StandardSchemaValidationError if validation fails; Error if the\n * schema only supports async validation.\n */\nexport function parseWithStandardSchema<T>(schema: unknown, data: unknown): T {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return data as T\n }\n\n const result = schema['~standard'].validate(data)\n\n // Handle async result (Promise)\n if (result instanceof Promise) {\n throw new Error(\n 'Schema validation returned a Promise. Use validateWithStandardSchema for async validation.',\n )\n }\n // Standard Schema validation returns { value } for success or { issues } for failure\n if (!result.issues) {\n return result.value as T\n }\n\n throw new StandardSchemaValidationError(result.issues)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema-converter.js","names":[],"sources":["../../../../../src/activities/chat/tools/schema-converter.ts"],"sourcesContent":["import type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from '@standard-schema/spec'\nimport type { NullWideningMap } from '@tanstack/ai-utils'\nimport type { JSONSchema, SchemaInput } from '../../../types'\n\n/**\n * Build a JSONSchema object from any plain key/value source. The `JSONSchema`\n * interface's `[key: string]: any` index signature makes every property\n * assignable through bracket access without a type cast — copying keys here\n * lets us narrow either `Record<string, unknown>` (returned by\n * `~standard.jsonSchema.input()`) or a `JSONSchema` (from the SchemaInput\n * pass-through arm) into the typed view used by the rest of this module.\n *\n * Accepts `object` so callers don't need a cast when narrowing from union\n * types like `SchemaInput`.\n */\nfunction toJsonSchema(obj: object): JSONSchema {\n const result: JSONSchema = {}\n for (const [key, value] of Object.entries(obj)) {\n if (key === '$schema') continue // not needed by LLM providers\n result[key] = value\n }\n return result\n}\n\n/**\n * Whether a value can carry a `~standard` property. Most schema libraries\n * (Zod, Valibot) return plain objects, but ArkType's `type()` returns a\n * *callable function* with `~standard` attached — so `typeof` must accept\n * both `'object'` and `'function'` or ArkType schemas are missed entirely\n * (issue #276).\n */\nfunction isPropertyCarrier(schema: unknown): schema is Record<string, unknown> {\n return (\n (typeof schema === 'object' || typeof schema === 'function') &&\n schema !== null\n )\n}\n\n/**\n * Check if a value is a Standard JSON Schema compliant schema.\n * Standard JSON Schema compliant libraries (Zod v4+, ArkType, Valibot with toStandardJsonSchema, etc.)\n * implement the '~standard' property with jsonSchema converter methods.\n */\nexport function isStandardJSONSchema(\n schema: unknown,\n): schema is StandardJSONSchemaV1 {\n if (!isPropertyCarrier(schema) || !('~standard' in schema)) return false\n\n const standard = schema['~standard']\n if (\n typeof standard !== 'object' ||\n standard === null ||\n !('version' in standard) ||\n standard.version !== 1 ||\n !('jsonSchema' in standard) ||\n typeof standard.jsonSchema !== 'object' ||\n standard.jsonSchema === null ||\n !('input' in standard.jsonSchema)\n ) {\n return false\n }\n\n return typeof standard.jsonSchema.input === 'function'\n}\n\n/**\n * Check if a value is a Standard Schema compliant schema (for validation).\n * Standard Schema compliant libraries implement the '~standard' property with a validate function.\n */\nexport function isStandardSchema(schema: unknown): schema is StandardSchemaV1 {\n return (\n isPropertyCarrier(schema) &&\n '~standard' in schema &&\n typeof schema['~standard'] === 'object' &&\n schema['~standard'] !== null &&\n 'version' in schema['~standard'] &&\n schema['~standard'].version === 1 &&\n 'validate' in schema['~standard'] &&\n typeof schema['~standard'].validate === 'function'\n )\n}\n\n/**\n * Result of {@link makeStructuredOutputCompatible}: the strict-ready schema plus\n * a {@link NullWideningMap} recording every position where a `null` was\n * synthesized, so the response can be un-widened before validation without\n * re-deriving (or guessing) which nulls were synthetic.\n */\ninterface StructuredOutputConversion {\n schema: JSONSchema\n nullWidening: NullWideningMap | undefined\n}\n\n/** Drop an empty map to `undefined` so leaf/no-op subtrees don't litter it. */\nfunction pruneMap(map: NullWideningMap): NullWideningMap | undefined {\n return Object.keys(map).length > 0 ? map : undefined\n}\n\n/**\n * Transform a JSON schema to be compatible with OpenAI's structured output requirements.\n * OpenAI requires:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for objects\n *\n * Alongside the transformed schema it returns a {@link NullWideningMap} marking\n * exactly the positions where `null` was added, so `undoNullWidening` can strip\n * those synthesized nulls (and only those) from the provider's response.\n *\n * @param schema - JSON schema to transform\n * @param originalRequired - Original required array (to know which fields were optional)\n * @returns Transformed schema + the null-widening map for the round trip\n */\nfunction makeStructuredOutputCompatible(\n schema: JSONSchema,\n originalRequired: Array<string> = [],\n): StructuredOutputConversion {\n const result: JSONSchema = { ...schema }\n const map: NullWideningMap = {}\n\n // Handle object types\n if (result.type === 'object' && result.properties) {\n const properties: Record<string, JSONSchema> = { ...result.properties }\n const allPropertyNames = Object.keys(properties)\n const propertyMaps: Record<string, NullWideningMap> = {}\n\n // Transform each property\n for (const propName of allPropertyNames) {\n const prop = properties[propName]\n if (!prop) continue\n const wasOptional = !originalRequired.includes(propName)\n // `null` synthesized AT this property (the field itself can come back null).\n let widenedHere = false\n // Map describing widened positions INSIDE this property.\n let childMap: NullWideningMap | undefined\n\n // Recursively transform nested objects/arrays\n if (prop.type === 'object' && prop.properties) {\n const nested = makeStructuredOutputCompatible(prop, prop.required || [])\n properties[propName] = wasOptional\n ? { ...nested.schema, type: ['object', 'null'] }\n : nested.schema\n widenedHere = wasOptional\n childMap = nested.nullWidening\n } else if (prop.type === 'array' && prop.items) {\n const items = Array.isArray(prop.items) ? prop.items[0] : prop.items\n const nestedItems = items\n ? makeStructuredOutputCompatible(items, items.required || [])\n : undefined\n properties[propName] = {\n ...prop,\n items: nestedItems ? nestedItems.schema : prop.items,\n ...(wasOptional ? { type: ['array', 'null'] } : {}),\n }\n widenedHere = wasOptional\n childMap = nestedItems?.nullWidening\n ? { items: nestedItems.nullWidening }\n : undefined\n } else if (wasOptional) {\n // Make optional fields nullable by adding null to the type. Mark\n // `widenedHere` only where we actually add `null`; a field already\n // typed nullable (`.nullish()`) is left as-is and keeps its null.\n if (prop.type && !Array.isArray(prop.type)) {\n properties[propName] = { ...prop, type: [prop.type, 'null'] }\n widenedHere = true\n } else if (Array.isArray(prop.type) && !prop.type.includes('null')) {\n properties[propName] = { ...prop, type: [...prop.type, 'null'] }\n widenedHere = true\n }\n }\n\n if (widenedHere || childMap) {\n propertyMaps[propName] = {\n ...(childMap ?? {}),\n ...(widenedHere ? { widened: true } : {}),\n }\n }\n }\n\n result.properties = properties\n // ALL properties must be required for OpenAI structured output\n result.required = allPropertyNames\n // additionalProperties must be false\n result.additionalProperties = false\n if (Object.keys(propertyMaps).length > 0) map.properties = propertyMaps\n }\n\n // Handle array types with object items\n if (result.type === 'array' && result.items) {\n const items = Array.isArray(result.items) ? result.items[0] : result.items\n if (items) {\n const nestedItems = makeStructuredOutputCompatible(\n items,\n items.required || [],\n )\n result.items = nestedItems.schema\n if (nestedItems.nullWidening) map.items = nestedItems.nullWidening\n }\n }\n\n return { schema: result, nullWidening: pruneMap(map) }\n}\n\n/**\n * Options for schema conversion\n */\nexport interface ConvertSchemaOptions {\n /**\n * When true, transforms the schema to be compatible with OpenAI's structured output requirements:\n * - All properties are added to the `required` array\n * - Optional fields get null added to their type union\n * - additionalProperties is set to false for all objects\n *\n * @default false\n */\n forStructuredOutput?: boolean\n}\n\n/**\n * Normalize any supported schema input to a typed, UN-widened `JSONSchema` —\n * the shared first half of conversion, before any structured-output widening.\n *\n * - Standard JSON Schemas are rebuilt structurally (dropping `$schema`, which\n * LLM providers ignore) and given the explicit `type`/`properties`/`required`\n * defaults object shapes need downstream.\n * - Plain `JSONSchema` inputs are rebuilt into the typed view; non-object inputs\n * are surfaced untouched (they can't be widened).\n * - Standard Schema validators lacking a `~standard.jsonSchema` converter throw\n * with actionable guidance, rather than shipping `{ '~standard': … }` to the\n * provider and producing an opaque downstream error.\n */\nfunction toTypedJsonSchema(schema: SchemaInput): JSONSchema | undefined {\n if (isStandardJSONSchema(schema)) {\n const jsonSchema = schema['~standard'].jsonSchema.input({\n target: 'draft-07',\n })\n const result: JSONSchema = toJsonSchema(jsonSchema)\n if ('properties' in result && !result.type) result.type = 'object'\n if (result.type === 'object' && !('properties' in result)) {\n result.properties = {}\n }\n if (result.type === 'object' && !('required' in result)) {\n result.required = []\n }\n return result\n }\n\n if (isStandardSchema(schema)) {\n throw new Error(\n 'Schema is a Standard Schema validator but does not expose a JSON Schema ' +\n 'converter on `~standard.jsonSchema`. Use Zod v4.2+, ArkType v2.1.28+, ' +\n 'or wrap a Valibot schema with `toStandardJsonSchema()` from ' +\n '`@valibot/to-json-schema` before passing it as `outputSchema`.',\n )\n }\n\n if (typeof schema !== 'object') return schema\n return toJsonSchema(schema)\n}\n\n/**\n * Converts a Standard JSON Schema compliant schema or plain JSONSchema to JSON Schema format\n * compatible with LLM providers.\n *\n * Supports any schema library that implements the Standard JSON Schema spec (v1):\n * - Zod v4+ (natively supports StandardJSONSchemaV1)\n * - ArkType (natively supports StandardJSONSchemaV1)\n * - Valibot (via `toStandardJsonSchema()` from `@valibot/to-json-schema`)\n *\n * If the input is already a plain JSONSchema object, it is returned as-is.\n *\n * @param schema - Standard JSON Schema compliant schema or plain JSONSchema object to convert\n * @param options - Conversion options\n * @returns JSON Schema object that can be sent to LLM providers\n *\n * @example\n * ```typescript\n * // Using Zod v4+ (natively supports Standard JSON Schema)\n * import * as z from 'zod';\n *\n * const zodSchema = z.object({\n * location: z.string().describe('City name'),\n * unit: z.enum(['celsius', 'fahrenheit']).optional()\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(zodSchema);\n *\n * @example\n * // Using ArkType (natively supports Standard JSON Schema)\n * import { type } from 'arktype';\n *\n * const arkSchema = type({\n * location: 'string',\n * unit: \"'celsius' | 'fahrenheit'\"\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(arkSchema);\n *\n * @example\n * // Using Valibot (via toStandardJsonSchema)\n * import * as v from 'valibot';\n * import { toStandardJsonSchema } from '@valibot/to-json-schema';\n *\n * const valibotSchema = toStandardJsonSchema(v.object({\n * location: v.string(),\n * unit: v.optional(v.picklist(['celsius', 'fahrenheit']))\n * }));\n *\n * const jsonSchema = convertSchemaToJsonSchema(valibotSchema);\n *\n * @example\n * // Using JSONSchema directly (passes through unchanged)\n * const rawSchema = {\n * type: 'object',\n * properties: { location: { type: 'string' } },\n * required: ['location']\n * };\n * const result = convertSchemaToJsonSchema(rawSchema);\n * ```\n */\nexport function convertSchemaToJsonSchema(\n schema: SchemaInput | undefined,\n options: ConvertSchemaOptions = {},\n): JSONSchema | undefined {\n if (!schema) return undefined\n\n const { forStructuredOutput = false } = options\n\n // Plain-JSONSchema passthrough: with no widening requested, return the schema\n // by reference so callers comparing via `===` keep identity. Only the widening\n // path needs the rebuilt, normalized view from `toTypedJsonSchema`.\n if (\n !forStructuredOutput &&\n !isStandardJSONSchema(schema) &&\n !isStandardSchema(schema)\n ) {\n return schema\n }\n\n const base = toTypedJsonSchema(schema)\n // Non-object inputs can't be widened; surface them untouched.\n if (!base || typeof base !== 'object') return base\n if (!forStructuredOutput) return base\n return makeStructuredOutputCompatible(base, base.required || []).schema\n}\n\n/**\n * Convert a schema for structured output AND capture the {@link NullWideningMap}\n * recording every `null` the strict-mode widening synthesized. The map lets the\n * caller undo that widening on the provider's response (via `undoNullWidening`)\n * before validating against the original schema — optional fields read back as\n * absent while genuine `.nullable()` nulls survive. The map is `undefined` when\n * the schema isn't a widenable object or when no field needed widening.\n */\nexport function convertSchemaForStructuredOutput(\n schema: SchemaInput | undefined,\n): {\n jsonSchema: JSONSchema | undefined\n nullWideningMap: NullWideningMap | undefined\n} {\n if (!schema) return { jsonSchema: undefined, nullWideningMap: undefined }\n const base = toTypedJsonSchema(schema)\n if (!base || typeof base !== 'object') {\n return { jsonSchema: base, nullWideningMap: undefined }\n }\n const { schema: jsonSchema, nullWidening } = makeStructuredOutputCompatible(\n base,\n base.required || [],\n )\n return { jsonSchema, nullWideningMap: nullWidening }\n}\n\n/**\n * Validates data against a Standard Schema compliant schema.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Validation result with success status, data or issues\n */\nexport async function validateWithStandardSchema<T>(\n schema: unknown,\n data: unknown,\n): Promise<\n | { success: true; data: T }\n | {\n success: false\n issues: Array<{ message: string; path?: Array<string> | undefined }>\n }\n> {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return { success: true, data: data as T }\n }\n\n const result = await schema['~standard'].validate(data)\n\n if (!result.issues) {\n return { success: true, data: result.value as T }\n }\n\n return {\n success: false,\n issues: result.issues.map((issue) => ({\n message: issue.message || 'Validation failed',\n path: issue.path?.map(String),\n })),\n }\n}\n\n/**\n * Error thrown when Standard Schema validation fails. Carries the original\n * `issues` array so consumers (middleware `onError`, callers catching from\n * `chat({ outputSchema })`) can programmatically inspect each failure.\n */\nexport class StandardSchemaValidationError extends Error {\n override readonly name = 'StandardSchemaValidationError'\n readonly issues: ReadonlyArray<StandardSchemaV1.Issue>\n\n constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n super(\n `Validation failed: ${issues\n .map((i) => i.message || 'Validation failed')\n .join(', ')}`,\n )\n this.issues = issues\n }\n}\n\n/**\n * Synchronously validates data against a Standard Schema compliant schema.\n * Note: Some Standard Schema implementations may only support async validation.\n * In those cases, this function will throw.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Parsed/validated data\n * @throws StandardSchemaValidationError if validation fails; Error if the\n * schema only supports async validation.\n */\nexport function parseWithStandardSchema<T>(schema: unknown, data: unknown): T {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return data as T\n }\n\n const result = schema['~standard'].validate(data)\n\n // Handle async result (Promise)\n if (result instanceof Promise) {\n throw new Error(\n 'Schema validation returned a Promise. Use validateWithStandardSchema for async validation.',\n )\n }\n // Standard Schema validation returns { value } for success or { issues } for failure\n if (!result.issues) {\n return result.value as T\n }\n\n throw new StandardSchemaValidationError(result.issues)\n}\n"],"mappings":"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"}
|
|
@@ -433,8 +433,15 @@ async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ n
|
|
|
433
433
|
if (argsStr) try {
|
|
434
434
|
const parsed = JSON.parse(argsStr);
|
|
435
435
|
input = parsed && typeof parsed === "object" ? parsed : {};
|
|
436
|
-
} catch
|
|
437
|
-
|
|
436
|
+
} catch {
|
|
437
|
+
results.push({
|
|
438
|
+
toolCallId: toolCall.id,
|
|
439
|
+
toolName,
|
|
440
|
+
result: { error: `Failed to parse tool arguments as JSON: ${argsStr}` },
|
|
441
|
+
input,
|
|
442
|
+
state: "output-error"
|
|
443
|
+
});
|
|
444
|
+
continue;
|
|
438
445
|
}
|
|
439
446
|
if (tool.inputSchema && isStandardSchema(tool.inputSchema)) try {
|
|
440
447
|
input = parseWithStandardSchema(tool.inputSchema, input);
|
|
@@ -465,22 +472,23 @@ async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ n
|
|
|
465
472
|
if (tool.needsApproval) {
|
|
466
473
|
const approvalId = `approval_${toolCall.id}`;
|
|
467
474
|
const resolution = approvalResolution(approvals, toolCall.id);
|
|
468
|
-
if (resolution !== void 0)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
475
|
+
if (resolution !== void 0) {
|
|
476
|
+
if (isApproved(resolution)) {
|
|
477
|
+
input = editedApprovalArgs(resolution) ?? input;
|
|
478
|
+
if (clientResults.has(toolCall.id)) results.push(buildClientToolResult(toolCall.id, toolName, tool, clientResults.get(toolCall.id), input));
|
|
479
|
+
else needsClientExecution.push({
|
|
480
|
+
toolCallId: toolCall.id,
|
|
481
|
+
toolName,
|
|
482
|
+
input
|
|
483
|
+
});
|
|
484
|
+
} else results.push({
|
|
472
485
|
toolCallId: toolCall.id,
|
|
473
486
|
toolName,
|
|
474
|
-
|
|
487
|
+
result: resumeState?.deniedToolResults?.get(toolCall.id) ?? deniedApprovalResult(resolution),
|
|
488
|
+
input,
|
|
489
|
+
state: "output-error"
|
|
475
490
|
});
|
|
476
|
-
} else
|
|
477
|
-
toolCallId: toolCall.id,
|
|
478
|
-
toolName,
|
|
479
|
-
result: resumeState?.deniedToolResults?.get(toolCall.id) ?? deniedApprovalResult(resolution),
|
|
480
|
-
input,
|
|
481
|
-
state: "output-error"
|
|
482
|
-
});
|
|
483
|
-
else needsApproval.push({
|
|
491
|
+
} else needsApproval.push({
|
|
484
492
|
toolCallId: toolCall.id,
|
|
485
493
|
toolName: toolCall.function.name,
|
|
486
494
|
input,
|
|
@@ -497,22 +505,23 @@ async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ n
|
|
|
497
505
|
if (tool.needsApproval) {
|
|
498
506
|
const approvalId = `approval_${toolCall.id}`;
|
|
499
507
|
const resolution = approvalResolution(approvals, toolCall.id);
|
|
500
|
-
if (resolution !== void 0)
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
508
|
+
if (resolution !== void 0) {
|
|
509
|
+
if (isApproved(resolution)) {
|
|
510
|
+
input = editedApprovalArgs(resolution) ?? input;
|
|
511
|
+
if (middlewareHooks) {
|
|
512
|
+
const decision = await applyBeforeToolCallDecision(toolCall, tool, input, toolName, middlewareHooks, results);
|
|
513
|
+
if (!decision.proceed) continue;
|
|
514
|
+
input = decision.input;
|
|
515
|
+
}
|
|
516
|
+
yield* executeServerTool(toolCall, tool, toolName, input, context, pendingEvents, results, middlewareHooks);
|
|
517
|
+
} else results.push({
|
|
518
|
+
toolCallId: toolCall.id,
|
|
519
|
+
toolName,
|
|
520
|
+
result: resumeState?.deniedToolResults?.get(toolCall.id) ?? deniedApprovalResult(resolution),
|
|
521
|
+
input,
|
|
522
|
+
state: "output-error"
|
|
523
|
+
});
|
|
524
|
+
} else needsApproval.push({
|
|
516
525
|
toolCallId: toolCall.id,
|
|
517
526
|
toolName,
|
|
518
527
|
input,
|
|
@@ -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 { 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.index ?? this.toolCallsMap.size\n const runtimeEvent = event as Partial<ToolCallStartEvent> &\n Pick<ToolCallStartEvent, 'toolName'>\n const name = runtimeEvent.toolCallName ?? runtimeEvent.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 // Find the tool call by ID\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n toolCall.function.arguments += event.delta\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.entries()) {\n if (toolCall.id === event.toolCallId) {\n if (event.input !== undefined) {\n // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)\n const normalized =\n event.input && typeof event.input === 'object' ? event.input : {}\n toolCall.function.arguments = JSON.stringify(normalized)\n }\n break\n }\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<ToolCallEndEvent, 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 // Surfaced on the emitted `TOOL_CALL_END` event as `output` so\n // consumers can read it typed (via `TypedStreamChunk` distribution\n // over the tools array) without re-parsing `result`.\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: finishEvent.model,\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 * Surfaced on engine-emitted `TOOL_CALL_END` events for TypedStreamChunk consumers.\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, throwing error if invalid JSON\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 (parseError) {\n // If parsing fails, throw error to fail fast\n throw new Error(`Failed to parse tool arguments as JSON: ${argsStr}`)\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 { 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.index ?? this.toolCallsMap.size\n const runtimeEvent = event as Partial<ToolCallStartEvent> &\n Pick<ToolCallStartEvent, 'toolName'>\n const name = runtimeEvent.toolCallName ?? runtimeEvent.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 // Find the tool call by ID\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n toolCall.function.arguments += event.delta\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.entries()) {\n if (toolCall.id === event.toolCallId) {\n if (event.input !== undefined) {\n // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)\n const normalized =\n event.input && typeof event.input === 'object' ? event.input : {}\n toolCall.function.arguments = JSON.stringify(normalized)\n }\n break\n }\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<ToolCallEndEvent, 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 // Surfaced on the emitted `TOOL_CALL_END` event as `output` so\n // consumers can read it typed (via `TypedStreamChunk` distribution\n // over the tools array) without re-parsing `result`.\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: finishEvent.model,\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 * Surfaced on engine-emitted `TOOL_CALL_END` events for TypedStreamChunk consumers.\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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"}
|
|
@@ -263,13 +263,14 @@ async function* runStreamingVideoGeneration(options) {
|
|
|
263
263
|
jobId: jobResult.jobId,
|
|
264
264
|
url: urlResult.url
|
|
265
265
|
});
|
|
266
|
-
const
|
|
266
|
+
const rawResult = {
|
|
267
267
|
jobId: jobResult.jobId,
|
|
268
268
|
status: "completed",
|
|
269
269
|
url: urlResult.url,
|
|
270
270
|
expiresAt: urlResult.expiresAt,
|
|
271
271
|
...urlResult.usage ? { usage: urlResult.usage } : {}
|
|
272
|
-
}
|
|
272
|
+
};
|
|
273
|
+
const result = await applyGenerationResultTransforms(mwCtx, rawResult);
|
|
273
274
|
if (urlResult.usage) await runGenerationUsage(middleware, mwCtx, urlResult.usage);
|
|
274
275
|
await runGenerationFinish(middleware, mwCtx, {
|
|
275
276
|
duration: Date.now() - obsStartTime,
|