@tanstack/ai 0.59.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.
Files changed (123) hide show
  1. package/README.md +1 -0
  2. package/dist/esm/activities/chat/adapter.d.ts +9 -0
  3. package/dist/esm/activities/chat/adapter.js +1 -0
  4. package/dist/esm/activities/chat/adapter.js.map +1 -1
  5. package/dist/esm/activities/chat/agents/define-agent.d.ts +17 -5
  6. package/dist/esm/activities/chat/agents/define-agent.js.map +1 -1
  7. package/dist/esm/activities/chat/agents/spawn.d.ts +2 -0
  8. package/dist/esm/activities/chat/agents/spawn.js +8 -5
  9. package/dist/esm/activities/chat/agents/spawn.js.map +1 -1
  10. package/dist/esm/activities/chat/index.js +289 -78
  11. package/dist/esm/activities/chat/index.js.map +1 -1
  12. package/dist/esm/activities/chat/messages.js +35 -19
  13. package/dist/esm/activities/chat/messages.js.map +1 -1
  14. package/dist/esm/activities/chat/middleware/types.d.ts +1 -0
  15. package/dist/esm/activities/chat/middleware/types.js.map +1 -1
  16. package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
  17. package/dist/esm/activities/chat/stream/message-updaters.js +11 -3
  18. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  19. package/dist/esm/activities/chat/stream/processor.d.ts +11 -10
  20. package/dist/esm/activities/chat/stream/processor.js +51 -28
  21. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  22. package/dist/esm/activities/chat/tools/tool-calls.d.ts +16 -2
  23. package/dist/esm/activities/chat/tools/tool-calls.js +57 -16
  24. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  25. package/dist/esm/activities/chat/tools/tool-definition.d.ts +4 -0
  26. package/dist/esm/activities/chat/tools/tool-definition.js +4 -0
  27. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  28. package/dist/esm/activities/embed/adapter.d.ts +7 -0
  29. package/dist/esm/activities/embed/adapter.js +1 -0
  30. package/dist/esm/activities/embed/adapter.js.map +1 -1
  31. package/dist/esm/activities/embed/index.js +2 -0
  32. package/dist/esm/activities/embed/index.js.map +1 -1
  33. package/dist/esm/activities/evaluate/adapter.d.ts +4 -0
  34. package/dist/esm/activities/evaluate/adapter.js.map +1 -1
  35. package/dist/esm/activities/evaluate/index.d.ts +4 -0
  36. package/dist/esm/activities/evaluate/index.js +3 -1
  37. package/dist/esm/activities/evaluate/index.js.map +1 -1
  38. package/dist/esm/activities/files/adapter.d.ts +97 -0
  39. package/dist/esm/activities/files/adapter.js +45 -0
  40. package/dist/esm/activities/files/adapter.js.map +1 -0
  41. package/dist/esm/activities/files/index.d.ts +66 -0
  42. package/dist/esm/activities/files/index.js +78 -0
  43. package/dist/esm/activities/files/index.js.map +1 -0
  44. package/dist/esm/activities/generateImage/adapter.d.ts +8 -0
  45. package/dist/esm/activities/generateImage/adapter.js +1 -0
  46. package/dist/esm/activities/generateImage/adapter.js.map +1 -1
  47. package/dist/esm/activities/generateImage/index.js +2 -0
  48. package/dist/esm/activities/generateImage/index.js.map +1 -1
  49. package/dist/esm/activities/generateVideo/adapter.d.ts +8 -0
  50. package/dist/esm/activities/generateVideo/adapter.js +1 -0
  51. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  52. package/dist/esm/activities/generateVideo/index.js +3 -0
  53. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  54. package/dist/esm/activities/generateWorld/adapter.d.ts +4 -2
  55. package/dist/esm/activities/generateWorld/adapter.js.map +1 -1
  56. package/dist/esm/activities/generateWorld/index.d.ts +4 -3
  57. package/dist/esm/activities/generateWorld/index.js +5 -4
  58. package/dist/esm/activities/generateWorld/index.js.map +1 -1
  59. package/dist/esm/activities/index.d.ts +6 -3
  60. package/dist/esm/activities/index.js +13 -11
  61. package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -0
  62. package/dist/esm/activities/summarize/chat-stream-summarize.js +8 -8
  63. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  64. package/dist/esm/client.d.ts +3 -1
  65. package/dist/esm/client.js +2 -1
  66. package/dist/esm/client.js.map +1 -1
  67. package/dist/esm/index.d.ts +4 -3
  68. package/dist/esm/index.js +5 -3
  69. package/dist/esm/interrupt-resume.js +29 -4
  70. package/dist/esm/interrupt-resume.js.map +1 -1
  71. package/dist/esm/middlewares/otel.d.ts +5 -2
  72. package/dist/esm/middlewares/otel.js +114 -0
  73. package/dist/esm/middlewares/otel.js.map +1 -1
  74. package/dist/esm/types.d.ts +114 -14
  75. package/dist/esm/utilities/ag-ui-wire.js +39 -16
  76. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  77. package/dist/esm/utilities/content-source.d.ts +60 -0
  78. package/dist/esm/utilities/content-source.js +85 -0
  79. package/dist/esm/utilities/content-source.js.map +1 -0
  80. package/dist/esm/utilities/provider-executed.d.ts +7 -0
  81. package/dist/esm/utilities/provider-executed.js +10 -1
  82. package/dist/esm/utilities/provider-executed.js.map +1 -1
  83. package/dist/esm/utilities/tool-result.d.ts +14 -3
  84. package/dist/esm/utilities/tool-result.js +26 -3
  85. package/dist/esm/utilities/tool-result.js.map +1 -1
  86. package/package.json +4 -4
  87. package/skills/ai-core/adapter-configuration/SKILL.md +62 -0
  88. package/skills/ai-core/chat-experience/SKILL.md +134 -0
  89. package/skills/ai-core/media-generation/SKILL.md +8 -0
  90. package/skills/ai-core/tool-calling/SKILL.md +103 -0
  91. package/src/activities/chat/adapter.ts +10 -0
  92. package/src/activities/chat/agents/define-agent.ts +20 -3
  93. package/src/activities/chat/agents/spawn.ts +20 -12
  94. package/src/activities/chat/index.ts +458 -99
  95. package/src/activities/chat/messages.ts +52 -6
  96. package/src/activities/chat/middleware/types.ts +1 -0
  97. package/src/activities/chat/stream/message-updaters.ts +27 -2
  98. package/src/activities/chat/stream/processor.ts +81 -49
  99. package/src/activities/chat/tools/tool-calls.ts +104 -9
  100. package/src/activities/chat/tools/tool-definition.ts +8 -0
  101. package/src/activities/embed/adapter.ts +7 -0
  102. package/src/activities/embed/index.ts +5 -0
  103. package/src/activities/evaluate/adapter.ts +4 -0
  104. package/src/activities/evaluate/index.ts +6 -0
  105. package/src/activities/files/adapter.ts +120 -0
  106. package/src/activities/files/index.ts +113 -0
  107. package/src/activities/generateImage/adapter.ts +8 -0
  108. package/src/activities/generateImage/index.ts +4 -0
  109. package/src/activities/generateVideo/adapter.ts +8 -0
  110. package/src/activities/generateVideo/index.ts +7 -0
  111. package/src/activities/generateWorld/adapter.ts +4 -2
  112. package/src/activities/generateWorld/index.ts +7 -6
  113. package/src/activities/index.ts +25 -1
  114. package/src/activities/summarize/chat-stream-summarize.ts +22 -12
  115. package/src/client.ts +8 -0
  116. package/src/index.ts +17 -0
  117. package/src/interrupt-resume.ts +55 -4
  118. package/src/middlewares/otel.ts +161 -3
  119. package/src/types.ts +114 -14
  120. package/src/utilities/ag-ui-wire.ts +72 -17
  121. package/src/utilities/content-source.ts +138 -0
  122. package/src/utilities/provider-executed.ts +13 -0
  123. package/src/utilities/tool-result.ts +45 -3
