@tanstack/ai 0.61.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/esm/activities/chat/agents/define-agent.d.ts +17 -5
- package/dist/esm/activities/chat/agents/define-agent.js.map +1 -1
- package/dist/esm/activities/chat/agents/spawn.d.ts +2 -0
- package/dist/esm/activities/chat/agents/spawn.js +8 -5
- package/dist/esm/activities/chat/agents/spawn.js.map +1 -1
- package/dist/esm/activities/chat/index.js +156 -57
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +30 -18
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +1 -0
- package/dist/esm/activities/chat/middleware/types.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
- package/dist/esm/activities/chat/stream/message-updaters.js +2 -1
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +11 -9
- package/dist/esm/activities/chat/stream/processor.js +38 -18
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +16 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +55 -16
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +4 -0
- package/dist/esm/activities/chat/tools/tool-definition.js +4 -0
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/evaluate/adapter.d.ts +4 -0
- package/dist/esm/activities/evaluate/adapter.js.map +1 -1
- package/dist/esm/activities/evaluate/index.d.ts +4 -0
- package/dist/esm/activities/evaluate/index.js +3 -1
- package/dist/esm/activities/evaluate/index.js.map +1 -1
- package/dist/esm/client.d.ts +1 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/interrupt-resume.js +29 -4
- package/dist/esm/interrupt-resume.js.map +1 -1
- package/dist/esm/middlewares/otel.d.ts +5 -2
- package/dist/esm/middlewares/otel.js +114 -0
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/types.d.ts +42 -1
- package/dist/esm/utilities/ag-ui-wire.js +5 -3
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/tool-result.d.ts +2 -1
- package/dist/esm/utilities/tool-result.js +4 -1
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-core/chat-experience/SKILL.md +120 -0
- package/skills/ai-core/tool-calling/SKILL.md +103 -0
- package/src/activities/chat/agents/define-agent.ts +20 -3
- package/src/activities/chat/agents/spawn.ts +20 -12
- package/src/activities/chat/index.ts +235 -62
- package/src/activities/chat/messages.ts +40 -5
- package/src/activities/chat/middleware/types.ts +1 -0
- package/src/activities/chat/stream/message-updaters.ts +3 -0
- package/src/activities/chat/stream/processor.ts +57 -27
- package/src/activities/chat/tools/tool-calls.ts +98 -9
- package/src/activities/chat/tools/tool-definition.ts +8 -0
- package/src/activities/evaluate/adapter.ts +4 -0
- package/src/activities/evaluate/index.ts +6 -0
- package/src/client.ts +1 -0
- package/src/index.ts +1 -0
- package/src/interrupt-resume.ts +55 -4
- package/src/middlewares/otel.ts +161 -3
- package/src/types.ts +38 -1
- package/src/utilities/ag-ui-wire.ts +12 -1
- package/src/utilities/tool-result.ts +7 -1
|
@@ -63,7 +63,7 @@ function updateToolCallPart(messages, messageId, toolCall) {
|
|
|
63
63
|
/**
|
|
64
64
|
* Update or add a tool result part to a message.
|
|
65
65
|
*/
|
|
66
|
-
function updateToolResultPart(messages, messageId, toolCallId, content, state, error) {
|
|
66
|
+
function updateToolResultPart(messages, messageId, toolCallId, content, state, error, outcome) {
|
|
67
67
|
return messages.map((msg) => {
|
|
68
68
|
if (msg.id !== messageId) return msg;
|
|
69
69
|
const parts = [...msg.parts];
|
|
@@ -73,6 +73,7 @@ function updateToolResultPart(messages, messageId, toolCallId, content, state, e
|
|
|
73
73
|
toolCallId,
|
|
74
74
|
content,
|
|
75
75
|
state,
|
|
76
|
+
...outcome !== void 0 && { outcome },
|
|
76
77
|
...error && { error }
|
|
77
78
|
};
|
|
78
79
|
if (resultPartIndex >= 0) parts[resultPartIndex] = toolResultPart;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-updaters.js","names":[],"sources":["../../../../../src/activities/chat/stream/message-updaters.ts"],"sourcesContent":["/**\n * Message Updaters (Internal)\n *\n * Internal helper functions for updating UIMessage parts.\n * These are used by StreamProcessor to manage the message array.\n */\n\nimport { parsePartialJSON } from './json-parser'\nimport type {\n ContentPart,\n StructuredOutputPart,\n ThinkingPart,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n} from '../../../types'\nimport type { ToolCallState, ToolResultState } from './types'\n\n/**\n * Update or add a text part to a message.\n *\n * If the last part is a text part, update it (continuing the same text segment).\n * Otherwise, create a new text part (starting a new text segment after tool calls).\n */\nexport function updateTextPart(\n messages: Array<UIMessage>,\n messageId: string,\n content: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const lastPart = parts.length > 0 ? parts[parts.length - 1] : null\n\n if (lastPart && lastPart.type === 'text') {\n // Update the last text part (continuing same text segment)\n parts[parts.length - 1] = { type: 'text', content }\n } else {\n // Create new text part (starting new text segment after tool calls/results)\n parts.push({ type: 'text', content })\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a tool call part to a message.\n */\nexport function updateToolCallPart(\n messages: Array<UIMessage>,\n messageId: string,\n toolCall: {\n id: string\n name: string\n arguments: string\n state: ToolCallState\n /** Parsed input — set when the arguments are complete. */\n input?: unknown\n metadata?: Record<string, unknown>\n },\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const existing = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCall.id,\n )\n\n // Carry forward metadata from either the new toolCall or the existing\n // part. Once the adapter has emitted metadata for a tool call (e.g.\n // Gemini's thoughtSignature on TOOL_CALL_START) we must not lose it on\n // subsequent updates that don't re-supply it.\n const metadata = toolCall.metadata ?? existing?.metadata\n // Same for the parsed input: it's supplied once at completion, so\n // subsequent arg-less updates (approval, etc.) must not drop it.\n const input = toolCall.input ?? existing?.input\n\n const toolCallPart: ToolCallPart = {\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: toolCall.state,\n // Carry forward approval, output and parsed input from the existing part\n ...(existing?.approval && { approval: { ...existing.approval } }),\n ...(existing?.output !== undefined && { output: existing.output }),\n ...(input !== undefined && { input }),\n ...(metadata !== undefined && { metadata }),\n }\n\n if (existing) {\n // Update existing tool call\n parts[parts.indexOf(existing)] = toolCallPart\n } else {\n // Add new tool call at the end (preserve natural streaming order)\n parts.push(toolCallPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a tool result part to a message.\n */\nexport function updateToolResultPart(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n content: string | Array<ContentPart>,\n state: ToolResultState,\n error?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const resultPartIndex = parts.findIndex(\n (p): p is ToolResultPart =>\n p.type === 'tool-result' && p.toolCallId === toolCallId,\n )\n\n const toolResultPart: ToolResultPart = {\n type: 'tool-result',\n toolCallId,\n content,\n state,\n ...(error && { error }),\n }\n\n if (resultPartIndex >= 0) {\n parts[resultPartIndex] = toolResultPart\n } else {\n parts.push(toolResultPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with approval request metadata.\n */\nexport function updateToolCallApproval(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n approvalId: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n state: 'approval-requested',\n approval: {\n id: approvalId,\n needsApproval: true,\n },\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part's state (e.g., to \"input-complete\").\n */\nexport function updateToolCallState(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n state: ToolCallState,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n const index = parts.indexOf(toolCallPart)\n parts[index] = { ...toolCallPart, state }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with output.\n * Searches all messages to find the tool call by ID.\n */\nexport function updateToolCallWithOutput(\n messages: Array<UIMessage>,\n toolCallId: string,\n output: any,\n state?: ToolCallState,\n errorText?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n output: errorText ? { error: errorText } : output,\n state: state ?? (errorText ? 'error' : 'complete'),\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with approval response.\n * Searches all messages to find the tool call by approval ID.\n */\nexport function updateToolCallApprovalResponse(\n messages: Array<UIMessage>,\n approvalId: string,\n approved: boolean,\n): Array<UIMessage> {\n return messages.map((msg) => {\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart =>\n p.type === 'tool-call' && p.approval?.id === approvalId,\n )\n\n if (toolCallPart && toolCallPart.approval) {\n const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n approval: { ...toolCallPart.approval, approved },\n state: 'approval-responded',\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Append a delta to the structured-output part on `messageId`, or create one\n * if absent. Progressive parse of the accumulated buffer fills `partial`.\n *\n * Callers must only invoke this while the part is still in flight — the\n * helper unconditionally writes `status: 'streaming'`, so feeding it a delta\n * after a `complete`/`error` terminal would regress the part. In practice the\n * processor gates calls via `structuredMessageIds`, which is dropped on\n * terminal events.\n *\n * If the progressive parse returns null/undefined (the buffer is not yet a\n * parseable JSON prefix), the previously-good `partial` is preserved so the\n * UI doesn't flicker back to empty for a single render.\n */\nexport function appendStructuredOutputDelta(\n messages: Array<UIMessage>,\n messageId: string,\n delta: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n const existing =\n existingIndex >= 0 ? (parts[existingIndex] as StructuredOutputPart) : null\n\n const nextRaw = (existing?.raw ?? '') + delta\n const progressive = parsePartialJSON(nextRaw)\n const nextPartial =\n progressive !== undefined && progressive !== null\n ? progressive\n : existing?.partial\n\n const nextPart: StructuredOutputPart = {\n type: 'structured-output',\n status: 'streaming',\n raw: nextRaw,\n ...(nextPartial !== undefined ? { partial: nextPartial } : {}),\n ...(existing?.reasoning !== undefined\n ? { reasoning: existing.reasoning }\n : {}),\n }\n\n if (existingIndex >= 0) {\n parts[existingIndex] = nextPart\n } else {\n parts.push(nextPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Snap the structured-output part on `messageId` to `complete` with the\n * validated `data`. Picks the freshest available `raw` so the wire\n * round-trip stays internally consistent:\n *\n * 1. Caller-supplied `raw` (the original streamed bytes from the model).\n * 2. The existing part's `raw` (deltas accumulated before this terminal).\n * 3. `JSON.stringify(data)` as a defensive fallback for terminal-only\n * completes that never shipped raw — keeps the part self-consistent\n * so downstream consumers never see a complete part with empty raw.\n */\nexport function completeStructuredOutputPart(\n messages: Array<UIMessage>,\n messageId: string,\n data: unknown,\n raw: string,\n reasoning?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n\n const existingRaw =\n existingIndex >= 0\n ? (parts[existingIndex] as StructuredOutputPart).raw\n : ''\n let resolvedRaw = raw || existingRaw\n if (resolvedRaw === '' && data !== undefined) {\n try {\n resolvedRaw = JSON.stringify(data)\n } catch {\n // Unserializable (circular, BigInt, throwing toJSON). Leave raw\n // empty. Both downstream paths handle this: `ag-ui-wire.ts`\n // `collectText` skips complete parts with empty raw entirely, and\n // `uiMessageToModelMessages` falls back to a defensive\n // `safeJsonStringify(data)` which itself returns `''` for the same\n // unserializable inputs — so the turn is silently dropped from the\n // next request rather than shipping garbage or crashing the stream.\n }\n }\n\n const nextPart: StructuredOutputPart = {\n type: 'structured-output',\n status: 'complete',\n data,\n partial: data,\n raw: resolvedRaw,\n ...(reasoning !== undefined ? { reasoning } : {}),\n }\n\n if (existingIndex >= 0) {\n parts[existingIndex] = nextPart\n } else {\n parts.push(nextPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Mark the structured-output part on `messageId` as errored. If no part\n * exists yet — RUN_ERROR fired after `structured-output.start` but before\n * any delta — create an empty errored placeholder so consumers have\n * something renderable. Existing complete parts are left alone (an error\n * after a successful complete should not retroactively un-complete it).\n */\nexport function errorStructuredOutputPart(\n messages: Array<UIMessage>,\n messageId: string,\n errorMessage: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n\n if (existingIndex < 0) {\n parts.push({\n type: 'structured-output',\n status: 'error',\n raw: '',\n errorMessage,\n })\n return { ...msg, parts }\n }\n\n const existing = parts[existingIndex] as StructuredOutputPart\n if (existing.status === 'complete') {\n return msg\n }\n parts[existingIndex] = {\n ...existing,\n status: 'error',\n errorMessage,\n }\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a thinking part to a message, keyed by stepId.\n * Each distinct stepId produces its own ThinkingPart.\n */\nexport function updateThinkingPart(\n messages: Array<UIMessage>,\n messageId: string,\n stepId: string,\n content: string,\n signature?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n let thinkingPartIndex = parts.findIndex(\n (p) => p.type === 'thinking' && p.stepId === stepId,\n )\n\n // A hydrated message carries its thinking without a stepId: the stored form\n // has no field for one, so `modelMessageToUIMessage` cannot put it back.\n // When a run is rejoined mid-stream the replayed reasoning is keyed by\n // stepId, matches nothing, and gets appended -- leaving a second thinking\n // part sitting after the answer text. Adopt the first stepId-less thinking\n // part instead, so the replay lands on the part it belongs to. Live\n // streaming always writes a stepId, so the only parts this can match are\n // hydrated ones.\n let adopted: ThinkingPart | undefined\n if (thinkingPartIndex < 0) {\n thinkingPartIndex = parts.findIndex(\n (p) => p.type === 'thinking' && p.stepId === undefined,\n )\n const candidate = parts[thinkingPartIndex]\n if (candidate?.type === 'thinking') adopted = candidate\n }\n\n // Keep the signature the hydrated part already had when this update does\n // not carry one; losing it would strip the provider's encrypted reasoning\n // from a message that is about to be sent back.\n const nextSignature = signature ?? adopted?.signature\n\n const thinkingPart: ThinkingPart = {\n type: 'thinking',\n content,\n stepId,\n ...(nextSignature && { signature: nextSignature }),\n }\n\n if (thinkingPartIndex >= 0) {\n // Update existing thinking part for this step\n parts[thinkingPartIndex] = thinkingPart\n } else {\n // Add new thinking part at the end (preserve natural streaming order)\n parts.push(thinkingPart)\n }\n\n return { ...msg, parts }\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"message-updaters.js","names":[],"sources":["../../../../../src/activities/chat/stream/message-updaters.ts"],"sourcesContent":["/**\n * Message Updaters (Internal)\n *\n * Internal helper functions for updating UIMessage parts.\n * These are used by StreamProcessor to manage the message array.\n */\n\nimport { parsePartialJSON } from './json-parser'\nimport type {\n ContentPart,\n StructuredOutputPart,\n ThinkingPart,\n ToolCallPart,\n ToolResultOutcome,\n ToolResultPart,\n UIMessage,\n} from '../../../types'\nimport type { ToolCallState, ToolResultState } from './types'\n\n/**\n * Update or add a text part to a message.\n *\n * If the last part is a text part, update it (continuing the same text segment).\n * Otherwise, create a new text part (starting a new text segment after tool calls).\n */\nexport function updateTextPart(\n messages: Array<UIMessage>,\n messageId: string,\n content: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const lastPart = parts.length > 0 ? parts[parts.length - 1] : null\n\n if (lastPart && lastPart.type === 'text') {\n // Update the last text part (continuing same text segment)\n parts[parts.length - 1] = { type: 'text', content }\n } else {\n // Create new text part (starting new text segment after tool calls/results)\n parts.push({ type: 'text', content })\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a tool call part to a message.\n */\nexport function updateToolCallPart(\n messages: Array<UIMessage>,\n messageId: string,\n toolCall: {\n id: string\n name: string\n arguments: string\n state: ToolCallState\n /** Parsed input — set when the arguments are complete. */\n input?: unknown\n metadata?: Record<string, unknown>\n },\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const existing = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCall.id,\n )\n\n // Carry forward metadata from either the new toolCall or the existing\n // part. Once the adapter has emitted metadata for a tool call (e.g.\n // Gemini's thoughtSignature on TOOL_CALL_START) we must not lose it on\n // subsequent updates that don't re-supply it.\n const metadata = toolCall.metadata ?? existing?.metadata\n // Same for the parsed input: it's supplied once at completion, so\n // subsequent arg-less updates (approval, etc.) must not drop it.\n const input = toolCall.input ?? existing?.input\n\n const toolCallPart: ToolCallPart = {\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: toolCall.state,\n // Carry forward approval, output and parsed input from the existing part\n ...(existing?.approval && { approval: { ...existing.approval } }),\n ...(existing?.output !== undefined && { output: existing.output }),\n ...(input !== undefined && { input }),\n ...(metadata !== undefined && { metadata }),\n }\n\n if (existing) {\n // Update existing tool call\n parts[parts.indexOf(existing)] = toolCallPart\n } else {\n // Add new tool call at the end (preserve natural streaming order)\n parts.push(toolCallPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a tool result part to a message.\n */\nexport function updateToolResultPart(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n content: string | Array<ContentPart>,\n state: ToolResultState,\n error?: string,\n outcome?: ToolResultOutcome,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const resultPartIndex = parts.findIndex(\n (p): p is ToolResultPart =>\n p.type === 'tool-result' && p.toolCallId === toolCallId,\n )\n\n const toolResultPart: ToolResultPart = {\n type: 'tool-result',\n toolCallId,\n content,\n state,\n ...(outcome !== undefined && { outcome }),\n ...(error && { error }),\n }\n\n if (resultPartIndex >= 0) {\n parts[resultPartIndex] = toolResultPart\n } else {\n parts.push(toolResultPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with approval request metadata.\n */\nexport function updateToolCallApproval(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n approvalId: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n state: 'approval-requested',\n approval: {\n id: approvalId,\n needsApproval: true,\n },\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part's state (e.g., to \"input-complete\").\n */\nexport function updateToolCallState(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n state: ToolCallState,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n const index = parts.indexOf(toolCallPart)\n parts[index] = { ...toolCallPart, state }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with output.\n * Searches all messages to find the tool call by ID.\n */\nexport function updateToolCallWithOutput(\n messages: Array<UIMessage>,\n toolCallId: string,\n output: any,\n state?: ToolCallState,\n errorText?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n output: errorText ? { error: errorText } : output,\n state: state ?? (errorText ? 'error' : 'complete'),\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with approval response.\n * Searches all messages to find the tool call by approval ID.\n */\nexport function updateToolCallApprovalResponse(\n messages: Array<UIMessage>,\n approvalId: string,\n approved: boolean,\n): Array<UIMessage> {\n return messages.map((msg) => {\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart =>\n p.type === 'tool-call' && p.approval?.id === approvalId,\n )\n\n if (toolCallPart && toolCallPart.approval) {\n const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n approval: { ...toolCallPart.approval, approved },\n state: 'approval-responded',\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Append a delta to the structured-output part on `messageId`, or create one\n * if absent. Progressive parse of the accumulated buffer fills `partial`.\n *\n * Callers must only invoke this while the part is still in flight — the\n * helper unconditionally writes `status: 'streaming'`, so feeding it a delta\n * after a `complete`/`error` terminal would regress the part. In practice the\n * processor gates calls via `structuredMessageIds`, which is dropped on\n * terminal events.\n *\n * If the progressive parse returns null/undefined (the buffer is not yet a\n * parseable JSON prefix), the previously-good `partial` is preserved so the\n * UI doesn't flicker back to empty for a single render.\n */\nexport function appendStructuredOutputDelta(\n messages: Array<UIMessage>,\n messageId: string,\n delta: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n const existing =\n existingIndex >= 0 ? (parts[existingIndex] as StructuredOutputPart) : null\n\n const nextRaw = (existing?.raw ?? '') + delta\n const progressive = parsePartialJSON(nextRaw)\n const nextPartial =\n progressive !== undefined && progressive !== null\n ? progressive\n : existing?.partial\n\n const nextPart: StructuredOutputPart = {\n type: 'structured-output',\n status: 'streaming',\n raw: nextRaw,\n ...(nextPartial !== undefined ? { partial: nextPartial } : {}),\n ...(existing?.reasoning !== undefined\n ? { reasoning: existing.reasoning }\n : {}),\n }\n\n if (existingIndex >= 0) {\n parts[existingIndex] = nextPart\n } else {\n parts.push(nextPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Snap the structured-output part on `messageId` to `complete` with the\n * validated `data`. Picks the freshest available `raw` so the wire\n * round-trip stays internally consistent:\n *\n * 1. Caller-supplied `raw` (the original streamed bytes from the model).\n * 2. The existing part's `raw` (deltas accumulated before this terminal).\n * 3. `JSON.stringify(data)` as a defensive fallback for terminal-only\n * completes that never shipped raw — keeps the part self-consistent\n * so downstream consumers never see a complete part with empty raw.\n */\nexport function completeStructuredOutputPart(\n messages: Array<UIMessage>,\n messageId: string,\n data: unknown,\n raw: string,\n reasoning?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n\n const existingRaw =\n existingIndex >= 0\n ? (parts[existingIndex] as StructuredOutputPart).raw\n : ''\n let resolvedRaw = raw || existingRaw\n if (resolvedRaw === '' && data !== undefined) {\n try {\n resolvedRaw = JSON.stringify(data)\n } catch {\n // Unserializable (circular, BigInt, throwing toJSON). Leave raw\n // empty. Both downstream paths handle this: `ag-ui-wire.ts`\n // `collectText` skips complete parts with empty raw entirely, and\n // `uiMessageToModelMessages` falls back to a defensive\n // `safeJsonStringify(data)` which itself returns `''` for the same\n // unserializable inputs — so the turn is silently dropped from the\n // next request rather than shipping garbage or crashing the stream.\n }\n }\n\n const nextPart: StructuredOutputPart = {\n type: 'structured-output',\n status: 'complete',\n data,\n partial: data,\n raw: resolvedRaw,\n ...(reasoning !== undefined ? { reasoning } : {}),\n }\n\n if (existingIndex >= 0) {\n parts[existingIndex] = nextPart\n } else {\n parts.push(nextPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Mark the structured-output part on `messageId` as errored. If no part\n * exists yet — RUN_ERROR fired after `structured-output.start` but before\n * any delta — create an empty errored placeholder so consumers have\n * something renderable. Existing complete parts are left alone (an error\n * after a successful complete should not retroactively un-complete it).\n */\nexport function errorStructuredOutputPart(\n messages: Array<UIMessage>,\n messageId: string,\n errorMessage: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n\n if (existingIndex < 0) {\n parts.push({\n type: 'structured-output',\n status: 'error',\n raw: '',\n errorMessage,\n })\n return { ...msg, parts }\n }\n\n const existing = parts[existingIndex] as StructuredOutputPart\n if (existing.status === 'complete') {\n return msg\n }\n parts[existingIndex] = {\n ...existing,\n status: 'error',\n errorMessage,\n }\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a thinking part to a message, keyed by stepId.\n * Each distinct stepId produces its own ThinkingPart.\n */\nexport function updateThinkingPart(\n messages: Array<UIMessage>,\n messageId: string,\n stepId: string,\n content: string,\n signature?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n let thinkingPartIndex = parts.findIndex(\n (p) => p.type === 'thinking' && p.stepId === stepId,\n )\n\n // A hydrated message carries its thinking without a stepId: the stored form\n // has no field for one, so `modelMessageToUIMessage` cannot put it back.\n // When a run is rejoined mid-stream the replayed reasoning is keyed by\n // stepId, matches nothing, and gets appended -- leaving a second thinking\n // part sitting after the answer text. Adopt the first stepId-less thinking\n // part instead, so the replay lands on the part it belongs to. Live\n // streaming always writes a stepId, so the only parts this can match are\n // hydrated ones.\n let adopted: ThinkingPart | undefined\n if (thinkingPartIndex < 0) {\n thinkingPartIndex = parts.findIndex(\n (p) => p.type === 'thinking' && p.stepId === undefined,\n )\n const candidate = parts[thinkingPartIndex]\n if (candidate?.type === 'thinking') adopted = candidate\n }\n\n // Keep the signature the hydrated part already had when this update does\n // not carry one; losing it would strip the provider's encrypted reasoning\n // from a message that is about to be sent back.\n const nextSignature = signature ?? adopted?.signature\n\n const thinkingPart: ThinkingPart = {\n type: 'thinking',\n content,\n stepId,\n ...(nextSignature && { signature: nextSignature }),\n }\n\n if (thinkingPartIndex >= 0) {\n // Update existing thinking part for this step\n parts[thinkingPartIndex] = thinkingPart\n } else {\n // Add new thinking part at the end (preserve natural streaming order)\n parts.push(thinkingPart)\n }\n\n return { ...msg, parts }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;AAyBA,SAAgB,eACd,UACA,WACA,SACkB;CAClB,OAAO,SAAS,KAAK,QAAQ;EAC3B,IAAI,IAAI,OAAO,WACb,OAAO;EAGT,MAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;EAC3B,MAAM,WAAW,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,KAAK;EAE9D,IAAI,YAAY,SAAS,SAAS,QAEhC,MAAM,MAAM,SAAS,KAAK;GAAE,MAAM;GAAQ;EAAQ;OAGlD,MAAM,KAAK;GAAE,MAAM;GAAQ;EAAQ,CAAC;EAGtC,OAAO;GAAE,GAAG;GAAK;EAAM;CACzB,CAAC;AACH;;;;AAKA,SAAgB,mBACd,UACA,WACA,UASkB;CAClB,OAAO,SAAS,KAAK,QAAQ;EAC3B,IAAI,IAAI,OAAO,WACb,OAAO;EAGT,MAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;EAC3B,MAAM,WAAW,MAAM,MACpB,MAAyB,EAAE,SAAS,eAAe,EAAE,OAAO,SAAS,EACxE;EAMA,MAAM,WAAW,SAAS,YAAY,UAAU;EAGhD,MAAM,QAAQ,SAAS,SAAS,UAAU;EAE1C,MAAM,eAA6B;GACjC,MAAM;GACN,IAAI,SAAS;GACb,MAAM,SAAS;GACf,WAAW,SAAS;GACpB,OAAO,SAAS;GAEhB,GAAI,UAAU,YAAY,EAAE,UAAU,EAAE,GAAG,SAAS,SAAS,EAAE;GAC/D,GAAI,UAAU,WAAW,KAAA,KAAa,EAAE,QAAQ,SAAS,OAAO;GAChE,GAAI,UAAU,KAAA,KAAa,EAAE,MAAM;GACnC,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;EAC3C;EAEA,IAAI,UAEF,MAAM,MAAM,QAAQ,QAAQ,KAAK;OAGjC,MAAM,KAAK,YAAY;EAGzB,OAAO;GAAE,GAAG;GAAK;EAAM;CACzB,CAAC;AACH;;;;AAKA,SAAgB,qBACd,UACA,WACA,YACA,SACA,OACA,OACA,SACkB;CAClB,OAAO,SAAS,KAAK,QAAQ;EAC3B,IAAI,IAAI,OAAO,WACb,OAAO;EAGT,MAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;EAC3B,MAAM,kBAAkB,MAAM,WAC3B,MACC,EAAE,SAAS,iBAAiB,EAAE,eAAe,UACjD;EAEA,MAAM,iBAAiC;GACrC,MAAM;GACN;GACA;GACA;GACA,GAAI,YAAY,KAAA,KAAa,EAAE,QAAQ;GACvC,GAAI,SAAS,EAAE,MAAM;EACvB;EAEA,IAAI,mBAAmB,GACrB,MAAM,mBAAmB;OAEzB,MAAM,KAAK,cAAc;EAG3B,OAAO;GAAE,GAAG;GAAK;EAAM;CACzB,CAAC;AACH;;;;AAKA,SAAgB,uBACd,UACA,WACA,YACA,YACkB;CAClB,OAAO,SAAS,KAAK,QAAQ;EAC3B,IAAI,IAAI,OAAO,WACb,OAAO;EAGT,MAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;EAC3B,MAAM,eAAe,MAAM,MACxB,MAAyB,EAAE,SAAS,eAAe,EAAE,OAAO,UAC/D;EAEA,IAAI,cAAc;GAChB,MAAM,QAAQ,MAAM,QAAQ,YAAY;GACxC,MAAM,SAAS;IACb,GAAG;IACH,OAAO;IACP,UAAU;KACR,IAAI;KACJ,eAAe;IACjB;GACF;EACF;EAEA,OAAO;GAAE,GAAG;GAAK;EAAM;CACzB,CAAC;AACH;;;;;AAkCA,SAAgB,yBACd,UACA,YACA,QACA,OACA,WACkB;CAClB,OAAO,SAAS,KAAK,QAAQ;EAC3B,MAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;EAC3B,MAAM,eAAe,MAAM,MACxB,MAAyB,EAAE,SAAS,eAAe,EAAE,OAAO,UAC/D;EAEA,IAAI,cAAc;GAChB,MAAM,QAAQ,MAAM,QAAQ,YAAY;GACxC,MAAM,SAAS;IACb,GAAG;IACH,QAAQ,YAAY,EAAE,OAAO,UAAU,IAAI;IAC3C,OAAO,UAAU,YAAY,UAAU;GACzC;EACF;EAEA,OAAO;GAAE,GAAG;GAAK;EAAM;CACzB,CAAC;AACH;;;;;AAMA,SAAgB,+BACd,UACA,YACA,UACkB;CAClB,OAAO,SAAS,KAAK,QAAQ;EAC3B,MAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;EAC3B,MAAM,eAAe,MAAM,MACxB,MACC,EAAE,SAAS,eAAe,EAAE,UAAU,OAAO,UACjD;EAEA,IAAI,gBAAgB,aAAa,UAAU;GACzC,MAAM,QAAQ,MAAM,QAAQ,YAAY;GACxC,MAAM,SAAS;IACb,GAAG;IACH,UAAU;KAAE,GAAG,aAAa;KAAU;IAAS;IAC/C,OAAO;GACT;EACF;EAEA,OAAO;GAAE,GAAG;GAAK;EAAM;CACzB,CAAC;AACH;;;;;;;;;;;;;;;AAgBA,SAAgB,4BACd,UACA,WACA,OACkB;CAClB,OAAO,SAAS,KAAK,QAAQ;EAC3B,IAAI,IAAI,OAAO,WACb,OAAO;EAGT,MAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;EAC3B,MAAM,gBAAgB,MAAM,WACzB,MAAiC,EAAE,SAAS,mBAC/C;EACA,MAAM,WACJ,iBAAiB,IAAK,MAAM,iBAA0C;EAExE,MAAM,WAAW,UAAU,OAAO,MAAM;EACxC,MAAM,cAAc,iBAAiB,OAAO;EAC5C,MAAM,cACJ,gBAAgB,KAAA,KAAa,gBAAgB,OACzC,cACA,UAAU;EAEhB,MAAM,WAAiC;GACrC,MAAM;GACN,QAAQ;GACR,KAAK;GACL,GAAI,gBAAgB,KAAA,IAAY,EAAE,SAAS,YAAY,IAAI,CAAC;GAC5D,GAAI,UAAU,cAAc,KAAA,IACxB,EAAE,WAAW,SAAS,UAAU,IAChC,CAAC;EACP;EAEA,IAAI,iBAAiB,GACnB,MAAM,iBAAiB;OAEvB,MAAM,KAAK,QAAQ;EAGrB,OAAO;GAAE,GAAG;GAAK;EAAM;CACzB,CAAC;AACH;;;;;;;;;;;;AAaA,SAAgB,6BACd,UACA,WACA,MACA,KACA,WACkB;CAClB,OAAO,SAAS,KAAK,QAAQ;EAC3B,IAAI,IAAI,OAAO,WACb,OAAO;EAGT,MAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;EAC3B,MAAM,gBAAgB,MAAM,WACzB,MAAiC,EAAE,SAAS,mBAC/C;EAEA,MAAM,cACJ,iBAAiB,IACZ,MAAM,cAAc,CAA0B,MAC/C;EACN,IAAI,cAAc,OAAO;EACzB,IAAI,gBAAgB,MAAM,SAAS,KAAA,GACjC,IAAI;GACF,cAAc,KAAK,UAAU,IAAI;EACnC,QAAQ,CAQR;EAGF,MAAM,WAAiC;GACrC,MAAM;GACN,QAAQ;GACR;GACA,SAAS;GACT,KAAK;GACL,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;EACjD;EAEA,IAAI,iBAAiB,GACnB,MAAM,iBAAiB;OAEvB,MAAM,KAAK,QAAQ;EAGrB,OAAO;GAAE,GAAG;GAAK;EAAM;CACzB,CAAC;AACH;;;;;;;;AASA,SAAgB,0BACd,UACA,WACA,cACkB;CAClB,OAAO,SAAS,KAAK,QAAQ;EAC3B,IAAI,IAAI,OAAO,WACb,OAAO;EAGT,MAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;EAC3B,MAAM,gBAAgB,MAAM,WACzB,MAAiC,EAAE,SAAS,mBAC/C;EAEA,IAAI,gBAAgB,GAAG;GACrB,MAAM,KAAK;IACT,MAAM;IACN,QAAQ;IACR,KAAK;IACL;GACF,CAAC;GACD,OAAO;IAAE,GAAG;IAAK;GAAM;EACzB;EAEA,MAAM,WAAW,MAAM;EACvB,IAAI,SAAS,WAAW,YACtB,OAAO;EAET,MAAM,iBAAiB;GACrB,GAAG;GACH,QAAQ;GACR;EACF;EACA,OAAO;GAAE,GAAG;GAAK;EAAM;CACzB,CAAC;AACH;;;;;AAMA,SAAgB,mBACd,UACA,WACA,QACA,SACA,WACkB;CAClB,OAAO,SAAS,KAAK,QAAQ;EAC3B,IAAI,IAAI,OAAO,WACb,OAAO;EAGT,MAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;EAC3B,IAAI,oBAAoB,MAAM,WAC3B,MAAM,EAAE,SAAS,cAAc,EAAE,WAAW,MAC/C;EAUA,IAAI;EACJ,IAAI,oBAAoB,GAAG;GACzB,oBAAoB,MAAM,WACvB,MAAM,EAAE,SAAS,cAAc,EAAE,WAAW,KAAA,CAC/C;GACA,MAAM,YAAY,MAAM;GACxB,IAAI,WAAW,SAAS,YAAY,UAAU;EAChD;EAKA,MAAM,gBAAgB,aAAa,SAAS;EAE5C,MAAM,eAA6B;GACjC,MAAM;GACN;GACA;GACA,GAAI,iBAAiB,EAAE,WAAW,cAAc;EAClD;EAEA,IAAI,qBAAqB,GAEvB,MAAM,qBAAqB;OAG3B,MAAM,KAAK,YAAY;EAGzB,OAAO;GAAE,GAAG;GAAK;EAAM;CACzB,CAAC;AACH"}
|
|
@@ -369,11 +369,13 @@ export declare class StreamProcessor {
|
|
|
369
369
|
* Handle TOOL_CALL_END event — arguments are finalized (input-complete).
|
|
370
370
|
* Tool output arrives on TOOL_CALL_RESULT, not on this event.
|
|
371
371
|
*
|
|
372
|
-
* If TOOL_CALL_END carries parsed `input`,
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* server_tool_use / web_search — issue #839) and
|
|
376
|
-
*
|
|
372
|
+
* If TOOL_CALL_END carries parsed `input`, it is the canonical arguments:
|
|
373
|
+
* write it into the accumulated string and override the rendered part's
|
|
374
|
+
* `input` with it. That covers adapters that deliver the whole input on END
|
|
375
|
+
* (e.g. Anthropic server_tool_use / web_search — issue #839) and adapters
|
|
376
|
+
* that stream the wire arguments and then normalize them (OpenAI strict-mode
|
|
377
|
+
* null widening, undone by the adapter after #939), so `arguments` and
|
|
378
|
+
* `input` never disagree on the persisted part.
|
|
377
379
|
*
|
|
378
380
|
* @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
|
|
379
381
|
*/
|
|
@@ -486,11 +488,11 @@ export declare class StreamProcessor {
|
|
|
486
488
|
private completeToolCall;
|
|
487
489
|
private isToolCallPartAwaitingUserAction;
|
|
488
490
|
/**
|
|
489
|
-
* Whether the rendered tool-call part for the given id has reached
|
|
490
|
-
* terminal 'error' state. Used to prevent the completion
|
|
491
|
-
* downgrading a
|
|
491
|
+
* Whether the rendered tool-call part for the given id has reached a
|
|
492
|
+
* terminal 'error' or 'complete' state. Used to prevent the completion
|
|
493
|
+
* safety net from downgrading a finished call back to 'input-complete'.
|
|
492
494
|
*/
|
|
493
|
-
private
|
|
495
|
+
private isToolCallPartTerminal;
|
|
494
496
|
/**
|
|
495
497
|
* Emit pending text update for a specific message.
|
|
496
498
|
*
|
|
@@ -3,7 +3,7 @@ import { mergeMetadata, tanstackMetadata } from "../../../utilities/merge-metada
|
|
|
3
3
|
import { runErrorEventToError } from "../../../utilities/errors.js";
|
|
4
4
|
import { INTERRUPT_BINDING_METADATA_KEY } from "../../../interrupt-resume.js";
|
|
5
5
|
import { isAssistantSegmentOf, isProviderExecutedToolCall } from "../../../utilities/provider-executed.js";
|
|
6
|
-
import { normalizeToolResult, toolResultErrorText } from "../../../utilities/tool-result.js";
|
|
6
|
+
import { isContentPartArray, isToolResultOutcome, normalizeToolResult, toolResultErrorText } from "../../../utilities/tool-result.js";
|
|
7
7
|
import { splitSubagentWire } from "../../../utilities/subagent-wire.js";
|
|
8
8
|
import { aguiContentToContentParts, aguiSnapshotMessageToUIMessage, coerceCreatedAt, generateMessageId, uiMessageToModelMessages } from "../messages.js";
|
|
9
9
|
import { getChunkRunId } from "../../../utilities/chunk-ids.js";
|
|
@@ -40,6 +40,22 @@ function interruptBatchHasGeneric(interrupts) {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
+
* The canonical arguments string for a `TOOL_CALL_END.input`, or `undefined`
|
|
44
|
+
* when JSON cannot carry it: `JSON.stringify` returns `undefined` (despite
|
|
45
|
+
* its declared type) for a top-level function or symbol and throws on BigInt
|
|
46
|
+
* and circular references. `null` is not tool arguments either, so it also
|
|
47
|
+
* keeps the streamed value instead of writing `arguments = "null"`.
|
|
48
|
+
*/
|
|
49
|
+
function serializeToolInput(input) {
|
|
50
|
+
if (input === void 0 || input === null) return void 0;
|
|
51
|
+
try {
|
|
52
|
+
const serialized = JSON.stringify(input);
|
|
53
|
+
return typeof serialized === "string" ? serialized : void 0;
|
|
54
|
+
} catch {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
43
59
|
* StreamProcessor - State machine for processing AI response streams
|
|
44
60
|
*
|
|
45
61
|
* Manages the full UIMessage[] conversation and emits events on changes.
|
|
@@ -1110,11 +1126,13 @@ var StreamProcessor = class StreamProcessor {
|
|
|
1110
1126
|
* Handle TOOL_CALL_END event — arguments are finalized (input-complete).
|
|
1111
1127
|
* Tool output arrives on TOOL_CALL_RESULT, not on this event.
|
|
1112
1128
|
*
|
|
1113
|
-
* If TOOL_CALL_END carries parsed `input`,
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1116
|
-
* server_tool_use / web_search — issue #839) and
|
|
1117
|
-
*
|
|
1129
|
+
* If TOOL_CALL_END carries parsed `input`, it is the canonical arguments:
|
|
1130
|
+
* write it into the accumulated string and override the rendered part's
|
|
1131
|
+
* `input` with it. That covers adapters that deliver the whole input on END
|
|
1132
|
+
* (e.g. Anthropic server_tool_use / web_search — issue #839) and adapters
|
|
1133
|
+
* that stream the wire arguments and then normalize them (OpenAI strict-mode
|
|
1134
|
+
* null widening, undone by the adapter after #939), so `arguments` and
|
|
1135
|
+
* `input` never disagree on the persisted part.
|
|
1118
1136
|
*
|
|
1119
1137
|
* @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
|
|
1120
1138
|
*/
|
|
@@ -1126,12 +1144,11 @@ var StreamProcessor = class StreamProcessor {
|
|
|
1126
1144
|
const input = chunk.input !== void 0 ? chunk.input : tanstackMetadata(chunk)?.input;
|
|
1127
1145
|
const existingToolCall = msgState.toolCalls.get(chunk.toolCallId);
|
|
1128
1146
|
if (existingToolCall && existingToolCall.state !== "input-complete") {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
} catch {}
|
|
1147
|
+
const serializedInput = serializeToolInput(input);
|
|
1148
|
+
if (serializedInput !== void 0) existingToolCall.arguments = serializedInput;
|
|
1132
1149
|
const index = msgState.toolCallOrder.indexOf(chunk.toolCallId);
|
|
1133
1150
|
this.completeToolCall(messageId, index, existingToolCall);
|
|
1134
|
-
if (
|
|
1151
|
+
if (serializedInput !== void 0) {
|
|
1135
1152
|
existingToolCall.parsedArguments = input;
|
|
1136
1153
|
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
1137
1154
|
id: existingToolCall.id,
|
|
@@ -1155,7 +1172,10 @@ var StreamProcessor = class StreamProcessor {
|
|
|
1155
1172
|
handleToolCallResultEvent(chunk) {
|
|
1156
1173
|
const messageId = this.toolCallToMessage.get(chunk.toolCallId) ?? this.messages.find((m) => m.parts.some((p) => p.type === "tool-call" && p.id === chunk.toolCallId))?.id;
|
|
1157
1174
|
if (!messageId) return;
|
|
1158
|
-
const
|
|
1175
|
+
const extra = chunk;
|
|
1176
|
+
const rawToolResultOutcome = tanstackMetadata(chunk)?.toolResultOutcome;
|
|
1177
|
+
const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome) ? rawToolResultOutcome : void 0;
|
|
1178
|
+
const isOutputError = extra.state === "output-error" || tanstackMetadata(chunk)?.state === "output-error" || toolResultOutcome !== void 0;
|
|
1159
1179
|
let output;
|
|
1160
1180
|
try {
|
|
1161
1181
|
output = typeof chunk.content === "string" ? JSON.parse(chunk.content) : chunk.content;
|
|
@@ -1164,7 +1184,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
1164
1184
|
}
|
|
1165
1185
|
this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output, isOutputError ? "error" : void 0);
|
|
1166
1186
|
const resultState = isOutputError ? "error" : "complete";
|
|
1167
|
-
this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, aguiContentToContentParts(chunk.content), resultState, resultState === "error" ? toolResultErrorText(output) : void 0);
|
|
1187
|
+
this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, isContentPartArray(output) ? output : aguiContentToContentParts(chunk.content), resultState, resultState === "error" ? toolResultErrorText(output) : void 0, toolResultOutcome);
|
|
1168
1188
|
this.emitMessagesChange();
|
|
1169
1189
|
}
|
|
1170
1190
|
/**
|
|
@@ -1530,7 +1550,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
1530
1550
|
} catch {
|
|
1531
1551
|
toolCall.parsedArguments = void 0;
|
|
1532
1552
|
}
|
|
1533
|
-
if (this.
|
|
1553
|
+
if (this.isToolCallPartTerminal(toolCall.id)) return;
|
|
1534
1554
|
if (this.isToolCallPartAwaitingUserAction(toolCall.id)) return;
|
|
1535
1555
|
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
1536
1556
|
id: toolCall.id,
|
|
@@ -1547,12 +1567,12 @@ var StreamProcessor = class StreamProcessor {
|
|
|
1547
1567
|
return this.messages.some((msg) => msg.parts?.some((part) => part.type === "tool-call" && part.id === toolCallId && (part.state === "approval-requested" || part.state === "approval-responded")));
|
|
1548
1568
|
}
|
|
1549
1569
|
/**
|
|
1550
|
-
* Whether the rendered tool-call part for the given id has reached
|
|
1551
|
-
* terminal 'error' state. Used to prevent the completion
|
|
1552
|
-
* downgrading a
|
|
1570
|
+
* Whether the rendered tool-call part for the given id has reached a
|
|
1571
|
+
* terminal 'error' or 'complete' state. Used to prevent the completion
|
|
1572
|
+
* safety net from downgrading a finished call back to 'input-complete'.
|
|
1553
1573
|
*/
|
|
1554
|
-
|
|
1555
|
-
return this.messages.some((msg) => msg.parts?.some((part) => part.type === "tool-call" && part.id === toolCallId && part.state === "error"));
|
|
1574
|
+
isToolCallPartTerminal(toolCallId) {
|
|
1575
|
+
return this.messages.some((msg) => msg.parts?.some((part) => part.type === "tool-call" && part.id === toolCallId && (part.state === "error" || part.state === "complete")));
|
|
1556
1576
|
}
|
|
1557
1577
|
/**
|
|
1558
1578
|
* Emit pending text update for a specific message.
|