@@ -1,4 +1,4 @@
1
- import { ContentPart, UIMessage } from '../../../types.js';
1
+ import { ContentPart, ToolResultOutcome, UIMessage } from '../../../types.js';
2
2
  import { ToolCallState, ToolResultState } from './types.js';
3
3
  /**
4
4
  * Update or add a text part to a message.
@@ -22,7 +22,7 @@ export declare function updateToolCallPart(messages: Array<UIMessage>, messageId
22
22
  /**
23
23
  * Update or add a tool result part to a message.
24
24
  */
25
- export declare function updateToolResultPart(messages: Array<UIMessage>, messageId: string, toolCallId: string, content: string | Array<ContentPart>, state: ToolResultState, error?: string): Array<UIMessage>;
25
+ export declare function updateToolResultPart(messages: Array<UIMessage>, messageId: string, toolCallId: string, content: string | Array<ContentPart>, state: ToolResultState, error?: string, outcome?: ToolResultOutcome): Array<UIMessage>;
26
26
  /**
27
27
  * Update a tool call part with approval request metadata.
28
28
  */
@@ -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;
@@ -275,12 +276,19 @@ function updateThinkingPart(messages, messageId, stepId, content, signature) {
275
276
  return messages.map((msg) => {
276
277
  if (msg.id !== messageId) return msg;
277
278
  const parts = [...msg.parts];
278
- const thinkingPartIndex = parts.findIndex((p) => p.type === "thinking" && p.stepId === stepId);
279
+ let thinkingPartIndex = parts.findIndex((p) => p.type === "thinking" && p.stepId === stepId);
280
+ let adopted;
281
+ if (thinkingPartIndex < 0) {
282
+ thinkingPartIndex = parts.findIndex((p) => p.type === "thinking" && p.stepId === void 0);
283
+ const candidate = parts[thinkingPartIndex];
284
+ if (candidate?.type === "thinking") adopted = candidate;
285
+ }
286
+ const nextSignature = signature ?? adopted?.signature;
279
287
  const thinkingPart = {
280
288
  type: "thinking",
281
289
  content,
282
290
  stepId,
283
- ...signature && { signature }
291
+ ...nextSignature && { signature: nextSignature }
284
292
  };
285
293
  if (thinkingPartIndex >= 0) parts[thinkingPartIndex] = thinkingPart;
286
294
  else parts.push(thinkingPart);
@@ -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 const thinkingPartIndex = parts.findIndex(\n (p) => p.type === 'thinking' && p.stepId === stepId,\n )\n\n const thinkingPart: ThinkingPart = {\n type: 'thinking',\n content,\n stepId,\n ...(signature && { signature }),\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":"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"}
@@ -369,16 +369,17 @@ 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`, use it as the canonical arguments:
373
- * back-fill the accumulated string when no TOOL_CALL_ARGS deltas were seen
374
- * (adapters that deliver the whole input on END — e.g. Anthropic
375
- * server_tool_use / web_search — issue #839) and override the rendered part's
376
- * `input` with the canonical value.
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
  */
380
382
  private handleToolCallEndEvent;
381
- private extractToolResultError;
382
383
  /**
383
384
  * Handle TOOL_CALL_RESULT event (AG-UI spec).
384
385
  *
@@ -487,11 +488,11 @@ export declare class StreamProcessor {
487
488
  private completeToolCall;
488
489
  private isToolCallPartAwaitingUserAction;
489
490
  /**
490
- * Whether the rendered tool-call part for the given id has reached the
491
- * terminal 'error' state. Used to prevent the completion safety net from
492
- * downgrading a failed call back to 'input-complete'.
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'.
493
494
  */
494
- private isToolCallPartErrored;
495
+ private isToolCallPartTerminal;
495
496
  /**
496
497
  * Emit pending text update for a specific message.
497
498
  *
@@ -2,9 +2,9 @@ import { EventType } from "../../../types.js";
2
2
  import { mergeMetadata, tanstackMetadata } from "../../../utilities/merge-metadata.js";
3
3
  import { runErrorEventToError } from "../../../utilities/errors.js";
4
4
  import { INTERRUPT_BINDING_METADATA_KEY } from "../../../interrupt-resume.js";
5
- import { normalizeToolResult } from "../../../utilities/tool-result.js";
5
+ import { isAssistantSegmentOf, isProviderExecutedToolCall } from "../../../utilities/provider-executed.js";
6
+ import { isContentPartArray, isToolResultOutcome, normalizeToolResult, toolResultErrorText } from "../../../utilities/tool-result.js";
6
7
  import { splitSubagentWire } from "../../../utilities/subagent-wire.js";
7
- import { isProviderExecutedToolCall } from "../../../utilities/provider-executed.js";
8
8
  import { aguiContentToContentParts, aguiSnapshotMessageToUIMessage, coerceCreatedAt, generateMessageId, uiMessageToModelMessages } from "../messages.js";
9
9
  import { getChunkRunId } from "../../../utilities/chunk-ids.js";
10
10
  import { defaultJSONParser } from "./json-parser.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.
@@ -890,16 +906,23 @@ var StreamProcessor = class StreamProcessor {
890
906
  pending.push(msg);
891
907
  continue;
892
908
  }
909
+ let next = msg;
893
910
  if (msg.role === "assistant" && pending.length > 0 && !isToolResultOnly(msg)) {
894
- out.push({
911
+ next = {
895
912
  ...msg,
896
913
  parts: [...pending.flatMap(thinkingParts), ...msg.parts]
897
- });
914
+ };
898
915
  pending = [];
916
+ } else flushPending();
917
+ const prev = out.at(-1);
918
+ if (prev?.role === "assistant" && next.role === "assistant" && isAssistantSegmentOf(next.id, prev.id)) {
919
+ out[out.length - 1] = {
920
+ ...prev,
921
+ parts: [...prev.parts, ...next.parts]
922
+ };
899
923
  continue;
900
924
  }
901
- flushPending();
902
- out.push(msg);
925
+ out.push(next);
903
926
  }
904
927
  flushPending();
905
928
  return out;
@@ -965,7 +988,7 @@ var StreamProcessor = class StreamProcessor {
965
988
  } catch {
966
989
  output = result.content;
967
990
  }
968
- const errorText = result.state === "error" ? this.extractToolResultError(output) : void 0;
991
+ const errorText = result.state === "error" ? toolResultErrorText(output) : void 0;
969
992
  next = {
970
993
  ...next,
971
994
  output: errorText ? { error: errorText } : output,
@@ -1103,11 +1126,13 @@ var StreamProcessor = class StreamProcessor {
1103
1126
  * Handle TOOL_CALL_END event — arguments are finalized (input-complete).
1104
1127
  * Tool output arrives on TOOL_CALL_RESULT, not on this event.
1105
1128
  *
1106
- * If TOOL_CALL_END carries parsed `input`, use it as the canonical arguments:
1107
- * back-fill the accumulated string when no TOOL_CALL_ARGS deltas were seen
1108
- * (adapters that deliver the whole input on END — e.g. Anthropic
1109
- * server_tool_use / web_search — issue #839) and override the rendered part's
1110
- * `input` with the canonical value.
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.
1111
1136
  *
1112
1137
  * @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
1113
1138
  */
@@ -1119,12 +1144,11 @@ var StreamProcessor = class StreamProcessor {
1119
1144
  const input = chunk.input !== void 0 ? chunk.input : tanstackMetadata(chunk)?.input;
1120
1145
  const existingToolCall = msgState.toolCalls.get(chunk.toolCallId);
1121
1146
  if (existingToolCall && existingToolCall.state !== "input-complete") {
1122
- if (input !== void 0 && !existingToolCall.arguments) try {
1123
- existingToolCall.arguments = JSON.stringify(input);
1124
- } catch {}
1147
+ const serializedInput = serializeToolInput(input);
1148
+ if (serializedInput !== void 0) existingToolCall.arguments = serializedInput;
1125
1149
  const index = msgState.toolCallOrder.indexOf(chunk.toolCallId);
1126
1150
  this.completeToolCall(messageId, index, existingToolCall);
1127
- if (input !== void 0) {
1151
+ if (serializedInput !== void 0) {
1128
1152
  existingToolCall.parsedArguments = input;
1129
1153
  this.messages = updateToolCallPart(this.messages, messageId, {
1130
1154
  id: existingToolCall.id,
@@ -1138,10 +1162,6 @@ var StreamProcessor = class StreamProcessor {
1138
1162
  }
1139
1163
  }
1140
1164
  }
1141
- extractToolResultError(output) {
1142
- if (output && typeof output === "object" && "error" in output && typeof output.error === "string") return output.error;
1143
- return typeof output === "string" ? output : "Tool execution failed";
1144
- }
1145
1165
  /**
1146
1166
  * Handle TOOL_CALL_RESULT event (AG-UI spec).
1147
1167
  *
@@ -1152,7 +1172,10 @@ var StreamProcessor = class StreamProcessor {
1152
1172
  handleToolCallResultEvent(chunk) {
1153
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;
1154
1174
  if (!messageId) return;
1155
- const isOutputError = chunk.state === "output-error" || tanstackMetadata(chunk)?.state === "output-error";
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;
1156
1179
  let output;
1157
1180
  try {
1158
1181
  output = typeof chunk.content === "string" ? JSON.parse(chunk.content) : chunk.content;
@@ -1161,7 +1184,7 @@ var StreamProcessor = class StreamProcessor {
1161
1184
  }
1162
1185
  this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output, isOutputError ? "error" : void 0);
1163
1186
  const resultState = isOutputError ? "error" : "complete";
1164
- this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, aguiContentToContentParts(chunk.content), resultState, resultState === "error" ? this.extractToolResultError(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);
1165
1188
  this.emitMessagesChange();
1166
1189
  }
1167
1190
  /**
@@ -1527,7 +1550,7 @@ var StreamProcessor = class StreamProcessor {
1527
1550
  } catch {
1528
1551
  toolCall.parsedArguments = void 0;
1529
1552
  }
1530
- if (this.isToolCallPartErrored(toolCall.id)) return;
1553
+ if (this.isToolCallPartTerminal(toolCall.id)) return;
1531
1554
  if (this.isToolCallPartAwaitingUserAction(toolCall.id)) return;
1532
1555
  this.messages = updateToolCallPart(this.messages, messageId, {
1533
1556
  id: toolCall.id,
@@ -1544,12 +1567,12 @@ var StreamProcessor = class StreamProcessor {
1544
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")));
1545
1568
  }
1546
1569
  /**
1547
- * Whether the rendered tool-call part for the given id has reached the
1548
- * terminal 'error' state. Used to prevent the completion safety net from
1549
- * downgrading a failed call back to 'input-complete'.
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'.
1550
1573
  */
1551
- isToolCallPartErrored(toolCallId) {
1552
- 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")));
1553
1576
  }
1554
1577
  /**
1555
1578
  * Emit pending text update for a specific message.