@tanstack/ai 0.34.0 → 0.35.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.
@@ -1,3 +1,4 @@
1
+ import { Message as AGUIMessage } from '@ag-ui/core';
1
2
  import { ModelMessage, UIMessage } from '../../types.js';
2
3
  /**
3
4
  * Convert UIMessages or ModelMessages to ModelMessages
@@ -36,6 +37,23 @@ export declare function uiMessageToModelMessages(uiMessage: UIMessage): Array<Mo
36
37
  * @returns A UIMessage with parts
37
38
  */
38
39
  export declare function modelMessageToUIMessage(modelMessage: ModelMessage, id?: string): UIMessage;
40
+ /**
41
+ * Normalize a single AG-UI `MESSAGES_SNAPSHOT` message into a `UIMessage`.
42
+ *
43
+ * AG-UI snapshot messages use the wire shape `{ id, role, content }` and have
44
+ * no `parts` array. Casting them directly to `UIMessage` is unsafe: any code
45
+ * that later reads `message.parts` (e.g. the devtools `onToolCallStateChange`
46
+ * handler) crashes with "Cannot read properties of undefined (reading 'find')".
47
+ *
48
+ * Each role is mapped to the canonical `UIMessage` shape, reusing
49
+ * `modelMessageToUIMessage` for the roles that share `ModelMessage`'s structure.
50
+ * The original AG-UI `id` is preserved so later `TEXT_MESSAGE_CONTENT` /
51
+ * `TOOL_CALL_*` events still route by `messageId` (falling back to a generated
52
+ * id only when the snapshot omits one). Messages that already carry `parts`
53
+ * (e.g. a TanStack server echoing `UIMessage`s back over the wire) pass through
54
+ * unchanged apart from ensuring an id.
55
+ */
56
+ export declare function aguiSnapshotMessageToUIMessage(message: AGUIMessage | UIMessage): UIMessage;
39
57
  /**
40
58
  * Convert an array of ModelMessages to UIMessages
41
59
  *
@@ -247,6 +247,68 @@ function modelMessageToUIMessage(modelMessage, id) {
247
247
  parts
248
248
  };
249
249
  }
250
+ function aguiSnapshotMessageToUIMessage(message) {
251
+ if ("parts" in message) {
252
+ return { ...message, id: message.id || generateMessageId() };
253
+ }
254
+ const id = message.id || generateMessageId();
255
+ switch (message.role) {
256
+ case "user":
257
+ return {
258
+ id,
259
+ role: "user",
260
+ parts: aguiUserContentToParts(message.content)
261
+ };
262
+ case "assistant":
263
+ return modelMessageToUIMessage(
264
+ {
265
+ role: "assistant",
266
+ content: message.content ?? null,
267
+ ...message.toolCalls && { toolCalls: message.toolCalls }
268
+ },
269
+ id
270
+ );
271
+ case "tool":
272
+ return modelMessageToUIMessage(
273
+ {
274
+ role: "tool",
275
+ content: message.content,
276
+ toolCallId: message.toolCallId
277
+ },
278
+ id
279
+ );
280
+ case "system":
281
+ case "developer":
282
+ return {
283
+ id,
284
+ role: "system",
285
+ parts: message.content ? [{ type: "text", content: message.content }] : []
286
+ };
287
+ case "reasoning":
288
+ return {
289
+ id,
290
+ role: "assistant",
291
+ parts: message.content ? [{ type: "thinking", content: message.content }] : []
292
+ };
293
+ case "activity":
294
+ default:
295
+ return { id, role: "assistant", parts: [] };
296
+ }
297
+ }
298
+ function aguiUserContentToParts(content) {
299
+ if (typeof content === "string") {
300
+ return content ? [{ type: "text", content }] : [];
301
+ }
302
+ const parts = [];
303
+ for (const part of content) {
304
+ if (part.type === "text") {
305
+ parts.push({ type: "text", content: part.text });
306
+ } else if (part.type !== "binary") {
307
+ parts.push(part);
308
+ }
309
+ }
310
+ return parts;
311
+ }
250
312
  function modelMessagesToUIMessages(modelMessages) {
251
313
  const uiMessages = [];
252
314
  let currentAssistantMessage = null;
@@ -301,6 +363,7 @@ function generateMessageId() {
301
363
  return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`;
302
364
  }
303
365
  export {
366
+ aguiSnapshotMessageToUIMessage,
304
367
  convertMessagesToModelMessages,
305
368
  generateMessageId,
306
369
  modelMessageToUIMessage,
@@ -1 +1 @@
1
- {"version":3,"file":"messages.js","sources":["../../../../src/activities/chat/messages.ts"],"sourcesContent":["import { normalizeToolResult } from '../../utilities/tool-result'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n TextPart,\n ToolCallPart,\n UIMessage,\n} from '../../types'\n// ===========================\n// Message Converters\n// ===========================\n\n/**\n * Check if a MessagePart is a content part (text, image, audio, video, document)\n * that maps directly to a ModelMessage ContentPart.\n */\nfunction isContentPart(part: MessagePart): part is ContentPart {\n return (\n part.type === 'text' ||\n part.type === 'image' ||\n part.type === 'audio' ||\n part.type === 'video' ||\n part.type === 'document'\n )\n}\n\nfunction safeJsonStringify(value: unknown): string {\n try {\n return JSON.stringify(value)\n } catch {\n return ''\n }\n}\n\nfunction parseToolResultContent(content: string): unknown {\n try {\n return JSON.parse(content)\n } catch {\n return content\n }\n}\n\n/**\n * Collapse an array of ContentParts into the most compact ModelMessage content:\n * - Empty array → null\n * - All text parts → joined string (or null if empty)\n * - Mixed content → ContentPart array as-is\n */\nfunction collapseContentParts(\n parts: Array<ContentPart>,\n): string | null | Array<ContentPart> {\n if (parts.length === 0) return null\n\n const allText = parts.every((p) => p.type === 'text')\n if (allText) {\n const joined = parts.map((p) => p.content).join('')\n return joined || null\n }\n\n return parts\n}\n\n/**\n * Extract text content from ModelMessage content (string, null, or ContentPart array).\n * Used when only the text portion is needed (e.g., tool result content).\n */\nfunction getTextContent(content: string | null | Array<ContentPart>): string {\n if (content === null) return ''\n if (typeof content === 'string') return content\n return content\n .filter((part): part is TextPart => part.type === 'text')\n .map((part) => part.content)\n .join('')\n}\n\n/**\n * Convert UIMessages or ModelMessages to ModelMessages\n */\nexport function convertMessagesToModelMessages(\n messages: Array<UIMessage | ModelMessage>,\n): Array<ModelMessage> {\n // Pre-pass: collect toolCallIds already represented in anchor UIMessage parts.\n // Fan-out tool messages whose toolCallId matches an anchored ToolResultPart\n // are AG-UI duplicates and must be dropped to avoid double-feeding the LLM.\n const anchoredToolCallIds = new Set<string>()\n for (const msg of messages) {\n if ('parts' in msg) {\n for (const part of msg.parts) {\n if (part.type === 'tool-result') {\n anchoredToolCallIds.add(part.toolCallId)\n }\n }\n }\n }\n\n const modelMessages: Array<ModelMessage> = []\n for (const msg of messages) {\n if ('parts' in msg) {\n // UIMessage anchor — existing fan-out path\n modelMessages.push(...uiMessageToModelMessages(msg))\n continue\n }\n\n const role = (msg as { role: string }).role\n\n // AG-UI tool fan-out duplicate — drop if anchor already covers it\n if (\n role === 'tool' &&\n msg.toolCallId &&\n anchoredToolCallIds.has(msg.toolCallId)\n ) {\n continue\n }\n\n // AG-UI reasoning and activity — no ModelMessage equivalent today\n if (role === 'reasoning' || role === 'activity') {\n continue\n }\n\n // AG-UI developer — collapse to system\n if (role === 'developer') {\n modelMessages.push({\n role: 'system' as ModelMessage['role'],\n content: (msg as { content: string }).content,\n })\n continue\n }\n\n // Already a ModelMessage (user, assistant, system, tool with no anchor) — pass through\n modelMessages.push(msg)\n }\n return modelMessages\n}\n\n/**\n * Convert a UIMessage to ModelMessage(s)\n *\n * Walks the parts array IN ORDER to preserve the interleaving of text,\n * tool calls, and tool results. This is critical for multi-round tool\n * flows where the model generates text, calls a tool, gets the result,\n * then generates more text and calls another tool.\n *\n * The output preserves the sequential structure:\n * text1 → toolCall1 → toolResult1 → text2 → toolCall2 → toolResult2\n * becomes:\n * assistant: {content: \"text1\", toolCalls: [toolCall1]}\n * tool: toolResult1\n * assistant: {content: \"text2\", toolCalls: [toolCall2]}\n * tool: toolResult2\n *\n * @param uiMessage - The UIMessage to convert\n * @returns An array of ModelMessages preserving part ordering\n */\nexport function uiMessageToModelMessages(\n uiMessage: UIMessage,\n): Array<ModelMessage> {\n // Skip system messages - they're handled via systemPrompts, not ModelMessages\n if (uiMessage.role === 'system') {\n return []\n }\n\n // For non-assistant messages (user), use the simpler path since they\n // don't have tool calls or tool results to interleave\n if (uiMessage.role !== 'assistant') {\n return [buildUserOrToolMessage(uiMessage)]\n }\n\n // For assistant messages, walk parts in order to preserve interleaving\n return buildAssistantMessages(uiMessage)\n}\n\n/**\n * Build a single ModelMessage for user messages (simple path).\n * Preserves ordering of text and multimodal content parts.\n */\nfunction buildUserOrToolMessage(uiMessage: UIMessage): ModelMessage {\n const contentParts: Array<ContentPart> = []\n for (const part of uiMessage.parts) {\n if (isContentPart(part)) {\n contentParts.push(part)\n }\n }\n\n return {\n role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n }\n}\n\n// Accumulator for building an assistant segment (content + tool calls)\ninterface AssistantSegment {\n contentParts: Array<ContentPart>\n toolCalls: Array<{\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n /** Provider-specific metadata that round-trips with the tool call.\n * Untyped at this framework layer; adapters narrow it via their\n * `TToolCallMetadata` generic. */\n metadata?: unknown\n }>\n}\n\nfunction createSegment(): AssistantSegment {\n return { contentParts: [], toolCalls: [] }\n}\n\nfunction isToolCallIncluded(part: ToolCallPart): boolean {\n return (\n part.state === 'input-complete' ||\n part.state === 'complete' ||\n part.state === 'approval-responded' ||\n part.state === 'error' ||\n part.output !== undefined\n )\n}\n\n/**\n * Build ModelMessages for an assistant UIMessage, preserving the\n * sequential interleaving of text, tool calls, and tool results.\n *\n * Walks parts in order. Text and tool-call parts accumulate into the\n * current \"segment\". When a tool-result part is encountered, the\n * current segment is flushed as an assistant message, then the tool\n * result is emitted as a tool message.\n */\nfunction buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {\n const messageList: Array<ModelMessage> = []\n let current = createSegment()\n let pendingThinking: Array<{ content: string; signature?: string }> = []\n\n // Track emitted tool result IDs to avoid duplicates.\n // A tool call can have BOTH an explicit tool-result part AND an output\n // field on the tool-call part. We only want one per tool call ID.\n const emittedToolResultIds = new Set<string>()\n\n function flushSegment(): void {\n const content = collapseContentParts(current.contentParts)\n const hasContent = content !== null\n const hasToolCalls = current.toolCalls.length > 0\n\n if (hasContent || hasToolCalls) {\n messageList.push({\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\n })\n pendingThinking = []\n }\n current = createSegment()\n }\n\n for (const part of uiMessage.parts) {\n switch (part.type) {\n case 'text':\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n current.contentParts.push(part)\n break\n\n case 'tool-call':\n if (isToolCallIncluded(part)) {\n current.toolCalls.push({\n id: part.id,\n type: 'function' as const,\n function: {\n name: part.name,\n arguments: part.arguments,\n },\n ...(part.metadata !== undefined && { metadata: part.metadata }),\n })\n }\n break\n\n case 'tool-result':\n // Flush the current assistant segment before emitting the tool result\n flushSegment()\n\n // Emit the tool result\n if (\n (part.state === 'complete' || part.state === 'error') &&\n !emittedToolResultIds.has(part.toolCallId)\n ) {\n messageList.push({\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n })\n emittedToolResultIds.add(part.toolCallId)\n }\n break\n\n case 'thinking':\n if (part.content) {\n pendingThinking.push({\n content: part.content,\n ...(part.signature && { signature: part.signature }),\n })\n }\n break\n\n case 'structured-output':\n // Only emit completed structured responses into history. Streaming or\n // errored buffers would push malformed JSON into the next LLM turn's\n // assistant content. `raw` is the source of truth; `data` is the\n // defensive fallback for terminal-only completes that didn't ship raw.\n if (part.status === 'complete') {\n const serialized =\n part.raw !== ''\n ? part.raw\n : part.data !== undefined\n ? safeJsonStringify(part.data)\n : ''\n if (serialized !== '') {\n current.contentParts.push({ type: 'text', content: serialized })\n }\n }\n break\n\n default:\n break\n }\n }\n\n // Flush any remaining accumulated content\n flushSegment()\n\n // Emit tool results from client tool-call parts with output or approval,\n // but only if not already covered by an explicit tool-result part above.\n // These are appended at the end since they don't have explicit tool-result\n // parts in the parts array to trigger inline emission.\n for (const part of uiMessage.parts) {\n if (part.type !== 'tool-call') continue\n\n // Output takes priority — if the tool has already produced a result,\n // emit the concrete output regardless of approval metadata.\n if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {\n messageList.push({\n role: 'tool',\n content: normalizeToolResult(part.output),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response without output — emit approval status for iteration tracking\n if (\n part.output === undefined &&\n part.state === 'approval-responded' &&\n part.approval?.approved !== undefined &&\n !emittedToolResultIds.has(part.id)\n ) {\n const approved = part.approval.approved\n messageList.push({\n role: 'tool',\n content: JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n }),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n }\n\n // If no messages were produced (e.g., empty parts), emit a minimal assistant message\n if (messageList.length === 0) {\n messageList.push({\n role: 'assistant',\n content: null,\n })\n }\n\n return messageList\n}\n\n/**\n * Convert a ModelMessage to UIMessage\n *\n * This conversion creates a parts-based structure:\n * - content field → TextPart\n * - toolCalls array → ToolCallPart[]\n * - role=\"tool\" messages should be converted separately and merged\n *\n * @param modelMessage - The ModelMessage to convert\n * @param id - Optional ID for the UIMessage (generated if not provided)\n * @returns A UIMessage with parts\n */\nexport function modelMessageToUIMessage(\n modelMessage: ModelMessage,\n id?: string,\n): UIMessage {\n const parts: Array<MessagePart> = []\n\n if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {\n for (const thinking of modelMessage.thinking) {\n if (!thinking.content) continue\n parts.push({\n type: 'thinking',\n content: thinking.content,\n ...(thinking.signature && { signature: thinking.signature }),\n })\n }\n }\n\n // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n parts.push({\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n state: 'input-complete', // Model messages have complete arguments\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n }\n }\n\n return {\n id: id || generateMessageId(),\n role: modelMessage.role === 'tool' ? 'assistant' : modelMessage.role,\n parts,\n }\n}\n\n/**\n * Convert an array of ModelMessages to UIMessages\n *\n * This handles merging tool result messages with their corresponding assistant messages\n *\n * @param modelMessages - Array of ModelMessages to convert\n * @returns Array of UIMessages\n */\nexport function modelMessagesToUIMessages(\n modelMessages: Array<ModelMessage>,\n): Array<UIMessage> {\n const uiMessages: Array<UIMessage> = []\n let currentAssistantMessage: UIMessage | null = null\n\n for (const msg of modelMessages) {\n if (msg.role === 'tool') {\n // Tool result - merge into the last assistant message if possible\n if (\n msg.toolCallId !== undefined &&\n currentAssistantMessage &&\n currentAssistantMessage.role === 'assistant'\n ) {\n const content = getTextContent(msg.content)\n const toolCallPart = currentAssistantMessage.parts.find(\n (part): part is ToolCallPart =>\n part.type === 'tool-call' && part.id === msg.toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.output = parseToolResultContent(content)\n toolCallPart.state = 'complete'\n }\n\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId,\n content,\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message\n const uiMessage = modelMessageToUIMessage(msg)\n uiMessages.push(uiMessage)\n\n // Track assistant messages for potential tool result merging\n if (msg.role === 'assistant') {\n currentAssistantMessage = uiMessage\n } else {\n currentAssistantMessage = null\n }\n }\n }\n\n return uiMessages\n}\n\n/**\n * Normalize a message (UIMessage or ModelMessage) to a UIMessage\n * Ensures the message has an ID and createdAt timestamp\n *\n * @param message - Either a UIMessage or ModelMessage\n * @param generateId - Function to generate a message ID if needed\n * @returns A UIMessage with guaranteed id and createdAt\n */\nexport function normalizeToUIMessage(\n message: UIMessage | ModelMessage,\n generateId: () => string,\n): UIMessage {\n if ('parts' in message) {\n // Already a UIMessage\n return {\n ...message,\n id: message.id || generateId(),\n createdAt: message.createdAt || new Date(),\n }\n } else {\n // ModelMessage - convert to UIMessage\n return {\n ...modelMessageToUIMessage(message, generateId()),\n createdAt: new Date(),\n }\n }\n}\n\n/**\n * Generate a unique message ID\n */\nexport function generateMessageId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"messages.js","sources":["../../../../src/activities/chat/messages.ts"],"sourcesContent":["import { normalizeToolResult } from '../../utilities/tool-result'\nimport type { Message as AGUIMessage } from '@ag-ui/core'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n TextPart,\n ToolCallPart,\n UIMessage,\n} from '../../types'\n// ===========================\n// Message Converters\n// ===========================\n\n/**\n * Check if a MessagePart is a content part (text, image, audio, video, document)\n * that maps directly to a ModelMessage ContentPart.\n */\nfunction isContentPart(part: MessagePart): part is ContentPart {\n return (\n part.type === 'text' ||\n part.type === 'image' ||\n part.type === 'audio' ||\n part.type === 'video' ||\n part.type === 'document'\n )\n}\n\nfunction safeJsonStringify(value: unknown): string {\n try {\n return JSON.stringify(value)\n } catch {\n return ''\n }\n}\n\nfunction parseToolResultContent(content: string): unknown {\n try {\n return JSON.parse(content)\n } catch {\n return content\n }\n}\n\n/**\n * Collapse an array of ContentParts into the most compact ModelMessage content:\n * - Empty array → null\n * - All text parts → joined string (or null if empty)\n * - Mixed content → ContentPart array as-is\n */\nfunction collapseContentParts(\n parts: Array<ContentPart>,\n): string | null | Array<ContentPart> {\n if (parts.length === 0) return null\n\n const allText = parts.every((p) => p.type === 'text')\n if (allText) {\n const joined = parts.map((p) => p.content).join('')\n return joined || null\n }\n\n return parts\n}\n\n/**\n * Extract text content from ModelMessage content (string, null, or ContentPart array).\n * Used when only the text portion is needed (e.g., tool result content).\n */\nfunction getTextContent(content: string | null | Array<ContentPart>): string {\n if (content === null) return ''\n if (typeof content === 'string') return content\n return content\n .filter((part): part is TextPart => part.type === 'text')\n .map((part) => part.content)\n .join('')\n}\n\n/**\n * Convert UIMessages or ModelMessages to ModelMessages\n */\nexport function convertMessagesToModelMessages(\n messages: Array<UIMessage | ModelMessage>,\n): Array<ModelMessage> {\n // Pre-pass: collect toolCallIds already represented in anchor UIMessage parts.\n // Fan-out tool messages whose toolCallId matches an anchored ToolResultPart\n // are AG-UI duplicates and must be dropped to avoid double-feeding the LLM.\n const anchoredToolCallIds = new Set<string>()\n for (const msg of messages) {\n if ('parts' in msg) {\n for (const part of msg.parts) {\n if (part.type === 'tool-result') {\n anchoredToolCallIds.add(part.toolCallId)\n }\n }\n }\n }\n\n const modelMessages: Array<ModelMessage> = []\n for (const msg of messages) {\n if ('parts' in msg) {\n // UIMessage anchor — existing fan-out path\n modelMessages.push(...uiMessageToModelMessages(msg))\n continue\n }\n\n const role = (msg as { role: string }).role\n\n // AG-UI tool fan-out duplicate — drop if anchor already covers it\n if (\n role === 'tool' &&\n msg.toolCallId &&\n anchoredToolCallIds.has(msg.toolCallId)\n ) {\n continue\n }\n\n // AG-UI reasoning and activity — no ModelMessage equivalent today\n if (role === 'reasoning' || role === 'activity') {\n continue\n }\n\n // AG-UI developer — collapse to system\n if (role === 'developer') {\n modelMessages.push({\n role: 'system' as ModelMessage['role'],\n content: (msg as { content: string }).content,\n })\n continue\n }\n\n // Already a ModelMessage (user, assistant, system, tool with no anchor) — pass through\n modelMessages.push(msg)\n }\n return modelMessages\n}\n\n/**\n * Convert a UIMessage to ModelMessage(s)\n *\n * Walks the parts array IN ORDER to preserve the interleaving of text,\n * tool calls, and tool results. This is critical for multi-round tool\n * flows where the model generates text, calls a tool, gets the result,\n * then generates more text and calls another tool.\n *\n * The output preserves the sequential structure:\n * text1 → toolCall1 → toolResult1 → text2 → toolCall2 → toolResult2\n * becomes:\n * assistant: {content: \"text1\", toolCalls: [toolCall1]}\n * tool: toolResult1\n * assistant: {content: \"text2\", toolCalls: [toolCall2]}\n * tool: toolResult2\n *\n * @param uiMessage - The UIMessage to convert\n * @returns An array of ModelMessages preserving part ordering\n */\nexport function uiMessageToModelMessages(\n uiMessage: UIMessage,\n): Array<ModelMessage> {\n // Skip system messages - they're handled via systemPrompts, not ModelMessages\n if (uiMessage.role === 'system') {\n return []\n }\n\n // For non-assistant messages (user), use the simpler path since they\n // don't have tool calls or tool results to interleave\n if (uiMessage.role !== 'assistant') {\n return [buildUserOrToolMessage(uiMessage)]\n }\n\n // For assistant messages, walk parts in order to preserve interleaving\n return buildAssistantMessages(uiMessage)\n}\n\n/**\n * Build a single ModelMessage for user messages (simple path).\n * Preserves ordering of text and multimodal content parts.\n */\nfunction buildUserOrToolMessage(uiMessage: UIMessage): ModelMessage {\n const contentParts: Array<ContentPart> = []\n for (const part of uiMessage.parts) {\n if (isContentPart(part)) {\n contentParts.push(part)\n }\n }\n\n return {\n role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n }\n}\n\n// Accumulator for building an assistant segment (content + tool calls)\ninterface AssistantSegment {\n contentParts: Array<ContentPart>\n toolCalls: Array<{\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n /** Provider-specific metadata that round-trips with the tool call.\n * Untyped at this framework layer; adapters narrow it via their\n * `TToolCallMetadata` generic. */\n metadata?: unknown\n }>\n}\n\nfunction createSegment(): AssistantSegment {\n return { contentParts: [], toolCalls: [] }\n}\n\nfunction isToolCallIncluded(part: ToolCallPart): boolean {\n return (\n part.state === 'input-complete' ||\n part.state === 'complete' ||\n part.state === 'approval-responded' ||\n part.state === 'error' ||\n part.output !== undefined\n )\n}\n\n/**\n * Build ModelMessages for an assistant UIMessage, preserving the\n * sequential interleaving of text, tool calls, and tool results.\n *\n * Walks parts in order. Text and tool-call parts accumulate into the\n * current \"segment\". When a tool-result part is encountered, the\n * current segment is flushed as an assistant message, then the tool\n * result is emitted as a tool message.\n */\nfunction buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {\n const messageList: Array<ModelMessage> = []\n let current = createSegment()\n let pendingThinking: Array<{ content: string; signature?: string }> = []\n\n // Track emitted tool result IDs to avoid duplicates.\n // A tool call can have BOTH an explicit tool-result part AND an output\n // field on the tool-call part. We only want one per tool call ID.\n const emittedToolResultIds = new Set<string>()\n\n function flushSegment(): void {\n const content = collapseContentParts(current.contentParts)\n const hasContent = content !== null\n const hasToolCalls = current.toolCalls.length > 0\n\n if (hasContent || hasToolCalls) {\n messageList.push({\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\n })\n pendingThinking = []\n }\n current = createSegment()\n }\n\n for (const part of uiMessage.parts) {\n switch (part.type) {\n case 'text':\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n current.contentParts.push(part)\n break\n\n case 'tool-call':\n if (isToolCallIncluded(part)) {\n current.toolCalls.push({\n id: part.id,\n type: 'function' as const,\n function: {\n name: part.name,\n arguments: part.arguments,\n },\n ...(part.metadata !== undefined && { metadata: part.metadata }),\n })\n }\n break\n\n case 'tool-result':\n // Flush the current assistant segment before emitting the tool result\n flushSegment()\n\n // Emit the tool result\n if (\n (part.state === 'complete' || part.state === 'error') &&\n !emittedToolResultIds.has(part.toolCallId)\n ) {\n messageList.push({\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n })\n emittedToolResultIds.add(part.toolCallId)\n }\n break\n\n case 'thinking':\n if (part.content) {\n pendingThinking.push({\n content: part.content,\n ...(part.signature && { signature: part.signature }),\n })\n }\n break\n\n case 'structured-output':\n // Only emit completed structured responses into history. Streaming or\n // errored buffers would push malformed JSON into the next LLM turn's\n // assistant content. `raw` is the source of truth; `data` is the\n // defensive fallback for terminal-only completes that didn't ship raw.\n if (part.status === 'complete') {\n const serialized =\n part.raw !== ''\n ? part.raw\n : part.data !== undefined\n ? safeJsonStringify(part.data)\n : ''\n if (serialized !== '') {\n current.contentParts.push({ type: 'text', content: serialized })\n }\n }\n break\n\n default:\n break\n }\n }\n\n // Flush any remaining accumulated content\n flushSegment()\n\n // Emit tool results from client tool-call parts with output or approval,\n // but only if not already covered by an explicit tool-result part above.\n // These are appended at the end since they don't have explicit tool-result\n // parts in the parts array to trigger inline emission.\n for (const part of uiMessage.parts) {\n if (part.type !== 'tool-call') continue\n\n // Output takes priority — if the tool has already produced a result,\n // emit the concrete output regardless of approval metadata.\n if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {\n messageList.push({\n role: 'tool',\n content: normalizeToolResult(part.output),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response without output — emit approval status for iteration tracking\n if (\n part.output === undefined &&\n part.state === 'approval-responded' &&\n part.approval?.approved !== undefined &&\n !emittedToolResultIds.has(part.id)\n ) {\n const approved = part.approval.approved\n messageList.push({\n role: 'tool',\n content: JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n }),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n }\n\n // If no messages were produced (e.g., empty parts), emit a minimal assistant message\n if (messageList.length === 0) {\n messageList.push({\n role: 'assistant',\n content: null,\n })\n }\n\n return messageList\n}\n\n/**\n * Convert a ModelMessage to UIMessage\n *\n * This conversion creates a parts-based structure:\n * - content field → TextPart\n * - toolCalls array → ToolCallPart[]\n * - role=\"tool\" messages should be converted separately and merged\n *\n * @param modelMessage - The ModelMessage to convert\n * @param id - Optional ID for the UIMessage (generated if not provided)\n * @returns A UIMessage with parts\n */\nexport function modelMessageToUIMessage(\n modelMessage: ModelMessage,\n id?: string,\n): UIMessage {\n const parts: Array<MessagePart> = []\n\n if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {\n for (const thinking of modelMessage.thinking) {\n if (!thinking.content) continue\n parts.push({\n type: 'thinking',\n content: thinking.content,\n ...(thinking.signature && { signature: thinking.signature }),\n })\n }\n }\n\n // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n parts.push({\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n state: 'input-complete', // Model messages have complete arguments\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n }\n }\n\n return {\n id: id || generateMessageId(),\n role: modelMessage.role === 'tool' ? 'assistant' : modelMessage.role,\n parts,\n }\n}\n\n/**\n * Normalize a single AG-UI `MESSAGES_SNAPSHOT` message into a `UIMessage`.\n *\n * AG-UI snapshot messages use the wire shape `{ id, role, content }` and have\n * no `parts` array. Casting them directly to `UIMessage` is unsafe: any code\n * that later reads `message.parts` (e.g. the devtools `onToolCallStateChange`\n * handler) crashes with \"Cannot read properties of undefined (reading 'find')\".\n *\n * Each role is mapped to the canonical `UIMessage` shape, reusing\n * `modelMessageToUIMessage` for the roles that share `ModelMessage`'s structure.\n * The original AG-UI `id` is preserved so later `TEXT_MESSAGE_CONTENT` /\n * `TOOL_CALL_*` events still route by `messageId` (falling back to a generated\n * id only when the snapshot omits one). Messages that already carry `parts`\n * (e.g. a TanStack server echoing `UIMessage`s back over the wire) pass through\n * unchanged apart from ensuring an id.\n */\nexport function aguiSnapshotMessageToUIMessage(\n message: AGUIMessage | UIMessage,\n): UIMessage {\n if ('parts' in message) {\n return { ...message, id: message.id || generateMessageId() }\n }\n\n const id = message.id || generateMessageId()\n\n switch (message.role) {\n case 'user':\n return {\n id,\n role: 'user',\n parts: aguiUserContentToParts(message.content),\n }\n case 'assistant':\n return modelMessageToUIMessage(\n {\n role: 'assistant',\n content: message.content ?? null,\n ...(message.toolCalls && { toolCalls: message.toolCalls }),\n },\n id,\n )\n case 'tool':\n return modelMessageToUIMessage(\n {\n role: 'tool',\n content: message.content,\n toolCallId: message.toolCallId,\n },\n id,\n )\n case 'system':\n case 'developer':\n // `ModelMessage` has no system/developer role; build the part directly.\n return {\n id,\n role: 'system',\n parts: message.content\n ? [{ type: 'text', content: message.content }]\n : [],\n }\n case 'reasoning':\n return {\n id,\n role: 'assistant',\n parts: message.content\n ? [{ type: 'thinking', content: message.content }]\n : [],\n }\n case 'activity':\n default:\n // `activity` (and any future role) has no text/parts equivalent today.\n return { id, role: 'assistant', parts: [] }\n }\n}\n\n/**\n * Convert AG-UI user message content into `UIMessage` parts.\n *\n * AG-UI user content is either a plain string or a multimodal array whose text\n * entries use `{ type: 'text', text }` (vs. TanStack's `{ type: 'text', content }`).\n * Text entries are rewritten to the TanStack shape; image/audio/video/document\n * entries already match `ContentPart` and pass through. `binary` entries have no\n * TanStack equivalent and are dropped.\n */\nfunction aguiUserContentToParts(\n content: Extract<AGUIMessage, { role: 'user' }>['content'],\n): Array<MessagePart> {\n if (typeof content === 'string') {\n return content ? [{ type: 'text', content }] : []\n }\n\n const parts: Array<MessagePart> = []\n for (const part of content) {\n if (part.type === 'text') {\n parts.push({ type: 'text', content: part.text })\n } else if (part.type !== 'binary') {\n parts.push(part)\n }\n }\n return parts\n}\n\n/**\n * Convert an array of ModelMessages to UIMessages\n *\n * This handles merging tool result messages with their corresponding assistant messages\n *\n * @param modelMessages - Array of ModelMessages to convert\n * @returns Array of UIMessages\n */\nexport function modelMessagesToUIMessages(\n modelMessages: Array<ModelMessage>,\n): Array<UIMessage> {\n const uiMessages: Array<UIMessage> = []\n let currentAssistantMessage: UIMessage | null = null\n\n for (const msg of modelMessages) {\n if (msg.role === 'tool') {\n // Tool result - merge into the last assistant message if possible\n if (\n msg.toolCallId !== undefined &&\n currentAssistantMessage &&\n currentAssistantMessage.role === 'assistant'\n ) {\n const content = getTextContent(msg.content)\n const toolCallPart = currentAssistantMessage.parts.find(\n (part): part is ToolCallPart =>\n part.type === 'tool-call' && part.id === msg.toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.output = parseToolResultContent(content)\n toolCallPart.state = 'complete'\n }\n\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId,\n content,\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message\n const uiMessage = modelMessageToUIMessage(msg)\n uiMessages.push(uiMessage)\n\n // Track assistant messages for potential tool result merging\n if (msg.role === 'assistant') {\n currentAssistantMessage = uiMessage\n } else {\n currentAssistantMessage = null\n }\n }\n }\n\n return uiMessages\n}\n\n/**\n * Normalize a message (UIMessage or ModelMessage) to a UIMessage\n * Ensures the message has an ID and createdAt timestamp\n *\n * @param message - Either a UIMessage or ModelMessage\n * @param generateId - Function to generate a message ID if needed\n * @returns A UIMessage with guaranteed id and createdAt\n */\nexport function normalizeToUIMessage(\n message: UIMessage | ModelMessage,\n generateId: () => string,\n): UIMessage {\n if ('parts' in message) {\n // Already a UIMessage\n return {\n ...message,\n id: message.id || generateId(),\n createdAt: message.createdAt || new Date(),\n }\n } else {\n // ModelMessage - convert to UIMessage\n return {\n ...modelMessageToUIMessage(message, generateId()),\n createdAt: new Date(),\n }\n }\n}\n\n/**\n * Generate a unique message ID\n */\nexport function generateMessageId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { generateMessageId, uiMessageToModelMessages } from "../messages.js";
1
+ import { generateMessageId, uiMessageToModelMessages, aguiSnapshotMessageToUIMessage } from "../messages.js";
2
2
  import { normalizeToolResult } from "../../../utilities/tool-result.js";
3
3
  import { defaultJSONParser } from "./json-parser.js";
4
4
  import { updateToolCallWithOutput, updateToolResultPart, updateToolCallApprovalResponse, appendStructuredOutputDelta, updateToolCallPart, errorStructuredOutputPart, updateThinkingPart, completeStructuredOutputPart, updateToolCallApproval, updateTextPart } from "./message-updaters.js";
@@ -548,7 +548,7 @@ class StreamProcessor {
548
548
  */
549
549
  handleMessagesSnapshotEvent(chunk) {
550
550
  this.resetStreamState();
551
- this.messages = [...chunk.messages];
551
+ this.messages = chunk.messages.map(aguiSnapshotMessageToUIMessage);
552
552
  this.emitMessagesChange();
553
553
  }
554
554
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"processor.js","sources":["../../../../../src/activities/chat/stream/processor.ts"],"sourcesContent":["/**\n * Unified Stream Processor\n *\n * Core stream processing engine that manages the full UIMessage[] conversation.\n * Single source of truth for message state.\n *\n * Handles:\n * - Full conversation management (UIMessage[])\n * - Text content accumulation with configurable chunking strategies\n * - Parallel tool calls with lifecycle state tracking\n * - Tool results and approval flows\n * - Thinking/reasoning content\n * - Recording/replay for testing\n * - Event-driven architecture for UI updates\n * - Per-message stream state tracking for multi-message sessions\n *\n * @see docs/chat-architecture.md — Canonical reference for AG-UI chunk ordering,\n * adapter contract, single-shot flows, and expected UIMessage output.\n */\nimport { generateMessageId, uiMessageToModelMessages } from '../messages.js'\nimport { normalizeToolResult } from '../../../utilities/tool-result'\nimport { defaultJSONParser } from './json-parser'\nimport {\n appendStructuredOutputDelta,\n completeStructuredOutputPart,\n errorStructuredOutputPart,\n updateTextPart,\n updateThinkingPart,\n updateToolCallApproval,\n updateToolCallApprovalResponse,\n updateToolCallPart,\n updateToolCallWithOutput,\n updateToolResultPart,\n} from './message-updaters'\nimport { ImmediateStrategy } from './strategies'\nimport type {\n ChunkRecording,\n ChunkStrategy,\n InternalToolCallState,\n MessageStreamState,\n ProcessorResult,\n ProcessorState,\n ToolCallState,\n ToolResultState,\n} from './types'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StreamChunk,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n} from '../../../types'\n\n/**\n * Events emitted by the StreamProcessor\n */\nexport interface StreamProcessorEvents {\n // State events - full array on any change\n onMessagesChange?: (messages: Array<UIMessage>) => void\n\n // Lifecycle events\n onStreamStart?: () => void\n onStreamEnd?: (message: UIMessage) => void\n onError?: (error: Error) => void\n\n // Interaction events - client must handle these\n onToolCall?: (args: {\n toolCallId: string\n toolName: string\n input: any\n }) => void\n onApprovalRequest?: (args: {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n }) => void\n\n // Custom events from server-side tools\n onCustomEvent?: (\n eventType: string,\n data: unknown,\n context: { toolCallId?: string },\n ) => void\n\n // Granular events for UI optimization (character-by-character, state tracking)\n onTextUpdate?: (messageId: string, content: string) => void\n onToolCallStateChange?: (\n messageId: string,\n toolCallId: string,\n state: ToolCallState,\n args: string,\n ) => void\n onThinkingUpdate?: (\n messageId: string,\n stepId: string,\n content: string,\n ) => void\n onStructuredOutputChange?: (args: {\n phase: 'start' | 'update' | 'complete' | 'error'\n messageId: string\n status: 'streaming' | 'complete' | 'error'\n raw: string\n partial?: unknown\n data?: unknown\n reasoning?: string\n errorMessage?: string\n delta?: string\n }) => void\n}\n\n/**\n * Options for StreamProcessor\n */\nexport interface StreamProcessorOptions {\n chunkStrategy?: ChunkStrategy\n /** Event-driven handlers */\n events?: StreamProcessorEvents\n jsonParser?: {\n parse: (jsonString: string) => any\n }\n /** Enable recording for replay testing */\n recording?: boolean\n /** Initial messages to populate the processor */\n initialMessages?: Array<UIMessage>\n}\n\nconst STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE = 12\n\n/**\n * StreamProcessor - State machine for processing AI response streams\n *\n * Manages the full UIMessage[] conversation and emits events on changes.\n * Trusts the adapter contract: adapters emit clean AG-UI events in the\n * correct order.\n *\n * State tracking:\n * - Full message array\n * - Per-message stream state (text, tool calls, thinking)\n * - Multiple concurrent message streams\n * - Tool call completion via TOOL_CALL_END events\n *\n * @see docs/chat-architecture.md#streamprocessor-internal-state — State field reference\n * @see docs/chat-architecture.md#adapter-contract — What this class expects from adapters\n */\nexport class StreamProcessor {\n private readonly chunkStrategy: ChunkStrategy\n private readonly events: StreamProcessorEvents\n private readonly jsonParser: { parse: (jsonString: string) => any }\n private recordingEnabled: boolean\n\n // Message state\n private messages: Array<UIMessage> = []\n\n // Per-message stream state\n private readonly messageStates: Map<string, MessageStreamState> = new Map()\n private readonly activeMessageIds: Set<string> = new Set()\n private readonly toolCallToMessage: Map<string, string> = new Map()\n private pendingManualMessageId: string | null = null\n private pendingThinkingStepId: string | null = null\n\n private readonly structuredMessageIds: Set<string> = new Set()\n private readonly structuredOutputUpdateBatches = new Map<\n string,\n {\n delta: string\n chunkCount: number\n }\n >()\n\n // Run tracking (for concurrent run safety)\n private readonly activeRuns = new Set<string>()\n\n // Shared stream state\n private finishReason: string | null = null\n private hasError = false\n private isDone = false\n\n // Recording\n private recording: ChunkRecording | null = null\n private recordingStartTime = 0\n\n constructor(options: StreamProcessorOptions = {}) {\n this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy()\n this.events = options.events || {}\n this.jsonParser = options.jsonParser || defaultJSONParser\n this.recordingEnabled = options.recording ?? false\n\n // Initialize with provided messages\n if (options.initialMessages) {\n this.messages = [...options.initialMessages]\n }\n }\n\n // ============================================\n // Message Management Methods\n // ============================================\n\n /**\n * Set the messages array (e.g., from persisted state)\n */\n setMessages(messages: Array<UIMessage>): void {\n this.messages = [...messages]\n this.emitMessagesChange()\n }\n\n /**\n * Add a user message to the conversation.\n * Supports both simple string content and multimodal content arrays.\n *\n * @param content - The message content (string or array of content parts)\n * @param id - Optional custom message ID (generated if not provided)\n * @returns The created UIMessage\n *\n * @example\n * ```ts\n * // Simple text message\n * processor.addUserMessage('Hello!')\n *\n * // Multimodal message with image\n * processor.addUserMessage([\n * { type: 'text', content: 'What is in this image?' },\n * { type: 'image', source: { type: 'url', value: 'https://example.com/photo.jpg' } }\n * ])\n *\n * // With custom ID\n * processor.addUserMessage('Hello!', 'custom-id-123')\n * ```\n */\n addUserMessage(content: string | Array<ContentPart>, id?: string): UIMessage {\n // Convert content to message parts\n const parts: Array<MessagePart> =\n typeof content === 'string'\n ? [{ type: 'text', content }]\n : content.map((part) => {\n // ContentPart types (text, image, audio, video, document) are compatible with MessagePart\n return part\n })\n\n const userMessage: UIMessage = {\n id: id ?? generateMessageId(),\n role: 'user',\n parts,\n createdAt: new Date(),\n }\n\n this.messages = [...this.messages, userMessage]\n this.emitMessagesChange()\n\n return userMessage\n }\n\n /**\n * Prepare for a new assistant message stream.\n * Does NOT create the message immediately -- the message is created lazily\n * when the first content-bearing chunk arrives via ensureAssistantMessage().\n * This prevents empty assistant messages from flickering in the UI when\n * auto-continuation produces no content.\n */\n prepareAssistantMessage(): void {\n // Reset stream state for new message\n this.resetStreamState()\n }\n\n /**\n * @deprecated Use prepareAssistantMessage() instead. This eagerly creates\n * an assistant message which can cause empty message flicker.\n */\n startAssistantMessage(messageId?: string): string {\n this.prepareAssistantMessage()\n const { messageId: id } = this.ensureAssistantMessage(messageId)\n this.pendingManualMessageId = id\n return id\n }\n\n /**\n * Get the current assistant message ID (if one has been created).\n * Returns null if prepareAssistantMessage() was called but no content\n * has arrived yet.\n */\n getCurrentAssistantMessageId(): string | null {\n // Scan all message states (not just active) for the last assistant.\n // After finalizeStream() clears activeMessageIds, messageStates retains entries.\n // After reset() / resetStreamState(), messageStates is cleared → returns null.\n let lastId: string | null = null\n for (const [id, state] of this.messageStates) {\n if (state.role === 'assistant') {\n lastId = id\n }\n }\n return lastId\n }\n\n /**\n * Add a tool result (called by client after handling onToolCall)\n */\n addToolResult(toolCallId: string, output: any, error?: string): void {\n // Find the message containing this tool call\n const messageWithToolCall = this.messages.find((msg) =>\n msg.parts.some(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n ),\n )\n\n if (!messageWithToolCall) {\n console.warn(\n `[StreamProcessor] Could not find message with tool call ${toolCallId}`,\n )\n return\n }\n\n // Step 1: Update the tool-call part's output field (for UI rendering)\n let updatedMessages = updateToolCallWithOutput(\n this.messages,\n toolCallId,\n output,\n error ? 'error' : undefined,\n error,\n )\n\n // Step 2: Create a tool-result part (for LLM conversation history)\n const content = normalizeToolResult(output)\n const toolResultState: ToolResultState = error ? 'error' : 'complete'\n\n updatedMessages = updateToolResultPart(\n updatedMessages,\n messageWithToolCall.id,\n toolCallId,\n content,\n toolResultState,\n error,\n )\n\n this.messages = updatedMessages\n this.emitMessagesChange()\n }\n\n /**\n * Add an approval response (called by client after handling onApprovalRequest)\n */\n addToolApprovalResponse(approvalId: string, approved: boolean): void {\n this.messages = updateToolCallApprovalResponse(\n this.messages,\n approvalId,\n approved,\n )\n this.emitMessagesChange()\n }\n\n /**\n * Get the conversation as ModelMessages (for sending to LLM)\n */\n toModelMessages(): Array<ModelMessage> {\n const modelMessages: Array<ModelMessage> = []\n for (const msg of this.messages) {\n modelMessages.push(...uiMessageToModelMessages(msg))\n }\n return modelMessages\n }\n\n /**\n * Get current messages\n */\n getMessages(): Array<UIMessage> {\n return this.messages\n }\n\n /**\n * Check if all tool calls in the last assistant message are complete\n * Useful for auto-continue logic\n */\n areAllToolsComplete(): boolean {\n const lastAssistant = this.messages.findLast(\n (m: UIMessage) => m.role === 'assistant',\n )\n\n if (!lastAssistant) return true\n\n const toolParts = lastAssistant.parts.filter(\n (p): p is ToolCallPart => p.type === 'tool-call',\n )\n\n if (toolParts.length === 0) return true\n\n // Get tool result parts to check for server tool completion\n const toolResultIds = new Set(\n lastAssistant.parts\n .filter((p): p is ToolResultPart => p.type === 'tool-result')\n .map((p) => p.toolCallId),\n )\n\n // All tool calls must be in a terminal state\n // A tool call is complete if:\n // 1. It was approved/denied (approval-responded state)\n // 2. It has an output field set (client tool completed via addToolResult)\n // 3. It has a corresponding tool-result part (server tool completed)\n return toolParts.every(\n (part) =>\n part.state === 'complete' ||\n part.state === 'approval-responded' ||\n (part.output !== undefined && !part.approval) ||\n toolResultIds.has(part.id),\n )\n }\n\n /**\n * Remove messages after a certain index (for reload/retry)\n */\n removeMessagesAfter(index: number): void {\n const keptIds = new Set(this.messages.slice(0, index + 1).map((m) => m.id))\n // Drop routing state for messages that no longer exist; otherwise a\n // resumed stream (`reload()` or a server that reuses messageIds across\n // runs) could land deltas / tool args on stale map entries and corrupt\n // the new assistant message's parts. Mirror the four routing maps that\n // key on messageId: structuredMessageIds (custom-event routing),\n // messageStates (per-message stream state), toolCallToMessage (tool\n // args → message), and activeMessageIds (finalize / completeAllToolCalls\n // iteration targets — must not include phantoms).\n for (const id of this.structuredMessageIds) {\n if (!keptIds.has(id)) this.structuredMessageIds.delete(id)\n }\n for (const id of this.structuredOutputUpdateBatches.keys()) {\n if (!keptIds.has(id)) this.structuredOutputUpdateBatches.delete(id)\n }\n for (const id of this.messageStates.keys()) {\n if (!keptIds.has(id)) this.messageStates.delete(id)\n }\n for (const [toolCallId, msgId] of this.toolCallToMessage) {\n if (!keptIds.has(msgId)) this.toolCallToMessage.delete(toolCallId)\n }\n for (const id of this.activeMessageIds) {\n if (!keptIds.has(id)) this.activeMessageIds.delete(id)\n }\n this.messages = this.messages.slice(0, index + 1)\n this.emitMessagesChange()\n }\n\n /**\n * Clear all messages\n */\n clearMessages(): void {\n this.messages = []\n this.messageStates.clear()\n this.activeMessageIds.clear()\n this.toolCallToMessage.clear()\n this.structuredMessageIds.clear()\n this.structuredOutputUpdateBatches.clear()\n this.pendingManualMessageId = null\n this.emitMessagesChange()\n }\n\n // ============================================\n // Stream Processing Methods\n // ============================================\n\n /**\n * Process a stream and emit events through handlers\n */\n async process(stream: AsyncIterable<any>): Promise<ProcessorResult> {\n // Reset stream state (but keep messages)\n this.resetStreamState()\n\n // Start recording if enabled\n if (this.recordingEnabled) {\n this.startRecording()\n }\n\n // Process each chunk\n for await (const chunk of stream) {\n this.processChunk(chunk)\n }\n\n // Stream ended - finalize everything\n this.finalizeStream()\n\n // Finalize recording\n if (this.recording) {\n this.recording.result = this.getResult()\n }\n\n return this.getResult()\n }\n\n /**\n * Process a single chunk from the stream.\n *\n * Central dispatch for all AG-UI events. Each event type maps to a specific\n * handler. Events not listed in the switch are intentionally ignored\n * (STEP_STARTED, STATE_SNAPSHOT, STATE_DELTA).\n *\n * @see docs/chat-architecture.md#adapter-contract — Expected event types and ordering\n */\n processChunk(chunk: StreamChunk): void {\n // Record chunk if enabled\n if (this.recording) {\n this.recording.chunks.push({\n chunk,\n timestamp: Date.now(),\n index: this.recording.chunks.length,\n })\n }\n\n // Cast needed: @ag-ui/core Zod passthrough types add `& { [k: string]: unknown }`\n // which prevents TypeScript from narrowing the `type` discriminant in switch.\n const c = chunk\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType enum members vs string-literal case labels; default branch handles untraced events.\n switch (c.type) {\n // AG-UI Events\n case 'TEXT_MESSAGE_START':\n this.handleTextMessageStartEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,\n )\n break\n\n case 'TEXT_MESSAGE_CONTENT':\n this.handleTextMessageContentEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n )\n break\n\n case 'TEXT_MESSAGE_END':\n this.handleTextMessageEndEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_END' }>,\n )\n break\n\n case 'TOOL_CALL_START':\n this.handleToolCallStartEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n )\n break\n\n case 'TOOL_CALL_ARGS':\n this.handleToolCallArgsEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n )\n break\n\n case 'TOOL_CALL_END':\n this.handleToolCallEndEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n )\n break\n\n case 'RUN_FINISHED':\n this.handleRunFinishedEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n )\n break\n\n case 'RUN_ERROR':\n this.handleRunErrorEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n )\n break\n\n case 'STEP_FINISHED':\n this.handleStepFinishedEvent(\n chunk as Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n )\n break\n\n case 'MESSAGES_SNAPSHOT':\n this.handleMessagesSnapshotEvent(\n chunk as Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,\n )\n break\n\n case 'CUSTOM':\n this.handleCustomEvent(\n chunk as Extract<StreamChunk, { type: 'CUSTOM' }>,\n )\n break\n\n case 'RUN_STARTED':\n this.handleRunStartedEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_STARTED' }>,\n )\n break\n\n case 'REASONING_START':\n case 'REASONING_MESSAGE_START':\n case 'REASONING_MESSAGE_END':\n case 'REASONING_END':\n // No special handling needed\n break\n\n case 'REASONING_MESSAGE_CONTENT':\n this.handleReasoningMessageContentEvent(\n chunk as Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,\n )\n break\n\n case 'TOOL_CALL_RESULT':\n this.handleToolCallResultEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_RESULT' }>,\n )\n break\n\n case 'STEP_STARTED':\n this.handleStepStartedEvent(\n chunk as Extract<StreamChunk, { type: 'STEP_STARTED' }>,\n )\n break\n\n default:\n // STATE_SNAPSHOT, STATE_DELTA - no special handling needed\n break\n }\n }\n\n // ============================================\n // Per-Message State Helpers\n // ============================================\n\n /**\n * Create a new MessageStreamState for a message\n */\n private createMessageState(\n messageId: string,\n role: 'user' | 'assistant' | 'system',\n ): MessageStreamState {\n const state: MessageStreamState = {\n id: messageId,\n role,\n totalTextContent: '',\n currentSegmentText: '',\n lastEmittedText: '',\n hasSeenReasoningEvents: false,\n thinkingSteps: new Map(),\n thinkingStepSignatures: new Map(),\n thinkingStepOrder: [],\n currentThinkingStepId: null,\n toolCalls: new Map(),\n toolCallOrder: [],\n hasToolCallsSinceTextStart: false,\n isComplete: false,\n }\n this.messageStates.set(messageId, state)\n return state\n }\n\n /**\n * Get the MessageStreamState for a message\n */\n private getMessageState(messageId: string): MessageStreamState | undefined {\n return this.messageStates.get(messageId)\n }\n\n /**\n * Promote a pending stepId from a STEP_STARTED that fired before the\n * assistant message existed onto the given message state, so the next\n * thinking event (STEP_FINISHED or REASONING_MESSAGE_CONTENT) attributes\n * to the correct step.\n */\n private consumePendingThinkingStep(state: MessageStreamState): void {\n if (!this.pendingThinkingStepId) return\n const stepId = this.pendingThinkingStepId\n state.currentThinkingStepId = stepId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n }\n this.pendingThinkingStepId = null\n }\n\n /**\n * Get the most recent active assistant message ID.\n * Used as fallback for events that don't include a messageId.\n */\n private getActiveAssistantMessageId(): string | null {\n // Set iteration is insertion-order; reverse-iterate to search from the end\n const ids = Array.from(this.activeMessageIds).reverse()\n for (const id of ids) {\n const state = this.messageStates.get(id)\n if (state && state.role === 'assistant') {\n return id\n }\n }\n return null\n }\n\n /**\n * Ensure an active assistant message exists, creating one if needed.\n * Used for backward compat when events arrive without prior TEXT_MESSAGE_START.\n *\n * On reconnect/resume, a TEXT_MESSAGE_CONTENT may arrive for a message that\n * already exists in this.messages (e.g. from initialMessages or a prior\n * MESSAGES_SNAPSHOT) but whose transient state was cleared. In that case we\n * hydrate state from the existing message rather than creating a duplicate.\n */\n private ensureAssistantMessage(preferredId?: string): {\n messageId: string\n state: MessageStreamState\n } {\n // Try to find state by preferred ID\n if (preferredId) {\n const state = this.getMessageState(preferredId)\n if (state) return { messageId: preferredId, state }\n }\n\n // Try active assistant message\n const activeId = this.getActiveAssistantMessageId()\n if (activeId) {\n const state = this.getMessageState(activeId)\n if (state) return { messageId: activeId, state }\n }\n\n // Check if a message with preferredId already exists (reconnect/resume case).\n // Hydrate transient state from the existing message instead of duplicating it.\n if (preferredId) {\n const existingMsg = this.messages.find((m) => m.id === preferredId)\n if (existingMsg) {\n const state = this.createMessageState(preferredId, existingMsg.role)\n this.activeMessageIds.add(preferredId)\n\n // Seed segment text from the existing last text part so that\n // incoming deltas append correctly and updateTextPart produces\n // the right content (existing text + new delta).\n const lastPart =\n existingMsg.parts.length > 0\n ? existingMsg.parts[existingMsg.parts.length - 1]\n : null\n if (lastPart && lastPart.type === 'text') {\n state.currentSegmentText = lastPart.content\n state.lastEmittedText = lastPart.content\n state.totalTextContent = lastPart.content\n }\n\n return { messageId: preferredId, state }\n }\n }\n\n // Auto-create an assistant message (backward compat for process() without TEXT_MESSAGE_START)\n const id = preferredId || generateMessageId()\n const assistantMessage: UIMessage = {\n id,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n this.messages = [...this.messages, assistantMessage]\n const state = this.createMessageState(id, 'assistant')\n this.activeMessageIds.add(id)\n this.pendingManualMessageId = id\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n return { messageId: id, state }\n }\n\n // ============================================\n // Event Handlers\n // ============================================\n\n /**\n * Handle TEXT_MESSAGE_START event\n */\n private handleTextMessageStartEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,\n ): void {\n const { messageId, role } = chunk\n\n // Map 'tool' and 'developer' roles to 'assistant' for both UIMessage and MessageStreamState\n // (UIMessage doesn't support 'tool'/'developer' role, and lookups like\n // getActiveAssistantMessageId() check state.role === 'assistant')\n const uiRole: 'system' | 'user' | 'assistant' =\n role === 'user' || role === 'system' ? role : 'assistant'\n\n // Case 1: A manual message was created via startAssistantMessage()\n if (this.pendingManualMessageId) {\n const pendingId = this.pendingManualMessageId\n this.pendingManualMessageId = null\n\n if (pendingId !== messageId) {\n // Update the message's ID in the messages array\n this.messages = this.messages.map((msg) =>\n msg.id === pendingId ? { ...msg, id: messageId } : msg,\n )\n\n // Move state to the new key\n const existingState = this.messageStates.get(pendingId)\n if (existingState) {\n existingState.id = messageId\n this.messageStates.delete(pendingId)\n this.messageStates.set(messageId, existingState)\n }\n\n // Update activeMessageIds\n this.activeMessageIds.delete(pendingId)\n this.activeMessageIds.add(messageId)\n }\n\n // Ensure state exists\n if (!this.messageStates.has(messageId)) {\n this.createMessageState(messageId, uiRole)\n this.activeMessageIds.add(messageId)\n }\n\n this.emitMessagesChange()\n return\n }\n\n // Case 2: Message already exists (dedup)\n const existingMsg = this.messages.find((m) => m.id === messageId)\n if (existingMsg) {\n this.activeMessageIds.add(messageId)\n const existingState = this.messageStates.get(messageId)\n if (!existingState) {\n this.createMessageState(messageId, uiRole)\n } else {\n // If tool calls happened since last text, this TEXT_MESSAGE_START\n // signals a new text segment — reset segment accumulation\n if (existingState.hasToolCallsSinceTextStart) {\n if (\n existingState.currentSegmentText !== existingState.lastEmittedText\n ) {\n this.emitTextUpdateForMessage(messageId)\n }\n existingState.currentSegmentText = ''\n existingState.lastEmittedText = ''\n existingState.hasToolCallsSinceTextStart = false\n }\n }\n return\n }\n\n // Case 3: New message from the stream\n const newMessage: UIMessage = {\n id: messageId,\n role: uiRole,\n parts: [],\n createdAt: new Date(),\n }\n\n this.messages = [...this.messages, newMessage]\n this.createMessageState(messageId, uiRole)\n this.activeMessageIds.add(messageId)\n\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n }\n\n /**\n * Handle TEXT_MESSAGE_END event\n */\n private handleTextMessageEndEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_END' }>,\n ): void {\n const { messageId } = chunk\n const state = this.getMessageState(messageId)\n if (!state) return\n if (state.isComplete) return\n\n // Emit any pending text for this message\n if (state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n\n // Complete all tool calls for this message\n this.completeAllToolCallsForMessage(messageId)\n }\n\n /**\n * Handle MESSAGES_SNAPSHOT event\n */\n private handleMessagesSnapshotEvent(\n chunk: Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,\n ): void {\n this.resetStreamState()\n // AG-UI Message[] is compatible with UIMessage[] at runtime\n this.messages = [...chunk.messages] as Array<UIMessage>\n this.emitMessagesChange()\n }\n\n /**\n * Handle TEXT_MESSAGE_CONTENT event.\n *\n * Accumulates delta into both currentSegmentText (for UI emission) and\n * totalTextContent (for ProcessorResult). Lazily creates the assistant\n * UIMessage on first content. Uses updateTextPart() which replaces the\n * last TextPart or creates a new one depending on part ordering.\n *\n * @see docs/chat-architecture.md#single-shot-text-response — Text accumulation step-by-step\n * @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic\n */\n private handleTextMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(chunk.messageId)\n\n // Content arriving means all current tool calls for this message are complete\n this.completeAllToolCallsForMessage(messageId)\n\n if (this.structuredMessageIds.has(messageId)) {\n // `chunk.delta` is incremental; `chunk.content` is sometimes cumulative\n // (mirrors what the plain-text branch handles below). Reconcile against\n // the existing raw buffer so adapters that emit cumulative content\n // don't duplicate the JSON.\n let delta = chunk.delta || ''\n if (delta === '' && chunk.content !== undefined && chunk.content !== '') {\n const existingRaw = (\n this.messages\n .find((m) => m.id === messageId)\n ?.parts.find(\n (p): p is Extract<MessagePart, { type: 'structured-output' }> =>\n p.type === 'structured-output',\n ) ?? { raw: '' }\n ).raw\n if (chunk.content.startsWith(existingRaw)) {\n delta = chunk.content.slice(existingRaw.length)\n } else if (existingRaw.startsWith(chunk.content)) {\n delta = ''\n } else {\n delta = chunk.content\n }\n }\n if (delta !== '') {\n this.messages = appendStructuredOutputDelta(\n this.messages,\n messageId,\n delta,\n )\n state.totalTextContent += delta\n this.queueStructuredOutputUpdate(messageId, delta)\n this.emitMessagesChange()\n }\n return\n }\n\n const previousSegment = state.currentSegmentText\n\n // Detect if this is a NEW text segment (after tool calls) vs continuation\n const isNewSegment =\n state.hasToolCallsSinceTextStart &&\n previousSegment.length > 0 &&\n this.isNewTextSegment(chunk, previousSegment)\n\n if (isNewSegment) {\n // Emit any accumulated text before starting new segment\n if (previousSegment !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n // Reset SEGMENT text accumulation for the new text segment after tool calls\n state.currentSegmentText = ''\n state.lastEmittedText = ''\n state.hasToolCallsSinceTextStart = false\n }\n\n const currentText = state.currentSegmentText\n let nextText = currentText\n\n // Prefer delta over content - delta is the incremental change\n // Normalize to empty string to avoid \"undefined\" string concatenation\n const delta = chunk.delta || ''\n if (delta !== '') {\n nextText = currentText + delta\n } else if (chunk.content !== undefined && chunk.content !== '') {\n // Fallback: use content if delta is not provided\n if (chunk.content.startsWith(currentText)) {\n nextText = chunk.content\n } else if (currentText.startsWith(chunk.content)) {\n nextText = currentText\n } else {\n nextText = currentText + chunk.content\n }\n }\n\n // Calculate the delta for totalTextContent\n const textDelta = nextText.slice(currentText.length)\n state.currentSegmentText = nextText\n state.totalTextContent += textDelta\n\n // Use delta for chunk strategy if available\n const chunkPortion = chunk.delta || chunk.content || ''\n const shouldEmit = this.chunkStrategy.shouldEmit(\n chunkPortion,\n state.currentSegmentText,\n )\n if (shouldEmit && state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n }\n\n /**\n * Handle TOOL_CALL_START event.\n *\n * Creates a new InternalToolCallState entry in the toolCalls Map and appends\n * a ToolCallPart to the UIMessage. Duplicate toolCallId is a no-op.\n *\n * CRITICAL: This MUST be received before any TOOL_CALL_ARGS for the same\n * toolCallId. Args for unknown IDs are silently dropped.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Tool call state transitions\n * @see docs/chat-architecture.md#parallel-tool-calls-single-shot — Parallel tracking by ID\n * @see docs/chat-architecture.md#adapter-contract — Ordering requirements\n */\n private handleToolCallStartEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n ): void {\n // Determine the message this tool call belongs to\n const targetMessageId =\n chunk.parentMessageId ?? this.getActiveAssistantMessageId()\n const { messageId, state } = this.ensureAssistantMessage(\n targetMessageId ?? undefined,\n )\n\n // Mark that we've seen tool calls since the last text segment\n state.hasToolCallsSinceTextStart = true\n\n const toolCallId = chunk.toolCallId\n const existingToolCall = state.toolCalls.get(toolCallId)\n\n if (!existingToolCall) {\n // New tool call starting\n const initialState: ToolCallState = 'awaiting-input'\n\n // `toolName` is a deprecated alias for `toolCallName` (see ToolCallStartEvent\n // in types.ts). Accept either so chunks from older code paths or any\n // adapter that only sets the deprecated field still produce a named part.\n // The type marks both as required strings, but in practice some emitters\n // only set one — fall back via the runtime value rather than the type.\n const toolName =\n (chunk as { toolCallName?: string }).toolCallName ?? chunk.toolName\n\n // Capture provider metadata that arrived on TOOL_CALL_START so it\n // round-trips back through the assistant message on the next turn\n // (e.g. Gemini's thoughtSignature).\n const chunkMetadata = chunk.metadata\n\n const newToolCall: InternalToolCallState = {\n id: chunk.toolCallId,\n name: toolName,\n arguments: '',\n state: initialState,\n parsedArguments: undefined,\n index: chunk.index ?? state.toolCalls.size,\n ...(chunkMetadata !== undefined && { metadata: chunkMetadata }),\n }\n\n state.toolCalls.set(toolCallId, newToolCall)\n state.toolCallOrder.push(toolCallId)\n\n // Store mapping for TOOL_CALL_ARGS/END routing\n this.toolCallToMessage.set(toolCallId, messageId)\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: chunk.toolCallId,\n name: toolName,\n arguments: '',\n state: initialState,\n ...(chunkMetadata !== undefined && { metadata: chunkMetadata }),\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n chunk.toolCallId,\n initialState,\n '',\n )\n }\n }\n\n /**\n * Handle TOOL_CALL_ARGS event.\n *\n * Appends the delta to the tool call's accumulated arguments string.\n * Transitions state from awaiting-input → input-streaming on first non-empty delta.\n * Attempts partial JSON parse on each update for UI preview.\n *\n * If toolCallId is not found in the Map (no preceding TOOL_CALL_START),\n * this event is silently dropped.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Step-by-step tool call processing\n */\n private handleToolCallArgsEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n ): void {\n const toolCallId = chunk.toolCallId\n const messageId = this.toolCallToMessage.get(toolCallId)\n if (!messageId) return\n\n const state = this.getMessageState(messageId)\n if (!state) return\n\n const existingToolCall = state.toolCalls.get(toolCallId)\n if (!existingToolCall) return\n\n const wasAwaitingInput = existingToolCall.state === 'awaiting-input'\n\n // Accumulate arguments from delta\n existingToolCall.arguments += chunk.delta || ''\n\n // Update state\n if (wasAwaitingInput && chunk.delta) {\n existingToolCall.state = 'input-streaming'\n }\n\n // Try to parse the updated arguments\n existingToolCall.parsedArguments = this.jsonParser.parse(\n existingToolCall.arguments,\n )\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: existingToolCall.id,\n name: existingToolCall.name,\n arguments: existingToolCall.arguments,\n state: existingToolCall.state,\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n existingToolCall.id,\n existingToolCall.state,\n existingToolCall.arguments,\n )\n }\n\n /**\n * Handle TOOL_CALL_END event — authoritative signal that a tool call's input is finalized.\n *\n * This event has a DUAL ROLE:\n * - Without `result`: Signals arguments are done (from adapter). Transitions to input-complete.\n * - With `result`: Signals tool was executed and result is available (from TextEngine).\n * Creates both output on the tool-call part AND a tool-result part.\n *\n * If `input` is provided, it overrides the accumulated string parse as the\n * canonical parsed arguments.\n *\n * @see docs/chat-architecture.md#tool-results-and-the-tool_call_end-dual-role — Full explanation\n * @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow\n */\n private handleToolCallEndEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n ): void {\n const messageId = this.toolCallToMessage.get(chunk.toolCallId)\n if (!messageId) return\n\n const msgState = this.getMessageState(messageId)\n if (!msgState) return\n\n // Transition the tool call to input-complete (the authoritative completion signal)\n const existingToolCall = msgState.toolCalls.get(chunk.toolCallId)\n if (existingToolCall && existingToolCall.state !== 'input-complete') {\n // If TOOL_CALL_END provides parsed input and no TOOL_CALL_ARGS were\n // received, back-fill the arguments string so the UIMessage ToolCallPart\n // carries the correct value (defensive against adapters that skip ARGS).\n if (chunk.input !== undefined && !existingToolCall.arguments) {\n existingToolCall.arguments = JSON.stringify(chunk.input)\n }\n\n const index = msgState.toolCallOrder.indexOf(chunk.toolCallId)\n this.completeToolCall(messageId, index, existingToolCall)\n // If TOOL_CALL_END provides parsed input, use it as the canonical parsed\n // arguments (overrides the accumulated string parse from completeToolCall)\n if (chunk.input !== undefined) {\n existingToolCall.parsedArguments = chunk.input\n }\n }\n\n // Update UIMessage if there's a result\n if (chunk.result) {\n // Step 1: Update the tool-call part's output field (for UI consistency\n // with client tools — see GitHub issue #176)\n let output: unknown\n if (Array.isArray(chunk.result)) {\n output = chunk.result\n } else {\n try {\n output = JSON.parse(chunk.result)\n } catch {\n output = chunk.result\n }\n }\n this.messages = updateToolCallWithOutput(\n this.messages,\n chunk.toolCallId,\n output,\n chunk.state === 'output-error' ? 'error' : undefined,\n )\n\n // Step 2: Create/update the tool-result part (for LLM conversation history)\n const resultState: ToolResultState =\n chunk.state === 'output-error' ? 'error' : 'complete'\n this.messages = updateToolResultPart(\n this.messages,\n messageId,\n chunk.toolCallId,\n chunk.result,\n resultState,\n resultState === 'error'\n ? this.extractToolResultError(output)\n : undefined,\n )\n this.emitMessagesChange()\n }\n }\n\n private extractToolResultError(output: unknown): string {\n if (\n output &&\n typeof output === 'object' &&\n 'error' in output &&\n typeof output.error === 'string'\n ) {\n return output.error\n }\n return typeof output === 'string' ? output : 'Tool execution failed'\n }\n\n /**\n * Handle TOOL_CALL_RESULT event (AG-UI spec).\n *\n * Creates a tool-result part and updates the tool-call output field,\n * mirroring the logic from TOOL_CALL_END when it carries a result.\n * This is the spec-compliant path for delivering tool results to the client.\n */\n private handleToolCallResultEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_RESULT' }>,\n ): void {\n const messageId = this.toolCallToMessage.get(chunk.toolCallId)\n if (!messageId) return\n\n // Step 1: Update the tool-call part's output field\n let output: unknown\n try {\n output = JSON.parse(chunk.content)\n } catch {\n output = chunk.content\n }\n this.messages = updateToolCallWithOutput(\n this.messages,\n chunk.toolCallId,\n output,\n chunk.state === 'output-error' ? 'error' : undefined,\n )\n\n // Step 2: Create/update the tool-result part\n const resultState: ToolResultState =\n chunk.state === 'output-error' ? 'error' : 'complete'\n this.messages = updateToolResultPart(\n this.messages,\n messageId,\n chunk.toolCallId,\n chunk.content,\n resultState,\n resultState === 'error' ? this.extractToolResultError(output) : undefined,\n )\n this.emitMessagesChange()\n }\n\n /**\n * Handle RUN_STARTED event.\n *\n * Registers the run so that RUN_FINISHED can determine whether other\n * runs are still active before finalizing.\n */\n private handleRunStartedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_STARTED' }>,\n ): void {\n this.activeRuns.add(chunk.runId)\n }\n\n /**\n * Handle RUN_FINISHED event.\n *\n * Records the finishReason and removes the run from activeRuns.\n * Only finalizes when no more runs are active, so that concurrent\n * runs don't interfere with each other.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — finishReason semantics\n * @see docs/chat-architecture.md#adapter-contract — Why RUN_FINISHED is mandatory\n */\n private handleRunFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n ): void {\n this.finishReason = chunk.finishReason ?? null\n this.activeRuns.delete(chunk.runId)\n\n if (this.activeRuns.size === 0) {\n this.isDone = true\n this.completeAllToolCalls()\n this.finalizeStream()\n }\n }\n\n /**\n * Handle RUN_ERROR event\n */\n private handleRunErrorEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n ): void {\n this.hasError = true\n const runId =\n 'runId' in chunk && typeof chunk.runId === 'string'\n ? chunk.runId\n : undefined\n if (runId) {\n this.activeRuns.delete(runId)\n } else {\n this.activeRuns.clear()\n }\n const { messageId } = this.ensureAssistantMessage()\n // Prefer spec field `message`; fall back to deprecated `error.message`.\n // If neither is set, the chunk still carries debug context (provider\n // error codes, request ids, etc.) — log it so the failure isn't silent.\n const errorMessage =\n chunk.message || chunk.error?.message || 'An error occurred'\n if (!chunk.message && !chunk.error?.message) {\n console.error(\n '[StreamProcessor] RUN_ERROR with no message; original chunk:',\n chunk,\n )\n }\n\n if (this.structuredMessageIds.has(messageId)) {\n this.flushStructuredOutputUpdate(messageId)\n this.messages = errorStructuredOutputPart(\n this.messages,\n messageId,\n errorMessage,\n )\n this.structuredMessageIds.delete(messageId)\n this.emitStructuredOutputChange(messageId, 'error')\n this.emitMessagesChange()\n }\n\n // Attach the provider's structured error body (`rawEvent`) and `code` to\n // the surfaced Error so consumers can recover the upstream detail that the\n // RUN_ERROR's `message` alone discards. Both are optional and added only\n // when present, keeping the Error backward compatible.\n const error = new Error(errorMessage)\n const code = chunk.code ?? chunk.error?.code\n if (code !== undefined) {\n Object.assign(error, { code })\n }\n if (chunk.rawEvent !== undefined) {\n Object.assign(error, { rawEvent: chunk.rawEvent })\n }\n this.events.onError?.(error)\n }\n\n /**\n * Handle STEP_STARTED event (for thinking/reasoning content).\n *\n * Records the stepId so that subsequent STEP_FINISHED deltas accumulate\n * into their own ThinkingPart. Does not create a message — the message\n * is lazily created when the first STEP_FINISHED content arrives.\n */\n private handleStepStartedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_STARTED' }>,\n ): void {\n const stepId = chunk.stepId ?? generateMessageId()\n const activeId = this.getActiveAssistantMessageId()\n if (activeId) {\n const state = this.getMessageState(activeId)\n if (state) {\n state.currentThinkingStepId = stepId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n }\n // Clear any pending stepId from a prior STEP_STARTED that fired\n // before the assistant message existed. Now that we're tracking\n // the step directly on message state, the pending value is stale\n // and must not leak into the next STEP_FINISHED (which would\n // misattribute its delta to the stale step).\n this.pendingThinkingStepId = null\n return\n }\n }\n\n // No active message yet — defer until ensureAssistantMessage in STEP_FINISHED\n this.pendingThinkingStepId = stepId\n }\n\n /**\n * Handle STEP_FINISHED event (for thinking/reasoning content).\n *\n * Accumulates delta into the current thinking step's content and updates\n * the corresponding ThinkingPart in the UIMessage.\n *\n * @see docs/chat-architecture.md#thinkingreasoning-content — Thinking flow\n */\n private handleStepFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(\n this.getActiveAssistantMessageId() ?? undefined,\n )\n\n // During the transition period, adapters emit BOTH STEP_FINISHED and\n // REASONING_MESSAGE_CONTENT with the same delta. If we've already processed\n // REASONING_MESSAGE_CONTENT events for this message, skip the duplicate\n // thinking content from STEP_FINISHED to avoid doubled content.\n if (state.hasSeenReasoningEvents) {\n if (chunk.signature) {\n const stepId = state.currentThinkingStepId ?? chunk.stepId\n if (!stepId) return\n const thinking = state.thinkingSteps.get(stepId)\n if (thinking !== undefined) {\n state.thinkingStepSignatures.set(stepId, chunk.signature)\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n thinking,\n chunk.signature,\n )\n this.emitMessagesChange()\n }\n }\n return\n }\n\n this.consumePendingThinkingStep(state)\n\n const stepId =\n state.currentThinkingStepId ?? chunk.stepId ?? generateMessageId()\n\n // Auto-initialize if no prior STEP_STARTED (backward compat)\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n state.currentThinkingStepId = stepId\n }\n\n const previous = state.thinkingSteps.get(stepId) ?? ''\n let nextThinking = previous\n\n // Prefer delta over content\n if (chunk.delta && chunk.delta !== '') {\n nextThinking = previous + chunk.delta\n } else if (chunk.content && chunk.content !== '') {\n if (chunk.content.startsWith(previous)) {\n nextThinking = chunk.content\n } else if (previous.startsWith(chunk.content)) {\n nextThinking = previous\n } else {\n nextThinking = previous + chunk.content\n }\n }\n\n state.thinkingSteps.set(stepId, nextThinking)\n\n if (chunk.signature) {\n state.thinkingStepSignatures.set(stepId, chunk.signature)\n }\n\n // Update UIMessage\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n nextThinking,\n state.thinkingStepSignatures.get(stepId),\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)\n }\n\n /**\n * Handle REASONING_MESSAGE_CONTENT event (AG-UI reasoning protocol).\n *\n * Accumulates reasoning delta into thinking content and updates the\n * corresponding ThinkingPart in the UIMessage.\n */\n private handleReasoningMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(\n this.getActiveAssistantMessageId() ?? undefined,\n )\n\n state.hasSeenReasoningEvents = true\n const delta = chunk.delta || ''\n\n this.consumePendingThinkingStep(state)\n\n const stepId = state.currentThinkingStepId ?? chunk.messageId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n state.currentThinkingStepId = stepId\n }\n\n const nextThinking = (state.thinkingSteps.get(stepId) ?? '') + delta\n state.thinkingSteps.set(stepId, nextThinking)\n\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n nextThinking,\n state.thinkingStepSignatures.get(stepId),\n )\n this.emitMessagesChange()\n\n this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)\n }\n\n /**\n * Handle CUSTOM event.\n *\n * Handles special custom events emitted by the TextEngine (not adapters):\n * - 'tool-input-available': Client tool needs execution. Fires onToolCall.\n * - 'approval-requested': Tool needs user approval. Updates tool-call part\n * state and fires onApprovalRequest.\n *\n * @see docs/chat-architecture.md#client-tools-and-approval-flows — Full flow details\n */\n private handleCustomEvent(\n chunk: Extract<StreamChunk, { type: 'CUSTOM' }>,\n ): void {\n const messageId = this.getActiveAssistantMessageId()\n\n if (chunk.name === 'structured-output.start' && chunk.value) {\n const v = chunk.value as { messageId?: string }\n const targetId = v.messageId ?? messageId\n if (targetId) {\n this.ensureAssistantMessage(targetId)\n this.structuredMessageIds.add(targetId)\n this.structuredOutputUpdateBatches.delete(targetId)\n this.events.onStructuredOutputChange?.({\n phase: 'start',\n messageId: targetId,\n status: 'streaming',\n raw: '',\n })\n }\n return\n }\n\n if (chunk.name === 'structured-output.complete' && chunk.value) {\n const v = chunk.value as {\n object: unknown\n raw?: string\n reasoning?: string\n messageId?: string\n }\n const targetId = v.messageId ?? messageId\n if (targetId) {\n this.flushStructuredOutputUpdate(targetId)\n this.messages = completeStructuredOutputPart(\n this.messages,\n targetId,\n v.object,\n v.raw ?? '',\n v.reasoning,\n )\n this.structuredMessageIds.delete(targetId)\n this.emitStructuredOutputChange(targetId, 'complete')\n this.emitMessagesChange()\n }\n // Fall through so user `onCustomEvent` callbacks still observe the event.\n }\n\n // Handle client tool input availability - trigger client-side execution\n if (chunk.name === 'tool-input-available' && chunk.value) {\n const { toolCallId, toolName, input } = chunk.value as {\n toolCallId: string\n toolName: string\n input: any\n }\n\n // Emit onToolCall event for the client to execute the tool\n this.events.onToolCall?.({\n toolCallId,\n toolName,\n input,\n })\n return\n }\n\n // Handle approval requests\n if (chunk.name === 'approval-requested' && chunk.value) {\n const { toolCallId, toolName, input, approval } = chunk.value as {\n toolCallId: string\n toolName: string\n input: any\n approval: { id: string; needsApproval: boolean }\n }\n\n // Resolve the message containing this tool call. After RUN_FINISHED,\n // activeMessageIds is cleared, so fall back to the toolCallToMessage map\n // which is populated during TOOL_CALL_START and preserved across finalize.\n const resolvedMessageId =\n messageId ?? this.toolCallToMessage.get(toolCallId)\n if (resolvedMessageId) {\n this.messages = updateToolCallApproval(\n this.messages,\n resolvedMessageId,\n toolCallId,\n approval.id,\n )\n this.emitMessagesChange()\n }\n\n // Emit approval request event\n this.events.onApprovalRequest?.({\n toolCallId,\n toolName,\n input,\n approvalId: approval.id,\n })\n return\n }\n\n // Forward non-system custom events to onCustomEvent callback\n if (this.events.onCustomEvent) {\n const toolCallId =\n chunk.value && typeof chunk.value === 'object'\n ? chunk.value.toolCallId\n : undefined\n this.events.onCustomEvent(chunk.name, chunk.value, { toolCallId })\n }\n }\n\n // ============================================\n // Internal Helpers\n // ============================================\n\n /**\n * Detect if an incoming content chunk represents a NEW text segment\n */\n private isNewTextSegment(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n previous: string,\n ): boolean {\n // Check if content is present (delta is always defined but may be empty string)\n if (chunk.content !== undefined) {\n if (chunk.content.length < previous.length) {\n return true\n }\n if (\n !chunk.content.startsWith(previous) &&\n !previous.startsWith(chunk.content)\n ) {\n return true\n }\n }\n return false\n }\n\n /**\n * Complete all tool calls across all active messages — safety net for stream termination.\n *\n * Called by RUN_FINISHED and finalizeStream(). Force-transitions any tool call\n * not yet in input-complete state. Handles cases where TOOL_CALL_END was\n * missed (adapter bug, network error, aborted stream).\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Safety net behavior\n */\n private completeAllToolCalls(): void {\n for (const messageId of this.activeMessageIds) {\n this.completeAllToolCallsForMessage(messageId)\n }\n }\n\n /**\n * Complete all tool calls for a specific message\n */\n private completeAllToolCallsForMessage(messageId: string): void {\n const state = this.getMessageState(messageId)\n if (!state) return\n\n state.toolCalls.forEach((toolCall, id) => {\n if (toolCall.state !== 'input-complete') {\n const index = state.toolCallOrder.indexOf(id)\n this.completeToolCall(messageId, index, toolCall)\n }\n })\n }\n\n /**\n * Mark a tool call as complete and emit event\n */\n private completeToolCall(\n messageId: string,\n _index: number,\n toolCall: InternalToolCallState,\n ): void {\n // Finalize the internal bookkeeping: the call's input arguments ARE\n // complete regardless of whether execution later failed, so the call still\n // counts as a completed tool call in getCompletedToolCalls()/getState().\n toolCall.state = 'input-complete'\n\n // Try final parse\n toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments)\n\n // Don't downgrade the rendered part of a call that already reached the\n // terminal 'error' state (e.g. an output-error TOOL_CALL_RESULT arrived\n // without a preceding TOOL_CALL_END). The RUN_FINISHED / finalizeStream\n // safety net must not clobber a failed call back to 'input-complete'.\n if (this.isToolCallPartErrored(toolCall.id)) {\n return\n }\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: 'input-complete',\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n toolCall.id,\n 'input-complete',\n toolCall.arguments,\n )\n }\n\n /**\n * Whether the rendered tool-call part for the given id has reached the\n * terminal 'error' state. Used to prevent the completion safety net from\n * downgrading a failed call back to 'input-complete'.\n */\n private isToolCallPartErrored(toolCallId: string): boolean {\n return this.messages.some((msg) =>\n msg.parts.some(\n (part) =>\n part.type === 'tool-call' &&\n part.id === toolCallId &&\n part.state === 'error',\n ),\n )\n }\n\n /**\n * Emit pending text update for a specific message.\n *\n * Calls updateTextPart() which has critical append-vs-replace logic:\n * - If last UIMessage part is TextPart → replaces its content (same segment).\n * - If last part is anything else → pushes new TextPart (new segment after tools).\n *\n * @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic\n */\n private emitTextUpdateForMessage(messageId: string): void {\n const state = this.getMessageState(messageId)\n if (!state) return\n\n state.lastEmittedText = state.currentSegmentText\n\n // Update UIMessage\n this.messages = updateTextPart(\n this.messages,\n messageId,\n state.currentSegmentText,\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onTextUpdate?.(messageId, state.currentSegmentText)\n }\n\n private queueStructuredOutputUpdate(messageId: string, delta: string): void {\n const existing = this.structuredOutputUpdateBatches.get(messageId)\n const next = {\n delta: `${existing?.delta ?? ''}${delta}`,\n chunkCount: (existing?.chunkCount ?? 0) + 1,\n }\n\n this.structuredOutputUpdateBatches.set(messageId, next)\n\n if (next.chunkCount >= STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE) {\n this.flushStructuredOutputUpdate(messageId)\n }\n }\n\n private flushStructuredOutputUpdate(messageId: string): void {\n const batch = this.structuredOutputUpdateBatches.get(messageId)\n if (!batch || batch.chunkCount === 0) return\n\n this.structuredOutputUpdateBatches.delete(messageId)\n this.emitStructuredOutputChange(messageId, 'update', batch.delta)\n }\n\n private emitStructuredOutputChange(\n messageId: string,\n phase: 'update' | 'complete' | 'error',\n delta?: string,\n ): void {\n const part = this.messages\n .find((message) => message.id === messageId)\n ?.parts.find(\n (\n messagePart,\n ): messagePart is Extract<MessagePart, { type: 'structured-output' }> =>\n messagePart.type === 'structured-output',\n )\n if (!part) return\n\n this.events.onStructuredOutputChange?.({\n phase,\n messageId,\n status: part.status,\n raw: part.raw,\n ...(part.partial !== undefined ? { partial: part.partial } : {}),\n ...(part.data !== undefined ? { data: part.data } : {}),\n ...(part.reasoning !== undefined ? { reasoning: part.reasoning } : {}),\n ...(part.errorMessage !== undefined\n ? { errorMessage: part.errorMessage }\n : {}),\n ...(delta !== undefined ? { delta } : {}),\n })\n }\n\n /**\n * Emit messages change event\n */\n private emitMessagesChange(): void {\n this.events.onMessagesChange?.([...this.messages])\n }\n\n /**\n * Finalize the stream — complete all pending operations.\n *\n * Called when the async iterable ends (stream closed). Acts as the final\n * safety net: completes any remaining tool calls, flushes un-emitted text,\n * and fires onStreamEnd.\n *\n * @see docs/chat-architecture.md#single-shot-text-response — Finalization step\n */\n finalizeStream(): void {\n let lastAssistantMessage: UIMessage | undefined\n\n // Finalize ALL active messages\n for (const messageId of this.activeMessageIds) {\n const state = this.getMessageState(messageId)\n if (!state) continue\n\n // Complete any remaining tool calls\n this.completeAllToolCallsForMessage(messageId)\n\n // Emit any pending text if not already emitted\n if (state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n\n state.isComplete = true\n\n const msg = this.messages.find((m) => m.id === messageId)\n if (msg && msg.role === 'assistant') {\n lastAssistantMessage = msg\n }\n }\n\n // The stream closed but one or more structured-output runs never sent\n // their terminal `structured-output.complete`. Snap each lingering\n // streaming part to error so the UI doesn't appear to stream forever,\n // and drop the routing entries so a subsequent run on the same\n // processor instance (long-lived `subscribe()` mode) doesn't reuse\n // the stale ids.\n //\n // The iteration is unconditional w.r.t. `this.hasError` — RUN_ERROR\n // already removed its target messageId from `structuredMessageIds`\n // before reaching finalize, so anything still in the set is by\n // definition a non-errored, never-completed run (the multi-run case:\n // run-A errors, run-B is still streaming when finalize fires).\n for (const messageId of this.structuredMessageIds) {\n this.flushStructuredOutputUpdate(messageId)\n this.messages = errorStructuredOutputPart(\n this.messages,\n messageId,\n 'Stream ended without structured-output.complete',\n )\n this.emitStructuredOutputChange(messageId, 'error')\n }\n this.structuredMessageIds.clear()\n this.structuredOutputUpdateBatches.clear()\n\n this.activeMessageIds.clear()\n\n // Remove whitespace-only assistant messages (handles models like Gemini\n // that sometimes return just \"\\n\" during auto-continuation).\n // Preserve the message on errors so the UI can show error state.\n if (lastAssistantMessage && !this.hasError) {\n if (this.isWhitespaceOnlyMessage(lastAssistantMessage)) {\n this.messages = this.messages.filter(\n (m) => m.id !== lastAssistantMessage.id,\n )\n this.emitMessagesChange()\n return\n }\n }\n\n // Emit stream end for the last assistant message\n if (lastAssistantMessage) {\n this.events.onStreamEnd?.(lastAssistantMessage)\n }\n }\n\n /**\n * Get completed tool calls in API format (aggregated across all messages)\n */\n private getCompletedToolCalls(): Array<ToolCall> {\n const result: Array<ToolCall> = []\n for (const state of this.messageStates.values()) {\n for (const tc of state.toolCalls.values()) {\n if (tc.state === 'input-complete') {\n result.push({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.name,\n arguments: tc.arguments,\n },\n // Preserve provider metadata (e.g. Gemini thoughtSignature) on\n // ProcessorResult.toolCalls so callers using process()/getResult()\n // get the same round-trip support as the streaming UI path.\n ...(tc.metadata !== undefined && { metadata: tc.metadata }),\n })\n }\n }\n }\n return result\n }\n\n /**\n * Get current result (aggregated across all messages)\n */\n private getResult(): ProcessorResult {\n const toolCalls = this.getCompletedToolCalls()\n let content = ''\n let thinking = ''\n\n for (const state of this.messageStates.values()) {\n content += state.totalTextContent\n for (const stepId of state.thinkingStepOrder) {\n thinking += state.thinkingSteps.get(stepId) ?? ''\n }\n }\n\n return {\n content,\n thinking: thinking || undefined,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n finishReason: this.finishReason,\n }\n }\n\n /**\n * Get current processor state (aggregated across all messages)\n */\n getState(): ProcessorState {\n let content = ''\n let thinking = ''\n const toolCalls = new Map<string, InternalToolCallState>()\n const toolCallOrder: Array<string> = []\n\n for (const state of this.messageStates.values()) {\n content += state.totalTextContent\n for (const stepId of state.thinkingStepOrder) {\n thinking += state.thinkingSteps.get(stepId) ?? ''\n }\n for (const [id, tc] of state.toolCalls) {\n toolCalls.set(id, tc)\n }\n toolCallOrder.push(...state.toolCallOrder)\n }\n\n return {\n content,\n thinking,\n toolCalls,\n toolCallOrder,\n finishReason: this.finishReason,\n done: this.isDone,\n }\n }\n\n /**\n * Start recording chunks\n */\n startRecording(): void {\n this.recordingEnabled = true\n this.recordingStartTime = Date.now()\n this.recording = {\n version: '1.0',\n timestamp: this.recordingStartTime,\n chunks: [],\n }\n }\n\n /**\n * Get the current recording\n */\n getRecording(): ChunkRecording | null {\n return this.recording\n }\n\n /**\n * Reset stream state (but keep messages)\n */\n private resetStreamState(): void {\n this.messageStates.clear()\n this.activeMessageIds.clear()\n this.activeRuns.clear()\n this.toolCallToMessage.clear()\n this.structuredMessageIds.clear()\n this.structuredOutputUpdateBatches.clear()\n this.pendingManualMessageId = null\n this.pendingThinkingStepId = null\n this.finishReason = null\n this.hasError = false\n this.isDone = false\n this.chunkStrategy.reset?.()\n }\n\n /**\n * Full reset (including messages)\n */\n reset(): void {\n this.resetStreamState()\n this.messages = []\n }\n\n /**\n * Check if a message contains only whitespace text and no other meaningful parts\n * (no tool calls, tool results, thinking, etc.)\n */\n private isWhitespaceOnlyMessage(message: UIMessage): boolean {\n if (message.parts.length === 0) return false\n return message.parts.every(\n (part) => part.type === 'text' && part.content.trim() === '',\n )\n }\n\n /**\n * Replay a recording through the processor\n */\n static async replay(\n recording: ChunkRecording,\n options?: StreamProcessorOptions,\n ): Promise<ProcessorResult> {\n const processor = new StreamProcessor(options)\n return processor.process(createReplayStream(recording))\n }\n}\n\n/**\n * Create an async iterable from a recording\n */\nexport function createReplayStream(\n recording: ChunkRecording,\n): AsyncIterable<StreamChunk> {\n return {\n // eslint-disable-next-line @typescript-eslint/require-await -- async generator required by AsyncIterable contract; body has no await\n async *[Symbol.asyncIterator]() {\n for (const { chunk } of recording.chunks) {\n yield chunk\n }\n },\n }\n}\n"],"names":["state","delta","stepId"],"mappings":"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"}
1
+ {"version":3,"file":"processor.js","sources":["../../../../../src/activities/chat/stream/processor.ts"],"sourcesContent":["/**\n * Unified Stream Processor\n *\n * Core stream processing engine that manages the full UIMessage[] conversation.\n * Single source of truth for message state.\n *\n * Handles:\n * - Full conversation management (UIMessage[])\n * - Text content accumulation with configurable chunking strategies\n * - Parallel tool calls with lifecycle state tracking\n * - Tool results and approval flows\n * - Thinking/reasoning content\n * - Recording/replay for testing\n * - Event-driven architecture for UI updates\n * - Per-message stream state tracking for multi-message sessions\n *\n * @see docs/chat-architecture.md — Canonical reference for AG-UI chunk ordering,\n * adapter contract, single-shot flows, and expected UIMessage output.\n */\nimport {\n aguiSnapshotMessageToUIMessage,\n generateMessageId,\n uiMessageToModelMessages,\n} from '../messages.js'\nimport { normalizeToolResult } from '../../../utilities/tool-result'\nimport { defaultJSONParser } from './json-parser'\nimport {\n appendStructuredOutputDelta,\n completeStructuredOutputPart,\n errorStructuredOutputPart,\n updateTextPart,\n updateThinkingPart,\n updateToolCallApproval,\n updateToolCallApprovalResponse,\n updateToolCallPart,\n updateToolCallWithOutput,\n updateToolResultPart,\n} from './message-updaters'\nimport { ImmediateStrategy } from './strategies'\nimport type {\n ChunkRecording,\n ChunkStrategy,\n InternalToolCallState,\n MessageStreamState,\n ProcessorResult,\n ProcessorState,\n ToolCallState,\n ToolResultState,\n} from './types'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StreamChunk,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n} from '../../../types'\n\n/**\n * Events emitted by the StreamProcessor\n */\nexport interface StreamProcessorEvents {\n // State events - full array on any change\n onMessagesChange?: (messages: Array<UIMessage>) => void\n\n // Lifecycle events\n onStreamStart?: () => void\n onStreamEnd?: (message: UIMessage) => void\n onError?: (error: Error) => void\n\n // Interaction events - client must handle these\n onToolCall?: (args: {\n toolCallId: string\n toolName: string\n input: any\n }) => void\n onApprovalRequest?: (args: {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n }) => void\n\n // Custom events from server-side tools\n onCustomEvent?: (\n eventType: string,\n data: unknown,\n context: { toolCallId?: string },\n ) => void\n\n // Granular events for UI optimization (character-by-character, state tracking)\n onTextUpdate?: (messageId: string, content: string) => void\n onToolCallStateChange?: (\n messageId: string,\n toolCallId: string,\n state: ToolCallState,\n args: string,\n ) => void\n onThinkingUpdate?: (\n messageId: string,\n stepId: string,\n content: string,\n ) => void\n onStructuredOutputChange?: (args: {\n phase: 'start' | 'update' | 'complete' | 'error'\n messageId: string\n status: 'streaming' | 'complete' | 'error'\n raw: string\n partial?: unknown\n data?: unknown\n reasoning?: string\n errorMessage?: string\n delta?: string\n }) => void\n}\n\n/**\n * Options for StreamProcessor\n */\nexport interface StreamProcessorOptions {\n chunkStrategy?: ChunkStrategy\n /** Event-driven handlers */\n events?: StreamProcessorEvents\n jsonParser?: {\n parse: (jsonString: string) => any\n }\n /** Enable recording for replay testing */\n recording?: boolean\n /** Initial messages to populate the processor */\n initialMessages?: Array<UIMessage>\n}\n\nconst STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE = 12\n\n/**\n * StreamProcessor - State machine for processing AI response streams\n *\n * Manages the full UIMessage[] conversation and emits events on changes.\n * Trusts the adapter contract: adapters emit clean AG-UI events in the\n * correct order.\n *\n * State tracking:\n * - Full message array\n * - Per-message stream state (text, tool calls, thinking)\n * - Multiple concurrent message streams\n * - Tool call completion via TOOL_CALL_END events\n *\n * @see docs/chat-architecture.md#streamprocessor-internal-state — State field reference\n * @see docs/chat-architecture.md#adapter-contract — What this class expects from adapters\n */\nexport class StreamProcessor {\n private readonly chunkStrategy: ChunkStrategy\n private readonly events: StreamProcessorEvents\n private readonly jsonParser: { parse: (jsonString: string) => any }\n private recordingEnabled: boolean\n\n // Message state\n private messages: Array<UIMessage> = []\n\n // Per-message stream state\n private readonly messageStates: Map<string, MessageStreamState> = new Map()\n private readonly activeMessageIds: Set<string> = new Set()\n private readonly toolCallToMessage: Map<string, string> = new Map()\n private pendingManualMessageId: string | null = null\n private pendingThinkingStepId: string | null = null\n\n private readonly structuredMessageIds: Set<string> = new Set()\n private readonly structuredOutputUpdateBatches = new Map<\n string,\n {\n delta: string\n chunkCount: number\n }\n >()\n\n // Run tracking (for concurrent run safety)\n private readonly activeRuns = new Set<string>()\n\n // Shared stream state\n private finishReason: string | null = null\n private hasError = false\n private isDone = false\n\n // Recording\n private recording: ChunkRecording | null = null\n private recordingStartTime = 0\n\n constructor(options: StreamProcessorOptions = {}) {\n this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy()\n this.events = options.events || {}\n this.jsonParser = options.jsonParser || defaultJSONParser\n this.recordingEnabled = options.recording ?? false\n\n // Initialize with provided messages\n if (options.initialMessages) {\n this.messages = [...options.initialMessages]\n }\n }\n\n // ============================================\n // Message Management Methods\n // ============================================\n\n /**\n * Set the messages array (e.g., from persisted state)\n */\n setMessages(messages: Array<UIMessage>): void {\n this.messages = [...messages]\n this.emitMessagesChange()\n }\n\n /**\n * Add a user message to the conversation.\n * Supports both simple string content and multimodal content arrays.\n *\n * @param content - The message content (string or array of content parts)\n * @param id - Optional custom message ID (generated if not provided)\n * @returns The created UIMessage\n *\n * @example\n * ```ts\n * // Simple text message\n * processor.addUserMessage('Hello!')\n *\n * // Multimodal message with image\n * processor.addUserMessage([\n * { type: 'text', content: 'What is in this image?' },\n * { type: 'image', source: { type: 'url', value: 'https://example.com/photo.jpg' } }\n * ])\n *\n * // With custom ID\n * processor.addUserMessage('Hello!', 'custom-id-123')\n * ```\n */\n addUserMessage(content: string | Array<ContentPart>, id?: string): UIMessage {\n // Convert content to message parts\n const parts: Array<MessagePart> =\n typeof content === 'string'\n ? [{ type: 'text', content }]\n : content.map((part) => {\n // ContentPart types (text, image, audio, video, document) are compatible with MessagePart\n return part\n })\n\n const userMessage: UIMessage = {\n id: id ?? generateMessageId(),\n role: 'user',\n parts,\n createdAt: new Date(),\n }\n\n this.messages = [...this.messages, userMessage]\n this.emitMessagesChange()\n\n return userMessage\n }\n\n /**\n * Prepare for a new assistant message stream.\n * Does NOT create the message immediately -- the message is created lazily\n * when the first content-bearing chunk arrives via ensureAssistantMessage().\n * This prevents empty assistant messages from flickering in the UI when\n * auto-continuation produces no content.\n */\n prepareAssistantMessage(): void {\n // Reset stream state for new message\n this.resetStreamState()\n }\n\n /**\n * @deprecated Use prepareAssistantMessage() instead. This eagerly creates\n * an assistant message which can cause empty message flicker.\n */\n startAssistantMessage(messageId?: string): string {\n this.prepareAssistantMessage()\n const { messageId: id } = this.ensureAssistantMessage(messageId)\n this.pendingManualMessageId = id\n return id\n }\n\n /**\n * Get the current assistant message ID (if one has been created).\n * Returns null if prepareAssistantMessage() was called but no content\n * has arrived yet.\n */\n getCurrentAssistantMessageId(): string | null {\n // Scan all message states (not just active) for the last assistant.\n // After finalizeStream() clears activeMessageIds, messageStates retains entries.\n // After reset() / resetStreamState(), messageStates is cleared → returns null.\n let lastId: string | null = null\n for (const [id, state] of this.messageStates) {\n if (state.role === 'assistant') {\n lastId = id\n }\n }\n return lastId\n }\n\n /**\n * Add a tool result (called by client after handling onToolCall)\n */\n addToolResult(toolCallId: string, output: any, error?: string): void {\n // Find the message containing this tool call\n const messageWithToolCall = this.messages.find((msg) =>\n msg.parts.some(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n ),\n )\n\n if (!messageWithToolCall) {\n console.warn(\n `[StreamProcessor] Could not find message with tool call ${toolCallId}`,\n )\n return\n }\n\n // Step 1: Update the tool-call part's output field (for UI rendering)\n let updatedMessages = updateToolCallWithOutput(\n this.messages,\n toolCallId,\n output,\n error ? 'error' : undefined,\n error,\n )\n\n // Step 2: Create a tool-result part (for LLM conversation history)\n const content = normalizeToolResult(output)\n const toolResultState: ToolResultState = error ? 'error' : 'complete'\n\n updatedMessages = updateToolResultPart(\n updatedMessages,\n messageWithToolCall.id,\n toolCallId,\n content,\n toolResultState,\n error,\n )\n\n this.messages = updatedMessages\n this.emitMessagesChange()\n }\n\n /**\n * Add an approval response (called by client after handling onApprovalRequest)\n */\n addToolApprovalResponse(approvalId: string, approved: boolean): void {\n this.messages = updateToolCallApprovalResponse(\n this.messages,\n approvalId,\n approved,\n )\n this.emitMessagesChange()\n }\n\n /**\n * Get the conversation as ModelMessages (for sending to LLM)\n */\n toModelMessages(): Array<ModelMessage> {\n const modelMessages: Array<ModelMessage> = []\n for (const msg of this.messages) {\n modelMessages.push(...uiMessageToModelMessages(msg))\n }\n return modelMessages\n }\n\n /**\n * Get current messages\n */\n getMessages(): Array<UIMessage> {\n return this.messages\n }\n\n /**\n * Check if all tool calls in the last assistant message are complete\n * Useful for auto-continue logic\n */\n areAllToolsComplete(): boolean {\n const lastAssistant = this.messages.findLast(\n (m: UIMessage) => m.role === 'assistant',\n )\n\n if (!lastAssistant) return true\n\n const toolParts = lastAssistant.parts.filter(\n (p): p is ToolCallPart => p.type === 'tool-call',\n )\n\n if (toolParts.length === 0) return true\n\n // Get tool result parts to check for server tool completion\n const toolResultIds = new Set(\n lastAssistant.parts\n .filter((p): p is ToolResultPart => p.type === 'tool-result')\n .map((p) => p.toolCallId),\n )\n\n // All tool calls must be in a terminal state\n // A tool call is complete if:\n // 1. It was approved/denied (approval-responded state)\n // 2. It has an output field set (client tool completed via addToolResult)\n // 3. It has a corresponding tool-result part (server tool completed)\n return toolParts.every(\n (part) =>\n part.state === 'complete' ||\n part.state === 'approval-responded' ||\n (part.output !== undefined && !part.approval) ||\n toolResultIds.has(part.id),\n )\n }\n\n /**\n * Remove messages after a certain index (for reload/retry)\n */\n removeMessagesAfter(index: number): void {\n const keptIds = new Set(this.messages.slice(0, index + 1).map((m) => m.id))\n // Drop routing state for messages that no longer exist; otherwise a\n // resumed stream (`reload()` or a server that reuses messageIds across\n // runs) could land deltas / tool args on stale map entries and corrupt\n // the new assistant message's parts. Mirror the four routing maps that\n // key on messageId: structuredMessageIds (custom-event routing),\n // messageStates (per-message stream state), toolCallToMessage (tool\n // args → message), and activeMessageIds (finalize / completeAllToolCalls\n // iteration targets — must not include phantoms).\n for (const id of this.structuredMessageIds) {\n if (!keptIds.has(id)) this.structuredMessageIds.delete(id)\n }\n for (const id of this.structuredOutputUpdateBatches.keys()) {\n if (!keptIds.has(id)) this.structuredOutputUpdateBatches.delete(id)\n }\n for (const id of this.messageStates.keys()) {\n if (!keptIds.has(id)) this.messageStates.delete(id)\n }\n for (const [toolCallId, msgId] of this.toolCallToMessage) {\n if (!keptIds.has(msgId)) this.toolCallToMessage.delete(toolCallId)\n }\n for (const id of this.activeMessageIds) {\n if (!keptIds.has(id)) this.activeMessageIds.delete(id)\n }\n this.messages = this.messages.slice(0, index + 1)\n this.emitMessagesChange()\n }\n\n /**\n * Clear all messages\n */\n clearMessages(): void {\n this.messages = []\n this.messageStates.clear()\n this.activeMessageIds.clear()\n this.toolCallToMessage.clear()\n this.structuredMessageIds.clear()\n this.structuredOutputUpdateBatches.clear()\n this.pendingManualMessageId = null\n this.emitMessagesChange()\n }\n\n // ============================================\n // Stream Processing Methods\n // ============================================\n\n /**\n * Process a stream and emit events through handlers\n */\n async process(stream: AsyncIterable<any>): Promise<ProcessorResult> {\n // Reset stream state (but keep messages)\n this.resetStreamState()\n\n // Start recording if enabled\n if (this.recordingEnabled) {\n this.startRecording()\n }\n\n // Process each chunk\n for await (const chunk of stream) {\n this.processChunk(chunk)\n }\n\n // Stream ended - finalize everything\n this.finalizeStream()\n\n // Finalize recording\n if (this.recording) {\n this.recording.result = this.getResult()\n }\n\n return this.getResult()\n }\n\n /**\n * Process a single chunk from the stream.\n *\n * Central dispatch for all AG-UI events. Each event type maps to a specific\n * handler. Events not listed in the switch are intentionally ignored\n * (STEP_STARTED, STATE_SNAPSHOT, STATE_DELTA).\n *\n * @see docs/chat-architecture.md#adapter-contract — Expected event types and ordering\n */\n processChunk(chunk: StreamChunk): void {\n // Record chunk if enabled\n if (this.recording) {\n this.recording.chunks.push({\n chunk,\n timestamp: Date.now(),\n index: this.recording.chunks.length,\n })\n }\n\n // Cast needed: @ag-ui/core Zod passthrough types add `& { [k: string]: unknown }`\n // which prevents TypeScript from narrowing the `type` discriminant in switch.\n const c = chunk\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType enum members vs string-literal case labels; default branch handles untraced events.\n switch (c.type) {\n // AG-UI Events\n case 'TEXT_MESSAGE_START':\n this.handleTextMessageStartEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,\n )\n break\n\n case 'TEXT_MESSAGE_CONTENT':\n this.handleTextMessageContentEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n )\n break\n\n case 'TEXT_MESSAGE_END':\n this.handleTextMessageEndEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_END' }>,\n )\n break\n\n case 'TOOL_CALL_START':\n this.handleToolCallStartEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n )\n break\n\n case 'TOOL_CALL_ARGS':\n this.handleToolCallArgsEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n )\n break\n\n case 'TOOL_CALL_END':\n this.handleToolCallEndEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n )\n break\n\n case 'RUN_FINISHED':\n this.handleRunFinishedEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n )\n break\n\n case 'RUN_ERROR':\n this.handleRunErrorEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n )\n break\n\n case 'STEP_FINISHED':\n this.handleStepFinishedEvent(\n chunk as Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n )\n break\n\n case 'MESSAGES_SNAPSHOT':\n this.handleMessagesSnapshotEvent(\n chunk as Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,\n )\n break\n\n case 'CUSTOM':\n this.handleCustomEvent(\n chunk as Extract<StreamChunk, { type: 'CUSTOM' }>,\n )\n break\n\n case 'RUN_STARTED':\n this.handleRunStartedEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_STARTED' }>,\n )\n break\n\n case 'REASONING_START':\n case 'REASONING_MESSAGE_START':\n case 'REASONING_MESSAGE_END':\n case 'REASONING_END':\n // No special handling needed\n break\n\n case 'REASONING_MESSAGE_CONTENT':\n this.handleReasoningMessageContentEvent(\n chunk as Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,\n )\n break\n\n case 'TOOL_CALL_RESULT':\n this.handleToolCallResultEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_RESULT' }>,\n )\n break\n\n case 'STEP_STARTED':\n this.handleStepStartedEvent(\n chunk as Extract<StreamChunk, { type: 'STEP_STARTED' }>,\n )\n break\n\n default:\n // STATE_SNAPSHOT, STATE_DELTA - no special handling needed\n break\n }\n }\n\n // ============================================\n // Per-Message State Helpers\n // ============================================\n\n /**\n * Create a new MessageStreamState for a message\n */\n private createMessageState(\n messageId: string,\n role: 'user' | 'assistant' | 'system',\n ): MessageStreamState {\n const state: MessageStreamState = {\n id: messageId,\n role,\n totalTextContent: '',\n currentSegmentText: '',\n lastEmittedText: '',\n hasSeenReasoningEvents: false,\n thinkingSteps: new Map(),\n thinkingStepSignatures: new Map(),\n thinkingStepOrder: [],\n currentThinkingStepId: null,\n toolCalls: new Map(),\n toolCallOrder: [],\n hasToolCallsSinceTextStart: false,\n isComplete: false,\n }\n this.messageStates.set(messageId, state)\n return state\n }\n\n /**\n * Get the MessageStreamState for a message\n */\n private getMessageState(messageId: string): MessageStreamState | undefined {\n return this.messageStates.get(messageId)\n }\n\n /**\n * Promote a pending stepId from a STEP_STARTED that fired before the\n * assistant message existed onto the given message state, so the next\n * thinking event (STEP_FINISHED or REASONING_MESSAGE_CONTENT) attributes\n * to the correct step.\n */\n private consumePendingThinkingStep(state: MessageStreamState): void {\n if (!this.pendingThinkingStepId) return\n const stepId = this.pendingThinkingStepId\n state.currentThinkingStepId = stepId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n }\n this.pendingThinkingStepId = null\n }\n\n /**\n * Get the most recent active assistant message ID.\n * Used as fallback for events that don't include a messageId.\n */\n private getActiveAssistantMessageId(): string | null {\n // Set iteration is insertion-order; reverse-iterate to search from the end\n const ids = Array.from(this.activeMessageIds).reverse()\n for (const id of ids) {\n const state = this.messageStates.get(id)\n if (state && state.role === 'assistant') {\n return id\n }\n }\n return null\n }\n\n /**\n * Ensure an active assistant message exists, creating one if needed.\n * Used for backward compat when events arrive without prior TEXT_MESSAGE_START.\n *\n * On reconnect/resume, a TEXT_MESSAGE_CONTENT may arrive for a message that\n * already exists in this.messages (e.g. from initialMessages or a prior\n * MESSAGES_SNAPSHOT) but whose transient state was cleared. In that case we\n * hydrate state from the existing message rather than creating a duplicate.\n */\n private ensureAssistantMessage(preferredId?: string): {\n messageId: string\n state: MessageStreamState\n } {\n // Try to find state by preferred ID\n if (preferredId) {\n const state = this.getMessageState(preferredId)\n if (state) return { messageId: preferredId, state }\n }\n\n // Try active assistant message\n const activeId = this.getActiveAssistantMessageId()\n if (activeId) {\n const state = this.getMessageState(activeId)\n if (state) return { messageId: activeId, state }\n }\n\n // Check if a message with preferredId already exists (reconnect/resume case).\n // Hydrate transient state from the existing message instead of duplicating it.\n if (preferredId) {\n const existingMsg = this.messages.find((m) => m.id === preferredId)\n if (existingMsg) {\n const state = this.createMessageState(preferredId, existingMsg.role)\n this.activeMessageIds.add(preferredId)\n\n // Seed segment text from the existing last text part so that\n // incoming deltas append correctly and updateTextPart produces\n // the right content (existing text + new delta).\n const lastPart =\n existingMsg.parts.length > 0\n ? existingMsg.parts[existingMsg.parts.length - 1]\n : null\n if (lastPart && lastPart.type === 'text') {\n state.currentSegmentText = lastPart.content\n state.lastEmittedText = lastPart.content\n state.totalTextContent = lastPart.content\n }\n\n return { messageId: preferredId, state }\n }\n }\n\n // Auto-create an assistant message (backward compat for process() without TEXT_MESSAGE_START)\n const id = preferredId || generateMessageId()\n const assistantMessage: UIMessage = {\n id,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n this.messages = [...this.messages, assistantMessage]\n const state = this.createMessageState(id, 'assistant')\n this.activeMessageIds.add(id)\n this.pendingManualMessageId = id\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n return { messageId: id, state }\n }\n\n // ============================================\n // Event Handlers\n // ============================================\n\n /**\n * Handle TEXT_MESSAGE_START event\n */\n private handleTextMessageStartEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,\n ): void {\n const { messageId, role } = chunk\n\n // Map 'tool' and 'developer' roles to 'assistant' for both UIMessage and MessageStreamState\n // (UIMessage doesn't support 'tool'/'developer' role, and lookups like\n // getActiveAssistantMessageId() check state.role === 'assistant')\n const uiRole: 'system' | 'user' | 'assistant' =\n role === 'user' || role === 'system' ? role : 'assistant'\n\n // Case 1: A manual message was created via startAssistantMessage()\n if (this.pendingManualMessageId) {\n const pendingId = this.pendingManualMessageId\n this.pendingManualMessageId = null\n\n if (pendingId !== messageId) {\n // Update the message's ID in the messages array\n this.messages = this.messages.map((msg) =>\n msg.id === pendingId ? { ...msg, id: messageId } : msg,\n )\n\n // Move state to the new key\n const existingState = this.messageStates.get(pendingId)\n if (existingState) {\n existingState.id = messageId\n this.messageStates.delete(pendingId)\n this.messageStates.set(messageId, existingState)\n }\n\n // Update activeMessageIds\n this.activeMessageIds.delete(pendingId)\n this.activeMessageIds.add(messageId)\n }\n\n // Ensure state exists\n if (!this.messageStates.has(messageId)) {\n this.createMessageState(messageId, uiRole)\n this.activeMessageIds.add(messageId)\n }\n\n this.emitMessagesChange()\n return\n }\n\n // Case 2: Message already exists (dedup)\n const existingMsg = this.messages.find((m) => m.id === messageId)\n if (existingMsg) {\n this.activeMessageIds.add(messageId)\n const existingState = this.messageStates.get(messageId)\n if (!existingState) {\n this.createMessageState(messageId, uiRole)\n } else {\n // If tool calls happened since last text, this TEXT_MESSAGE_START\n // signals a new text segment — reset segment accumulation\n if (existingState.hasToolCallsSinceTextStart) {\n if (\n existingState.currentSegmentText !== existingState.lastEmittedText\n ) {\n this.emitTextUpdateForMessage(messageId)\n }\n existingState.currentSegmentText = ''\n existingState.lastEmittedText = ''\n existingState.hasToolCallsSinceTextStart = false\n }\n }\n return\n }\n\n // Case 3: New message from the stream\n const newMessage: UIMessage = {\n id: messageId,\n role: uiRole,\n parts: [],\n createdAt: new Date(),\n }\n\n this.messages = [...this.messages, newMessage]\n this.createMessageState(messageId, uiRole)\n this.activeMessageIds.add(messageId)\n\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n }\n\n /**\n * Handle TEXT_MESSAGE_END event\n */\n private handleTextMessageEndEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_END' }>,\n ): void {\n const { messageId } = chunk\n const state = this.getMessageState(messageId)\n if (!state) return\n if (state.isComplete) return\n\n // Emit any pending text for this message\n if (state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n\n // Complete all tool calls for this message\n this.completeAllToolCallsForMessage(messageId)\n }\n\n /**\n * Handle MESSAGES_SNAPSHOT event\n */\n private handleMessagesSnapshotEvent(\n chunk: Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,\n ): void {\n this.resetStreamState()\n // Normalize AG-UI snapshot messages to UIMessage[] so every message has a\n // `parts` array. AG-UI messages carry `content` but no `parts`, so casting\n // them directly to UIMessage[] is unsafe and causes \"Cannot read properties\n // of undefined (reading 'find')\" when code later reads message.parts (e.g.\n // the onToolCallStateChange devtools handler).\n this.messages = chunk.messages.map(aguiSnapshotMessageToUIMessage)\n this.emitMessagesChange()\n }\n\n /**\n * Handle TEXT_MESSAGE_CONTENT event.\n *\n * Accumulates delta into both currentSegmentText (for UI emission) and\n * totalTextContent (for ProcessorResult). Lazily creates the assistant\n * UIMessage on first content. Uses updateTextPart() which replaces the\n * last TextPart or creates a new one depending on part ordering.\n *\n * @see docs/chat-architecture.md#single-shot-text-response — Text accumulation step-by-step\n * @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic\n */\n private handleTextMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(chunk.messageId)\n\n // Content arriving means all current tool calls for this message are complete\n this.completeAllToolCallsForMessage(messageId)\n\n if (this.structuredMessageIds.has(messageId)) {\n // `chunk.delta` is incremental; `chunk.content` is sometimes cumulative\n // (mirrors what the plain-text branch handles below). Reconcile against\n // the existing raw buffer so adapters that emit cumulative content\n // don't duplicate the JSON.\n let delta = chunk.delta || ''\n if (delta === '' && chunk.content !== undefined && chunk.content !== '') {\n const existingRaw = (\n this.messages\n .find((m) => m.id === messageId)\n ?.parts.find(\n (p): p is Extract<MessagePart, { type: 'structured-output' }> =>\n p.type === 'structured-output',\n ) ?? { raw: '' }\n ).raw\n if (chunk.content.startsWith(existingRaw)) {\n delta = chunk.content.slice(existingRaw.length)\n } else if (existingRaw.startsWith(chunk.content)) {\n delta = ''\n } else {\n delta = chunk.content\n }\n }\n if (delta !== '') {\n this.messages = appendStructuredOutputDelta(\n this.messages,\n messageId,\n delta,\n )\n state.totalTextContent += delta\n this.queueStructuredOutputUpdate(messageId, delta)\n this.emitMessagesChange()\n }\n return\n }\n\n const previousSegment = state.currentSegmentText\n\n // Detect if this is a NEW text segment (after tool calls) vs continuation\n const isNewSegment =\n state.hasToolCallsSinceTextStart &&\n previousSegment.length > 0 &&\n this.isNewTextSegment(chunk, previousSegment)\n\n if (isNewSegment) {\n // Emit any accumulated text before starting new segment\n if (previousSegment !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n // Reset SEGMENT text accumulation for the new text segment after tool calls\n state.currentSegmentText = ''\n state.lastEmittedText = ''\n state.hasToolCallsSinceTextStart = false\n }\n\n const currentText = state.currentSegmentText\n let nextText = currentText\n\n // Prefer delta over content - delta is the incremental change\n // Normalize to empty string to avoid \"undefined\" string concatenation\n const delta = chunk.delta || ''\n if (delta !== '') {\n nextText = currentText + delta\n } else if (chunk.content !== undefined && chunk.content !== '') {\n // Fallback: use content if delta is not provided\n if (chunk.content.startsWith(currentText)) {\n nextText = chunk.content\n } else if (currentText.startsWith(chunk.content)) {\n nextText = currentText\n } else {\n nextText = currentText + chunk.content\n }\n }\n\n // Calculate the delta for totalTextContent\n const textDelta = nextText.slice(currentText.length)\n state.currentSegmentText = nextText\n state.totalTextContent += textDelta\n\n // Use delta for chunk strategy if available\n const chunkPortion = chunk.delta || chunk.content || ''\n const shouldEmit = this.chunkStrategy.shouldEmit(\n chunkPortion,\n state.currentSegmentText,\n )\n if (shouldEmit && state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n }\n\n /**\n * Handle TOOL_CALL_START event.\n *\n * Creates a new InternalToolCallState entry in the toolCalls Map and appends\n * a ToolCallPart to the UIMessage. Duplicate toolCallId is a no-op.\n *\n * CRITICAL: This MUST be received before any TOOL_CALL_ARGS for the same\n * toolCallId. Args for unknown IDs are silently dropped.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Tool call state transitions\n * @see docs/chat-architecture.md#parallel-tool-calls-single-shot — Parallel tracking by ID\n * @see docs/chat-architecture.md#adapter-contract — Ordering requirements\n */\n private handleToolCallStartEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n ): void {\n // Determine the message this tool call belongs to\n const targetMessageId =\n chunk.parentMessageId ?? this.getActiveAssistantMessageId()\n const { messageId, state } = this.ensureAssistantMessage(\n targetMessageId ?? undefined,\n )\n\n // Mark that we've seen tool calls since the last text segment\n state.hasToolCallsSinceTextStart = true\n\n const toolCallId = chunk.toolCallId\n const existingToolCall = state.toolCalls.get(toolCallId)\n\n if (!existingToolCall) {\n // New tool call starting\n const initialState: ToolCallState = 'awaiting-input'\n\n // `toolName` is a deprecated alias for `toolCallName` (see ToolCallStartEvent\n // in types.ts). Accept either so chunks from older code paths or any\n // adapter that only sets the deprecated field still produce a named part.\n // The type marks both as required strings, but in practice some emitters\n // only set one — fall back via the runtime value rather than the type.\n const toolName =\n (chunk as { toolCallName?: string }).toolCallName ?? chunk.toolName\n\n // Capture provider metadata that arrived on TOOL_CALL_START so it\n // round-trips back through the assistant message on the next turn\n // (e.g. Gemini's thoughtSignature).\n const chunkMetadata = chunk.metadata\n\n const newToolCall: InternalToolCallState = {\n id: chunk.toolCallId,\n name: toolName,\n arguments: '',\n state: initialState,\n parsedArguments: undefined,\n index: chunk.index ?? state.toolCalls.size,\n ...(chunkMetadata !== undefined && { metadata: chunkMetadata }),\n }\n\n state.toolCalls.set(toolCallId, newToolCall)\n state.toolCallOrder.push(toolCallId)\n\n // Store mapping for TOOL_CALL_ARGS/END routing\n this.toolCallToMessage.set(toolCallId, messageId)\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: chunk.toolCallId,\n name: toolName,\n arguments: '',\n state: initialState,\n ...(chunkMetadata !== undefined && { metadata: chunkMetadata }),\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n chunk.toolCallId,\n initialState,\n '',\n )\n }\n }\n\n /**\n * Handle TOOL_CALL_ARGS event.\n *\n * Appends the delta to the tool call's accumulated arguments string.\n * Transitions state from awaiting-input → input-streaming on first non-empty delta.\n * Attempts partial JSON parse on each update for UI preview.\n *\n * If toolCallId is not found in the Map (no preceding TOOL_CALL_START),\n * this event is silently dropped.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Step-by-step tool call processing\n */\n private handleToolCallArgsEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n ): void {\n const toolCallId = chunk.toolCallId\n const messageId = this.toolCallToMessage.get(toolCallId)\n if (!messageId) return\n\n const state = this.getMessageState(messageId)\n if (!state) return\n\n const existingToolCall = state.toolCalls.get(toolCallId)\n if (!existingToolCall) return\n\n const wasAwaitingInput = existingToolCall.state === 'awaiting-input'\n\n // Accumulate arguments from delta\n existingToolCall.arguments += chunk.delta || ''\n\n // Update state\n if (wasAwaitingInput && chunk.delta) {\n existingToolCall.state = 'input-streaming'\n }\n\n // Try to parse the updated arguments\n existingToolCall.parsedArguments = this.jsonParser.parse(\n existingToolCall.arguments,\n )\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: existingToolCall.id,\n name: existingToolCall.name,\n arguments: existingToolCall.arguments,\n state: existingToolCall.state,\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n existingToolCall.id,\n existingToolCall.state,\n existingToolCall.arguments,\n )\n }\n\n /**\n * Handle TOOL_CALL_END event — authoritative signal that a tool call's input is finalized.\n *\n * This event has a DUAL ROLE:\n * - Without `result`: Signals arguments are done (from adapter). Transitions to input-complete.\n * - With `result`: Signals tool was executed and result is available (from TextEngine).\n * Creates both output on the tool-call part AND a tool-result part.\n *\n * If `input` is provided, it overrides the accumulated string parse as the\n * canonical parsed arguments.\n *\n * @see docs/chat-architecture.md#tool-results-and-the-tool_call_end-dual-role — Full explanation\n * @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow\n */\n private handleToolCallEndEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n ): void {\n const messageId = this.toolCallToMessage.get(chunk.toolCallId)\n if (!messageId) return\n\n const msgState = this.getMessageState(messageId)\n if (!msgState) return\n\n // Transition the tool call to input-complete (the authoritative completion signal)\n const existingToolCall = msgState.toolCalls.get(chunk.toolCallId)\n if (existingToolCall && existingToolCall.state !== 'input-complete') {\n // If TOOL_CALL_END provides parsed input and no TOOL_CALL_ARGS were\n // received, back-fill the arguments string so the UIMessage ToolCallPart\n // carries the correct value (defensive against adapters that skip ARGS).\n if (chunk.input !== undefined && !existingToolCall.arguments) {\n existingToolCall.arguments = JSON.stringify(chunk.input)\n }\n\n const index = msgState.toolCallOrder.indexOf(chunk.toolCallId)\n this.completeToolCall(messageId, index, existingToolCall)\n // If TOOL_CALL_END provides parsed input, use it as the canonical parsed\n // arguments (overrides the accumulated string parse from completeToolCall)\n if (chunk.input !== undefined) {\n existingToolCall.parsedArguments = chunk.input\n }\n }\n\n // Update UIMessage if there's a result\n if (chunk.result) {\n // Step 1: Update the tool-call part's output field (for UI consistency\n // with client tools — see GitHub issue #176)\n let output: unknown\n if (Array.isArray(chunk.result)) {\n output = chunk.result\n } else {\n try {\n output = JSON.parse(chunk.result)\n } catch {\n output = chunk.result\n }\n }\n this.messages = updateToolCallWithOutput(\n this.messages,\n chunk.toolCallId,\n output,\n chunk.state === 'output-error' ? 'error' : undefined,\n )\n\n // Step 2: Create/update the tool-result part (for LLM conversation history)\n const resultState: ToolResultState =\n chunk.state === 'output-error' ? 'error' : 'complete'\n this.messages = updateToolResultPart(\n this.messages,\n messageId,\n chunk.toolCallId,\n chunk.result,\n resultState,\n resultState === 'error'\n ? this.extractToolResultError(output)\n : undefined,\n )\n this.emitMessagesChange()\n }\n }\n\n private extractToolResultError(output: unknown): string {\n if (\n output &&\n typeof output === 'object' &&\n 'error' in output &&\n typeof output.error === 'string'\n ) {\n return output.error\n }\n return typeof output === 'string' ? output : 'Tool execution failed'\n }\n\n /**\n * Handle TOOL_CALL_RESULT event (AG-UI spec).\n *\n * Creates a tool-result part and updates the tool-call output field,\n * mirroring the logic from TOOL_CALL_END when it carries a result.\n * This is the spec-compliant path for delivering tool results to the client.\n */\n private handleToolCallResultEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_RESULT' }>,\n ): void {\n const messageId = this.toolCallToMessage.get(chunk.toolCallId)\n if (!messageId) return\n\n // Step 1: Update the tool-call part's output field\n let output: unknown\n try {\n output = JSON.parse(chunk.content)\n } catch {\n output = chunk.content\n }\n this.messages = updateToolCallWithOutput(\n this.messages,\n chunk.toolCallId,\n output,\n chunk.state === 'output-error' ? 'error' : undefined,\n )\n\n // Step 2: Create/update the tool-result part\n const resultState: ToolResultState =\n chunk.state === 'output-error' ? 'error' : 'complete'\n this.messages = updateToolResultPart(\n this.messages,\n messageId,\n chunk.toolCallId,\n chunk.content,\n resultState,\n resultState === 'error' ? this.extractToolResultError(output) : undefined,\n )\n this.emitMessagesChange()\n }\n\n /**\n * Handle RUN_STARTED event.\n *\n * Registers the run so that RUN_FINISHED can determine whether other\n * runs are still active before finalizing.\n */\n private handleRunStartedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_STARTED' }>,\n ): void {\n this.activeRuns.add(chunk.runId)\n }\n\n /**\n * Handle RUN_FINISHED event.\n *\n * Records the finishReason and removes the run from activeRuns.\n * Only finalizes when no more runs are active, so that concurrent\n * runs don't interfere with each other.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — finishReason semantics\n * @see docs/chat-architecture.md#adapter-contract — Why RUN_FINISHED is mandatory\n */\n private handleRunFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n ): void {\n this.finishReason = chunk.finishReason ?? null\n this.activeRuns.delete(chunk.runId)\n\n if (this.activeRuns.size === 0) {\n this.isDone = true\n this.completeAllToolCalls()\n this.finalizeStream()\n }\n }\n\n /**\n * Handle RUN_ERROR event\n */\n private handleRunErrorEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n ): void {\n this.hasError = true\n const runId =\n 'runId' in chunk && typeof chunk.runId === 'string'\n ? chunk.runId\n : undefined\n if (runId) {\n this.activeRuns.delete(runId)\n } else {\n this.activeRuns.clear()\n }\n const { messageId } = this.ensureAssistantMessage()\n // Prefer spec field `message`; fall back to deprecated `error.message`.\n // If neither is set, the chunk still carries debug context (provider\n // error codes, request ids, etc.) — log it so the failure isn't silent.\n const errorMessage =\n chunk.message || chunk.error?.message || 'An error occurred'\n if (!chunk.message && !chunk.error?.message) {\n console.error(\n '[StreamProcessor] RUN_ERROR with no message; original chunk:',\n chunk,\n )\n }\n\n if (this.structuredMessageIds.has(messageId)) {\n this.flushStructuredOutputUpdate(messageId)\n this.messages = errorStructuredOutputPart(\n this.messages,\n messageId,\n errorMessage,\n )\n this.structuredMessageIds.delete(messageId)\n this.emitStructuredOutputChange(messageId, 'error')\n this.emitMessagesChange()\n }\n\n // Attach the provider's structured error body (`rawEvent`) and `code` to\n // the surfaced Error so consumers can recover the upstream detail that the\n // RUN_ERROR's `message` alone discards. Both are optional and added only\n // when present, keeping the Error backward compatible.\n const error = new Error(errorMessage)\n const code = chunk.code ?? chunk.error?.code\n if (code !== undefined) {\n Object.assign(error, { code })\n }\n if (chunk.rawEvent !== undefined) {\n Object.assign(error, { rawEvent: chunk.rawEvent })\n }\n this.events.onError?.(error)\n }\n\n /**\n * Handle STEP_STARTED event (for thinking/reasoning content).\n *\n * Records the stepId so that subsequent STEP_FINISHED deltas accumulate\n * into their own ThinkingPart. Does not create a message — the message\n * is lazily created when the first STEP_FINISHED content arrives.\n */\n private handleStepStartedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_STARTED' }>,\n ): void {\n const stepId = chunk.stepId ?? generateMessageId()\n const activeId = this.getActiveAssistantMessageId()\n if (activeId) {\n const state = this.getMessageState(activeId)\n if (state) {\n state.currentThinkingStepId = stepId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n }\n // Clear any pending stepId from a prior STEP_STARTED that fired\n // before the assistant message existed. Now that we're tracking\n // the step directly on message state, the pending value is stale\n // and must not leak into the next STEP_FINISHED (which would\n // misattribute its delta to the stale step).\n this.pendingThinkingStepId = null\n return\n }\n }\n\n // No active message yet — defer until ensureAssistantMessage in STEP_FINISHED\n this.pendingThinkingStepId = stepId\n }\n\n /**\n * Handle STEP_FINISHED event (for thinking/reasoning content).\n *\n * Accumulates delta into the current thinking step's content and updates\n * the corresponding ThinkingPart in the UIMessage.\n *\n * @see docs/chat-architecture.md#thinkingreasoning-content — Thinking flow\n */\n private handleStepFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(\n this.getActiveAssistantMessageId() ?? undefined,\n )\n\n // During the transition period, adapters emit BOTH STEP_FINISHED and\n // REASONING_MESSAGE_CONTENT with the same delta. If we've already processed\n // REASONING_MESSAGE_CONTENT events for this message, skip the duplicate\n // thinking content from STEP_FINISHED to avoid doubled content.\n if (state.hasSeenReasoningEvents) {\n if (chunk.signature) {\n const stepId = state.currentThinkingStepId ?? chunk.stepId\n if (!stepId) return\n const thinking = state.thinkingSteps.get(stepId)\n if (thinking !== undefined) {\n state.thinkingStepSignatures.set(stepId, chunk.signature)\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n thinking,\n chunk.signature,\n )\n this.emitMessagesChange()\n }\n }\n return\n }\n\n this.consumePendingThinkingStep(state)\n\n const stepId =\n state.currentThinkingStepId ?? chunk.stepId ?? generateMessageId()\n\n // Auto-initialize if no prior STEP_STARTED (backward compat)\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n state.currentThinkingStepId = stepId\n }\n\n const previous = state.thinkingSteps.get(stepId) ?? ''\n let nextThinking = previous\n\n // Prefer delta over content\n if (chunk.delta && chunk.delta !== '') {\n nextThinking = previous + chunk.delta\n } else if (chunk.content && chunk.content !== '') {\n if (chunk.content.startsWith(previous)) {\n nextThinking = chunk.content\n } else if (previous.startsWith(chunk.content)) {\n nextThinking = previous\n } else {\n nextThinking = previous + chunk.content\n }\n }\n\n state.thinkingSteps.set(stepId, nextThinking)\n\n if (chunk.signature) {\n state.thinkingStepSignatures.set(stepId, chunk.signature)\n }\n\n // Update UIMessage\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n nextThinking,\n state.thinkingStepSignatures.get(stepId),\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)\n }\n\n /**\n * Handle REASONING_MESSAGE_CONTENT event (AG-UI reasoning protocol).\n *\n * Accumulates reasoning delta into thinking content and updates the\n * corresponding ThinkingPart in the UIMessage.\n */\n private handleReasoningMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(\n this.getActiveAssistantMessageId() ?? undefined,\n )\n\n state.hasSeenReasoningEvents = true\n const delta = chunk.delta || ''\n\n this.consumePendingThinkingStep(state)\n\n const stepId = state.currentThinkingStepId ?? chunk.messageId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n state.currentThinkingStepId = stepId\n }\n\n const nextThinking = (state.thinkingSteps.get(stepId) ?? '') + delta\n state.thinkingSteps.set(stepId, nextThinking)\n\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n nextThinking,\n state.thinkingStepSignatures.get(stepId),\n )\n this.emitMessagesChange()\n\n this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)\n }\n\n /**\n * Handle CUSTOM event.\n *\n * Handles special custom events emitted by the TextEngine (not adapters):\n * - 'tool-input-available': Client tool needs execution. Fires onToolCall.\n * - 'approval-requested': Tool needs user approval. Updates tool-call part\n * state and fires onApprovalRequest.\n *\n * @see docs/chat-architecture.md#client-tools-and-approval-flows — Full flow details\n */\n private handleCustomEvent(\n chunk: Extract<StreamChunk, { type: 'CUSTOM' }>,\n ): void {\n const messageId = this.getActiveAssistantMessageId()\n\n if (chunk.name === 'structured-output.start' && chunk.value) {\n const v = chunk.value as { messageId?: string }\n const targetId = v.messageId ?? messageId\n if (targetId) {\n this.ensureAssistantMessage(targetId)\n this.structuredMessageIds.add(targetId)\n this.structuredOutputUpdateBatches.delete(targetId)\n this.events.onStructuredOutputChange?.({\n phase: 'start',\n messageId: targetId,\n status: 'streaming',\n raw: '',\n })\n }\n return\n }\n\n if (chunk.name === 'structured-output.complete' && chunk.value) {\n const v = chunk.value as {\n object: unknown\n raw?: string\n reasoning?: string\n messageId?: string\n }\n const targetId = v.messageId ?? messageId\n if (targetId) {\n this.flushStructuredOutputUpdate(targetId)\n this.messages = completeStructuredOutputPart(\n this.messages,\n targetId,\n v.object,\n v.raw ?? '',\n v.reasoning,\n )\n this.structuredMessageIds.delete(targetId)\n this.emitStructuredOutputChange(targetId, 'complete')\n this.emitMessagesChange()\n }\n // Fall through so user `onCustomEvent` callbacks still observe the event.\n }\n\n // Handle client tool input availability - trigger client-side execution\n if (chunk.name === 'tool-input-available' && chunk.value) {\n const { toolCallId, toolName, input } = chunk.value as {\n toolCallId: string\n toolName: string\n input: any\n }\n\n // Emit onToolCall event for the client to execute the tool\n this.events.onToolCall?.({\n toolCallId,\n toolName,\n input,\n })\n return\n }\n\n // Handle approval requests\n if (chunk.name === 'approval-requested' && chunk.value) {\n const { toolCallId, toolName, input, approval } = chunk.value as {\n toolCallId: string\n toolName: string\n input: any\n approval: { id: string; needsApproval: boolean }\n }\n\n // Resolve the message containing this tool call. After RUN_FINISHED,\n // activeMessageIds is cleared, so fall back to the toolCallToMessage map\n // which is populated during TOOL_CALL_START and preserved across finalize.\n const resolvedMessageId =\n messageId ?? this.toolCallToMessage.get(toolCallId)\n if (resolvedMessageId) {\n this.messages = updateToolCallApproval(\n this.messages,\n resolvedMessageId,\n toolCallId,\n approval.id,\n )\n this.emitMessagesChange()\n }\n\n // Emit approval request event\n this.events.onApprovalRequest?.({\n toolCallId,\n toolName,\n input,\n approvalId: approval.id,\n })\n return\n }\n\n // Forward non-system custom events to onCustomEvent callback\n if (this.events.onCustomEvent) {\n const toolCallId =\n chunk.value && typeof chunk.value === 'object'\n ? chunk.value.toolCallId\n : undefined\n this.events.onCustomEvent(chunk.name, chunk.value, { toolCallId })\n }\n }\n\n // ============================================\n // Internal Helpers\n // ============================================\n\n /**\n * Detect if an incoming content chunk represents a NEW text segment\n */\n private isNewTextSegment(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n previous: string,\n ): boolean {\n // Check if content is present (delta is always defined but may be empty string)\n if (chunk.content !== undefined) {\n if (chunk.content.length < previous.length) {\n return true\n }\n if (\n !chunk.content.startsWith(previous) &&\n !previous.startsWith(chunk.content)\n ) {\n return true\n }\n }\n return false\n }\n\n /**\n * Complete all tool calls across all active messages — safety net for stream termination.\n *\n * Called by RUN_FINISHED and finalizeStream(). Force-transitions any tool call\n * not yet in input-complete state. Handles cases where TOOL_CALL_END was\n * missed (adapter bug, network error, aborted stream).\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Safety net behavior\n */\n private completeAllToolCalls(): void {\n for (const messageId of this.activeMessageIds) {\n this.completeAllToolCallsForMessage(messageId)\n }\n }\n\n /**\n * Complete all tool calls for a specific message\n */\n private completeAllToolCallsForMessage(messageId: string): void {\n const state = this.getMessageState(messageId)\n if (!state) return\n\n state.toolCalls.forEach((toolCall, id) => {\n if (toolCall.state !== 'input-complete') {\n const index = state.toolCallOrder.indexOf(id)\n this.completeToolCall(messageId, index, toolCall)\n }\n })\n }\n\n /**\n * Mark a tool call as complete and emit event\n */\n private completeToolCall(\n messageId: string,\n _index: number,\n toolCall: InternalToolCallState,\n ): void {\n // Finalize the internal bookkeeping: the call's input arguments ARE\n // complete regardless of whether execution later failed, so the call still\n // counts as a completed tool call in getCompletedToolCalls()/getState().\n toolCall.state = 'input-complete'\n\n // Try final parse\n toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments)\n\n // Don't downgrade the rendered part of a call that already reached the\n // terminal 'error' state (e.g. an output-error TOOL_CALL_RESULT arrived\n // without a preceding TOOL_CALL_END). The RUN_FINISHED / finalizeStream\n // safety net must not clobber a failed call back to 'input-complete'.\n if (this.isToolCallPartErrored(toolCall.id)) {\n return\n }\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: 'input-complete',\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n toolCall.id,\n 'input-complete',\n toolCall.arguments,\n )\n }\n\n /**\n * Whether the rendered tool-call part for the given id has reached the\n * terminal 'error' state. Used to prevent the completion safety net from\n * downgrading a failed call back to 'input-complete'.\n */\n private isToolCallPartErrored(toolCallId: string): boolean {\n return this.messages.some((msg) =>\n msg.parts.some(\n (part) =>\n part.type === 'tool-call' &&\n part.id === toolCallId &&\n part.state === 'error',\n ),\n )\n }\n\n /**\n * Emit pending text update for a specific message.\n *\n * Calls updateTextPart() which has critical append-vs-replace logic:\n * - If last UIMessage part is TextPart → replaces its content (same segment).\n * - If last part is anything else → pushes new TextPart (new segment after tools).\n *\n * @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic\n */\n private emitTextUpdateForMessage(messageId: string): void {\n const state = this.getMessageState(messageId)\n if (!state) return\n\n state.lastEmittedText = state.currentSegmentText\n\n // Update UIMessage\n this.messages = updateTextPart(\n this.messages,\n messageId,\n state.currentSegmentText,\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onTextUpdate?.(messageId, state.currentSegmentText)\n }\n\n private queueStructuredOutputUpdate(messageId: string, delta: string): void {\n const existing = this.structuredOutputUpdateBatches.get(messageId)\n const next = {\n delta: `${existing?.delta ?? ''}${delta}`,\n chunkCount: (existing?.chunkCount ?? 0) + 1,\n }\n\n this.structuredOutputUpdateBatches.set(messageId, next)\n\n if (next.chunkCount >= STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE) {\n this.flushStructuredOutputUpdate(messageId)\n }\n }\n\n private flushStructuredOutputUpdate(messageId: string): void {\n const batch = this.structuredOutputUpdateBatches.get(messageId)\n if (!batch || batch.chunkCount === 0) return\n\n this.structuredOutputUpdateBatches.delete(messageId)\n this.emitStructuredOutputChange(messageId, 'update', batch.delta)\n }\n\n private emitStructuredOutputChange(\n messageId: string,\n phase: 'update' | 'complete' | 'error',\n delta?: string,\n ): void {\n const part = this.messages\n .find((message) => message.id === messageId)\n ?.parts.find(\n (\n messagePart,\n ): messagePart is Extract<MessagePart, { type: 'structured-output' }> =>\n messagePart.type === 'structured-output',\n )\n if (!part) return\n\n this.events.onStructuredOutputChange?.({\n phase,\n messageId,\n status: part.status,\n raw: part.raw,\n ...(part.partial !== undefined ? { partial: part.partial } : {}),\n ...(part.data !== undefined ? { data: part.data } : {}),\n ...(part.reasoning !== undefined ? { reasoning: part.reasoning } : {}),\n ...(part.errorMessage !== undefined\n ? { errorMessage: part.errorMessage }\n : {}),\n ...(delta !== undefined ? { delta } : {}),\n })\n }\n\n /**\n * Emit messages change event\n */\n private emitMessagesChange(): void {\n this.events.onMessagesChange?.([...this.messages])\n }\n\n /**\n * Finalize the stream — complete all pending operations.\n *\n * Called when the async iterable ends (stream closed). Acts as the final\n * safety net: completes any remaining tool calls, flushes un-emitted text,\n * and fires onStreamEnd.\n *\n * @see docs/chat-architecture.md#single-shot-text-response — Finalization step\n */\n finalizeStream(): void {\n let lastAssistantMessage: UIMessage | undefined\n\n // Finalize ALL active messages\n for (const messageId of this.activeMessageIds) {\n const state = this.getMessageState(messageId)\n if (!state) continue\n\n // Complete any remaining tool calls\n this.completeAllToolCallsForMessage(messageId)\n\n // Emit any pending text if not already emitted\n if (state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n\n state.isComplete = true\n\n const msg = this.messages.find((m) => m.id === messageId)\n if (msg && msg.role === 'assistant') {\n lastAssistantMessage = msg\n }\n }\n\n // The stream closed but one or more structured-output runs never sent\n // their terminal `structured-output.complete`. Snap each lingering\n // streaming part to error so the UI doesn't appear to stream forever,\n // and drop the routing entries so a subsequent run on the same\n // processor instance (long-lived `subscribe()` mode) doesn't reuse\n // the stale ids.\n //\n // The iteration is unconditional w.r.t. `this.hasError` — RUN_ERROR\n // already removed its target messageId from `structuredMessageIds`\n // before reaching finalize, so anything still in the set is by\n // definition a non-errored, never-completed run (the multi-run case:\n // run-A errors, run-B is still streaming when finalize fires).\n for (const messageId of this.structuredMessageIds) {\n this.flushStructuredOutputUpdate(messageId)\n this.messages = errorStructuredOutputPart(\n this.messages,\n messageId,\n 'Stream ended without structured-output.complete',\n )\n this.emitStructuredOutputChange(messageId, 'error')\n }\n this.structuredMessageIds.clear()\n this.structuredOutputUpdateBatches.clear()\n\n this.activeMessageIds.clear()\n\n // Remove whitespace-only assistant messages (handles models like Gemini\n // that sometimes return just \"\\n\" during auto-continuation).\n // Preserve the message on errors so the UI can show error state.\n if (lastAssistantMessage && !this.hasError) {\n if (this.isWhitespaceOnlyMessage(lastAssistantMessage)) {\n this.messages = this.messages.filter(\n (m) => m.id !== lastAssistantMessage.id,\n )\n this.emitMessagesChange()\n return\n }\n }\n\n // Emit stream end for the last assistant message\n if (lastAssistantMessage) {\n this.events.onStreamEnd?.(lastAssistantMessage)\n }\n }\n\n /**\n * Get completed tool calls in API format (aggregated across all messages)\n */\n private getCompletedToolCalls(): Array<ToolCall> {\n const result: Array<ToolCall> = []\n for (const state of this.messageStates.values()) {\n for (const tc of state.toolCalls.values()) {\n if (tc.state === 'input-complete') {\n result.push({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.name,\n arguments: tc.arguments,\n },\n // Preserve provider metadata (e.g. Gemini thoughtSignature) on\n // ProcessorResult.toolCalls so callers using process()/getResult()\n // get the same round-trip support as the streaming UI path.\n ...(tc.metadata !== undefined && { metadata: tc.metadata }),\n })\n }\n }\n }\n return result\n }\n\n /**\n * Get current result (aggregated across all messages)\n */\n private getResult(): ProcessorResult {\n const toolCalls = this.getCompletedToolCalls()\n let content = ''\n let thinking = ''\n\n for (const state of this.messageStates.values()) {\n content += state.totalTextContent\n for (const stepId of state.thinkingStepOrder) {\n thinking += state.thinkingSteps.get(stepId) ?? ''\n }\n }\n\n return {\n content,\n thinking: thinking || undefined,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n finishReason: this.finishReason,\n }\n }\n\n /**\n * Get current processor state (aggregated across all messages)\n */\n getState(): ProcessorState {\n let content = ''\n let thinking = ''\n const toolCalls = new Map<string, InternalToolCallState>()\n const toolCallOrder: Array<string> = []\n\n for (const state of this.messageStates.values()) {\n content += state.totalTextContent\n for (const stepId of state.thinkingStepOrder) {\n thinking += state.thinkingSteps.get(stepId) ?? ''\n }\n for (const [id, tc] of state.toolCalls) {\n toolCalls.set(id, tc)\n }\n toolCallOrder.push(...state.toolCallOrder)\n }\n\n return {\n content,\n thinking,\n toolCalls,\n toolCallOrder,\n finishReason: this.finishReason,\n done: this.isDone,\n }\n }\n\n /**\n * Start recording chunks\n */\n startRecording(): void {\n this.recordingEnabled = true\n this.recordingStartTime = Date.now()\n this.recording = {\n version: '1.0',\n timestamp: this.recordingStartTime,\n chunks: [],\n }\n }\n\n /**\n * Get the current recording\n */\n getRecording(): ChunkRecording | null {\n return this.recording\n }\n\n /**\n * Reset stream state (but keep messages)\n */\n private resetStreamState(): void {\n this.messageStates.clear()\n this.activeMessageIds.clear()\n this.activeRuns.clear()\n this.toolCallToMessage.clear()\n this.structuredMessageIds.clear()\n this.structuredOutputUpdateBatches.clear()\n this.pendingManualMessageId = null\n this.pendingThinkingStepId = null\n this.finishReason = null\n this.hasError = false\n this.isDone = false\n this.chunkStrategy.reset?.()\n }\n\n /**\n * Full reset (including messages)\n */\n reset(): void {\n this.resetStreamState()\n this.messages = []\n }\n\n /**\n * Check if a message contains only whitespace text and no other meaningful parts\n * (no tool calls, tool results, thinking, etc.)\n */\n private isWhitespaceOnlyMessage(message: UIMessage): boolean {\n if (message.parts.length === 0) return false\n return message.parts.every(\n (part) => part.type === 'text' && part.content.trim() === '',\n )\n }\n\n /**\n * Replay a recording through the processor\n */\n static async replay(\n recording: ChunkRecording,\n options?: StreamProcessorOptions,\n ): Promise<ProcessorResult> {\n const processor = new StreamProcessor(options)\n return processor.process(createReplayStream(recording))\n }\n}\n\n/**\n * Create an async iterable from a recording\n */\nexport function createReplayStream(\n recording: ChunkRecording,\n): AsyncIterable<StreamChunk> {\n return {\n // eslint-disable-next-line @typescript-eslint/require-await -- async generator required by AsyncIterable contract; body has no await\n async *[Symbol.asyncIterator]() {\n for (const { chunk } of recording.chunks) {\n yield chunk\n }\n },\n }\n}\n"],"names":["state","delta","stepId"],"mappings":"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"}
@@ -41,4 +41,4 @@ export type { ChunkRecording, ChunkStrategy, InternalToolCallState, JSONParser,
41
41
  export { uiMessagesToWire } from './utilities/ag-ui-wire.js';
42
42
  export type { WireMessage } from './utilities/ag-ui-wire.js';
43
43
  export type { AudioPart, ContentPart, ContentPartDataSource, ContentPartSource, ContentPartUrlSource, CustomEvent, DocumentPart, ImagePart, MediaInputMetadata, MediaInputRole, MediaPrompt, MediaPromptPart, MessagePart, ModelMessage, RunErrorEvent, RunFinishedEvent, SchemaInput, StreamChunk, StructuredOutputPart, TextPart, ThinkingPart, ToolCall, ToolCallPart, ToolResultPart, UIMessage, VideoPart, InferSchemaType, } from './types.js';
44
- export type { AudioVisualization, RealtimeError, RealtimeErrorCode, RealtimeEvent, RealtimeEventHandler, RealtimeEventPayloads, RealtimeMessage, RealtimeMessagePart, RealtimeMode, RealtimeSessionConfig, RealtimeStatus, RealtimeToken, RealtimeAudioPart, RealtimeImagePart, RealtimeTextPart, RealtimeToolCallPart, RealtimeToolResultPart, VADConfig, } from './realtime/types.js';
44
+ export type { AudioVisualization, RealtimeAdapter, RealtimeConnection, RealtimeError, RealtimeErrorCode, RealtimeEvent, RealtimeEventHandler, RealtimeEventPayloads, RealtimeMessage, RealtimeMessagePart, RealtimeMode, RealtimeSessionConfig, RealtimeStatus, RealtimeToken, RealtimeAudioPart, RealtimeImagePart, RealtimeTextPart, RealtimeToolCallPart, RealtimeToolResultPart, VADConfig, } from './realtime/types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sources":["../../src/client.ts"],"sourcesContent":["export enum EventType {\n TEXT_MESSAGE_START = 'TEXT_MESSAGE_START',\n TEXT_MESSAGE_CONTENT = 'TEXT_MESSAGE_CONTENT',\n TEXT_MESSAGE_END = 'TEXT_MESSAGE_END',\n TEXT_MESSAGE_CHUNK = 'TEXT_MESSAGE_CHUNK',\n TOOL_CALL_START = 'TOOL_CALL_START',\n TOOL_CALL_ARGS = 'TOOL_CALL_ARGS',\n TOOL_CALL_END = 'TOOL_CALL_END',\n TOOL_CALL_CHUNK = 'TOOL_CALL_CHUNK',\n TOOL_CALL_RESULT = 'TOOL_CALL_RESULT',\n THINKING_START = 'THINKING_START',\n THINKING_END = 'THINKING_END',\n THINKING_TEXT_MESSAGE_START = 'THINKING_TEXT_MESSAGE_START',\n THINKING_TEXT_MESSAGE_CONTENT = 'THINKING_TEXT_MESSAGE_CONTENT',\n THINKING_TEXT_MESSAGE_END = 'THINKING_TEXT_MESSAGE_END',\n STATE_SNAPSHOT = 'STATE_SNAPSHOT',\n STATE_DELTA = 'STATE_DELTA',\n MESSAGES_SNAPSHOT = 'MESSAGES_SNAPSHOT',\n ACTIVITY_SNAPSHOT = 'ACTIVITY_SNAPSHOT',\n ACTIVITY_DELTA = 'ACTIVITY_DELTA',\n RAW = 'RAW',\n CUSTOM = 'CUSTOM',\n RUN_STARTED = 'RUN_STARTED',\n RUN_FINISHED = 'RUN_FINISHED',\n RUN_ERROR = 'RUN_ERROR',\n STEP_STARTED = 'STEP_STARTED',\n STEP_FINISHED = 'STEP_FINISHED',\n REASONING_START = 'REASONING_START',\n REASONING_MESSAGE_START = 'REASONING_MESSAGE_START',\n REASONING_MESSAGE_CONTENT = 'REASONING_MESSAGE_CONTENT',\n REASONING_MESSAGE_END = 'REASONING_MESSAGE_END',\n REASONING_MESSAGE_CHUNK = 'REASONING_MESSAGE_CHUNK',\n REASONING_END = 'REASONING_END',\n REASONING_ENCRYPTED_VALUE = 'REASONING_ENCRYPTED_VALUE',\n}\n\nexport {\n toolDefinition,\n type AnyClientTool,\n type ClientTool,\n type InferToolInput,\n type InferToolName,\n type InferToolOutput,\n type ToolDefinition,\n type ToolDefinitionConfig,\n type ToolDefinitionInstance,\n} from './activities/chat/tools/tool-definition'\n\nexport {\n convertSchemaToJsonSchema,\n isStandardSchema,\n parseWithStandardSchema,\n} from './activities/chat/tools/schema-converter'\n\nexport {\n convertMessagesToModelMessages,\n generateMessageId,\n modelMessageToUIMessage,\n modelMessagesToUIMessages,\n normalizeToUIMessage,\n uiMessageToModelMessages,\n} from './activities/chat/messages'\n\nexport {\n BatchStrategy,\n CompositeStrategy,\n defaultJSONParser,\n ImmediateStrategy,\n parsePartialJSON,\n PartialJSONParser,\n PunctuationStrategy,\n StreamProcessor,\n WordBoundaryStrategy,\n} from './activities/chat/stream/index'\nexport type {\n ChunkRecording,\n ChunkStrategy,\n InternalToolCallState,\n JSONParser,\n ProcessorResult,\n ProcessorState,\n StreamProcessorEvents,\n StreamProcessorOptions,\n ToolCallState,\n ToolResultState,\n} from './activities/chat/stream/index'\n\nexport { uiMessagesToWire } from './utilities/ag-ui-wire'\nexport type { WireMessage } from './utilities/ag-ui-wire'\n\nexport type {\n AudioPart,\n ContentPart,\n ContentPartDataSource,\n ContentPartSource,\n ContentPartUrlSource,\n CustomEvent,\n DocumentPart,\n ImagePart,\n MediaInputMetadata,\n MediaInputRole,\n MediaPrompt,\n MediaPromptPart,\n MessagePart,\n ModelMessage,\n RunErrorEvent,\n RunFinishedEvent,\n SchemaInput,\n StreamChunk,\n StructuredOutputPart,\n TextPart,\n ThinkingPart,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n VideoPart,\n InferSchemaType,\n} from './types'\n\nexport type {\n AudioVisualization,\n RealtimeError,\n RealtimeErrorCode,\n RealtimeEvent,\n RealtimeEventHandler,\n RealtimeEventPayloads,\n RealtimeMessage,\n RealtimeMessagePart,\n RealtimeMode,\n RealtimeSessionConfig,\n RealtimeStatus,\n RealtimeToken,\n RealtimeAudioPart,\n RealtimeImagePart,\n RealtimeTextPart,\n RealtimeToolCallPart,\n RealtimeToolResultPart,\n VADConfig,\n} from './realtime/types'\n"],"names":["EventType"],"mappings":";;;;;;;AAAO,IAAK,8BAAAA,eAAL;AACLA,aAAA,oBAAA,IAAqB;AACrBA,aAAA,sBAAA,IAAuB;AACvBA,aAAA,kBAAA,IAAmB;AACnBA,aAAA,oBAAA,IAAqB;AACrBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,eAAA,IAAgB;AAChBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,kBAAA,IAAmB;AACnBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,cAAA,IAAe;AACfA,aAAA,6BAAA,IAA8B;AAC9BA,aAAA,+BAAA,IAAgC;AAChCA,aAAA,2BAAA,IAA4B;AAC5BA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,aAAA,IAAc;AACdA,aAAA,mBAAA,IAAoB;AACpBA,aAAA,mBAAA,IAAoB;AACpBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,KAAA,IAAM;AACNA,aAAA,QAAA,IAAS;AACTA,aAAA,aAAA,IAAc;AACdA,aAAA,cAAA,IAAe;AACfA,aAAA,WAAA,IAAY;AACZA,aAAA,cAAA,IAAe;AACfA,aAAA,eAAA,IAAgB;AAChBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,yBAAA,IAA0B;AAC1BA,aAAA,2BAAA,IAA4B;AAC5BA,aAAA,uBAAA,IAAwB;AACxBA,aAAA,yBAAA,IAA0B;AAC1BA,aAAA,eAAA,IAAgB;AAChBA,aAAA,2BAAA,IAA4B;AAjClB,SAAAA;AAAA,GAAA,aAAA,CAAA,CAAA;"}
1
+ {"version":3,"file":"client.js","sources":["../../src/client.ts"],"sourcesContent":["export enum EventType {\n TEXT_MESSAGE_START = 'TEXT_MESSAGE_START',\n TEXT_MESSAGE_CONTENT = 'TEXT_MESSAGE_CONTENT',\n TEXT_MESSAGE_END = 'TEXT_MESSAGE_END',\n TEXT_MESSAGE_CHUNK = 'TEXT_MESSAGE_CHUNK',\n TOOL_CALL_START = 'TOOL_CALL_START',\n TOOL_CALL_ARGS = 'TOOL_CALL_ARGS',\n TOOL_CALL_END = 'TOOL_CALL_END',\n TOOL_CALL_CHUNK = 'TOOL_CALL_CHUNK',\n TOOL_CALL_RESULT = 'TOOL_CALL_RESULT',\n THINKING_START = 'THINKING_START',\n THINKING_END = 'THINKING_END',\n THINKING_TEXT_MESSAGE_START = 'THINKING_TEXT_MESSAGE_START',\n THINKING_TEXT_MESSAGE_CONTENT = 'THINKING_TEXT_MESSAGE_CONTENT',\n THINKING_TEXT_MESSAGE_END = 'THINKING_TEXT_MESSAGE_END',\n STATE_SNAPSHOT = 'STATE_SNAPSHOT',\n STATE_DELTA = 'STATE_DELTA',\n MESSAGES_SNAPSHOT = 'MESSAGES_SNAPSHOT',\n ACTIVITY_SNAPSHOT = 'ACTIVITY_SNAPSHOT',\n ACTIVITY_DELTA = 'ACTIVITY_DELTA',\n RAW = 'RAW',\n CUSTOM = 'CUSTOM',\n RUN_STARTED = 'RUN_STARTED',\n RUN_FINISHED = 'RUN_FINISHED',\n RUN_ERROR = 'RUN_ERROR',\n STEP_STARTED = 'STEP_STARTED',\n STEP_FINISHED = 'STEP_FINISHED',\n REASONING_START = 'REASONING_START',\n REASONING_MESSAGE_START = 'REASONING_MESSAGE_START',\n REASONING_MESSAGE_CONTENT = 'REASONING_MESSAGE_CONTENT',\n REASONING_MESSAGE_END = 'REASONING_MESSAGE_END',\n REASONING_MESSAGE_CHUNK = 'REASONING_MESSAGE_CHUNK',\n REASONING_END = 'REASONING_END',\n REASONING_ENCRYPTED_VALUE = 'REASONING_ENCRYPTED_VALUE',\n}\n\nexport {\n toolDefinition,\n type AnyClientTool,\n type ClientTool,\n type InferToolInput,\n type InferToolName,\n type InferToolOutput,\n type ToolDefinition,\n type ToolDefinitionConfig,\n type ToolDefinitionInstance,\n} from './activities/chat/tools/tool-definition'\n\nexport {\n convertSchemaToJsonSchema,\n isStandardSchema,\n parseWithStandardSchema,\n} from './activities/chat/tools/schema-converter'\n\nexport {\n convertMessagesToModelMessages,\n generateMessageId,\n modelMessageToUIMessage,\n modelMessagesToUIMessages,\n normalizeToUIMessage,\n uiMessageToModelMessages,\n} from './activities/chat/messages'\n\nexport {\n BatchStrategy,\n CompositeStrategy,\n defaultJSONParser,\n ImmediateStrategy,\n parsePartialJSON,\n PartialJSONParser,\n PunctuationStrategy,\n StreamProcessor,\n WordBoundaryStrategy,\n} from './activities/chat/stream/index'\nexport type {\n ChunkRecording,\n ChunkStrategy,\n InternalToolCallState,\n JSONParser,\n ProcessorResult,\n ProcessorState,\n StreamProcessorEvents,\n StreamProcessorOptions,\n ToolCallState,\n ToolResultState,\n} from './activities/chat/stream/index'\n\nexport { uiMessagesToWire } from './utilities/ag-ui-wire'\nexport type { WireMessage } from './utilities/ag-ui-wire'\n\nexport type {\n AudioPart,\n ContentPart,\n ContentPartDataSource,\n ContentPartSource,\n ContentPartUrlSource,\n CustomEvent,\n DocumentPart,\n ImagePart,\n MediaInputMetadata,\n MediaInputRole,\n MediaPrompt,\n MediaPromptPart,\n MessagePart,\n ModelMessage,\n RunErrorEvent,\n RunFinishedEvent,\n SchemaInput,\n StreamChunk,\n StructuredOutputPart,\n TextPart,\n ThinkingPart,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n VideoPart,\n InferSchemaType,\n} from './types'\n\nexport type {\n AudioVisualization,\n RealtimeAdapter,\n RealtimeConnection,\n RealtimeError,\n RealtimeErrorCode,\n RealtimeEvent,\n RealtimeEventHandler,\n RealtimeEventPayloads,\n RealtimeMessage,\n RealtimeMessagePart,\n RealtimeMode,\n RealtimeSessionConfig,\n RealtimeStatus,\n RealtimeToken,\n RealtimeAudioPart,\n RealtimeImagePart,\n RealtimeTextPart,\n RealtimeToolCallPart,\n RealtimeToolResultPart,\n VADConfig,\n} from './realtime/types'\n"],"names":["EventType"],"mappings":";;;;;;;AAAO,IAAK,8BAAAA,eAAL;AACLA,aAAA,oBAAA,IAAqB;AACrBA,aAAA,sBAAA,IAAuB;AACvBA,aAAA,kBAAA,IAAmB;AACnBA,aAAA,oBAAA,IAAqB;AACrBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,eAAA,IAAgB;AAChBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,kBAAA,IAAmB;AACnBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,cAAA,IAAe;AACfA,aAAA,6BAAA,IAA8B;AAC9BA,aAAA,+BAAA,IAAgC;AAChCA,aAAA,2BAAA,IAA4B;AAC5BA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,aAAA,IAAc;AACdA,aAAA,mBAAA,IAAoB;AACpBA,aAAA,mBAAA,IAAoB;AACpBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,KAAA,IAAM;AACNA,aAAA,QAAA,IAAS;AACTA,aAAA,aAAA,IAAc;AACdA,aAAA,cAAA,IAAe;AACfA,aAAA,WAAA,IAAY;AACZA,aAAA,cAAA,IAAe;AACfA,aAAA,eAAA,IAAgB;AAChBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,yBAAA,IAA0B;AAC1BA,aAAA,2BAAA,IAA4B;AAC5BA,aAAA,uBAAA,IAAwB;AACxBA,aAAA,yBAAA,IAA0B;AAC1BA,aAAA,eAAA,IAAgB;AAChBA,aAAA,2BAAA,IAA4B;AAjClB,SAAAA;AAAA,GAAA,aAAA,CAAA,CAAA;"}
@@ -30,7 +30,7 @@ export type { SystemPrompt, NormalizedSystemPrompt } from './system-prompts.js';
30
30
  export { normalizeSystemPrompts } from './system-prompts.js';
31
31
  export { detectImageMimeType } from './utils.js';
32
32
  export { realtimeToken } from './realtime/index.js';
33
- export type { RealtimeToken, RealtimeTokenAdapter, RealtimeTokenOptions, RealtimeSessionConfig, VADConfig, RealtimeMessage, RealtimeMessagePart, RealtimeTextPart, RealtimeAudioPart, RealtimeToolCallPart, RealtimeToolResultPart, RealtimeImagePart, RealtimeStatus, RealtimeMode, AudioVisualization, RealtimeEvent, RealtimeEventPayloads, RealtimeEventHandler, RealtimeErrorCode, RealtimeError, } from './realtime/index.js';
33
+ export type { RealtimeToken, RealtimeTokenAdapter, RealtimeTokenOptions, RealtimeSessionConfig, VADConfig, RealtimeMessage, RealtimeMessagePart, RealtimeTextPart, RealtimeAudioPart, RealtimeToolCallPart, RealtimeToolResultPart, RealtimeImagePart, RealtimeStatus, RealtimeMode, AudioVisualization, RealtimeEvent, RealtimeEventPayloads, RealtimeEventHandler, RealtimeErrorCode, RealtimeError, RealtimeAdapter, RealtimeConnection, } from './realtime/index.js';
34
34
  export { convertMessagesToModelMessages, generateMessageId, uiMessageToModelMessages, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, } from './activities/chat/messages.js';
35
35
  export { StreamProcessor, createReplayStream, ImmediateStrategy, PunctuationStrategy, BatchStrategy, WordBoundaryStrategy, CompositeStrategy, PartialJSONParser, defaultJSONParser, parsePartialJSON, } from './activities/chat/stream/index.js';
36
36
  export type { ChunkStrategy, ChunkRecording, InternalToolCallState, ProcessorResult, ProcessorState, StreamProcessorEvents, StreamProcessorOptions, ToolCallState, ToolResultState, JSONParser, } from './activities/chat/stream/index.js';
@@ -1,3 +1,4 @@
1
+ import { AnyClientTool } from '../activities/chat/tools/tool-definition.js';
1
2
  /**
2
3
  * Voice activity detection configuration
3
4
  */
@@ -232,3 +233,50 @@ export interface RealtimeError extends Error {
232
233
  provider?: string;
233
234
  details?: unknown;
234
235
  }
236
+ /**
237
+ * Adapter interface for connecting to realtime providers.
238
+ * Each provider (OpenAI, ElevenLabs, etc.) implements this interface.
239
+ *
240
+ * Defined here in `@tanstack/ai` — the shared layer that both provider adapter
241
+ * packages and the client runtime (`@tanstack/ai-client`) already depend on —
242
+ * so a provider package can describe its realtime adapter without taking a
243
+ * dependency on the client-only `@tanstack/ai-client`. `@tanstack/ai-client`
244
+ * re-exports this type for backwards compatibility.
245
+ */
246
+ export interface RealtimeAdapter {
247
+ /** Provider identifier */
248
+ provider: string;
249
+ /**
250
+ * Create a connection using the provided token
251
+ * @param token - The ephemeral token from the server
252
+ * @param clientTools - Optional client-side tools to register with the provider
253
+ * @returns A connection instance
254
+ */
255
+ connect: (token: RealtimeToken, clientTools?: ReadonlyArray<AnyClientTool>) => Promise<RealtimeConnection>;
256
+ }
257
+ /**
258
+ * Connection interface representing an active realtime session.
259
+ * Handles audio I/O, events, and session management.
260
+ */
261
+ export interface RealtimeConnection {
262
+ /** Disconnect from the realtime session */
263
+ disconnect: () => Promise<void>;
264
+ /** Start capturing audio from the microphone */
265
+ startAudioCapture: () => Promise<void>;
266
+ /** Stop capturing audio */
267
+ stopAudioCapture: () => void;
268
+ /** Send a text message (fallback for when voice isn't available) */
269
+ sendText: (text: string) => void;
270
+ /** Send an image to the conversation */
271
+ sendImage: (imageData: string, mimeType: string) => void;
272
+ /** Send a tool execution result back to the provider */
273
+ sendToolResult: (callId: string, result: string) => void;
274
+ /** Update session configuration */
275
+ updateSession: (config: Partial<RealtimeSessionConfig>) => void;
276
+ /** Interrupt the current response */
277
+ interrupt: () => void;
278
+ /** Subscribe to connection events */
279
+ on: <TEvent extends RealtimeEvent>(event: TEvent, handler: RealtimeEventHandler<TEvent>) => () => void;
280
+ /** Get audio visualization data */
281
+ getAudioVisualization: () => AudioVisualization;
282
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -76,7 +76,7 @@
76
76
  "@ag-ui/core": "^0.0.52",
77
77
  "@standard-schema/spec": "^1.1.0",
78
78
  "partial-json": "^0.1.7",
79
- "@tanstack/ai-event-client": "0.6.5",
79
+ "@tanstack/ai-event-client": "0.6.6",
80
80
  "@tanstack/ai-utils": "0.3.0"
81
81
  },
82
82
  "peerDependencies": {
@@ -3,10 +3,10 @@ name: ai-core/media-generation
3
3
  description: >
4
4
  Image, audio, video, speech (TTS), and transcription generation using
5
5
  activity-specific adapters: generateImage() with openaiImage/geminiImage,
6
- generateAudio() with geminiAudio/falAudio, generateVideo() with
7
- openaiVideo/geminiVideo (async polling, per-model typed durations),
8
- generateSpeech() with openaiSpeech, generateTranscription() with
9
- openaiTranscription. React hooks: useGenerateImage, useGenerateAudio,
6
+ generateAudio() with geminiAudio/falAudio, generateVideo() with async
7
+ polling (openaiVideo/geminiVideo/grokVideo/falVideo, per-model typed
8
+ durations), generateSpeech() with openaiSpeech, generateTranscription()
9
+ with openaiTranscription. React hooks: useGenerateImage, useGenerateAudio,
10
10
  useGenerateSpeech, useTranscription, useGenerateVideo.
11
11
  TanStack Start server function integration with toServerSentEventsResponse.
12
12
  type: sub-skill
@@ -454,6 +454,13 @@ const { jobId } = await generateVideo({
454
454
  // (x-goog-api-key header or ?key= query parameter).
455
455
  ```
456
456
 
457
+ Other video adapters: `openaiVideo('sora-2')` (pixel sizes like `'1280x720'`,
458
+ durations 4/8/12s, single `input_reference` image prompt part), `grokVideo(...)`
459
+ (`grok-imagine-video` does text-to-video + image-to-video; `grok-imagine-video-1.5` is
460
+ image-to-video only — needs an `image` prompt part as the starting frame, text-only throws;
461
+ aspect-ratio size template like `'16:9_720p'`, integer durations 1-15s, reports
462
+ `usage.unitsBilled` seconds and exact `usage.cost`), and `falVideo(...)` (hosted models, see cost tracking below).
463
+
457
464
  Client hook with job tracking:
458
465
 
459
466
  ```tsx
@@ -1,4 +1,5 @@
1
1
  import { normalizeToolResult } from '../../utilities/tool-result'
2
+ import type { Message as AGUIMessage } from '@ag-ui/core'
2
3
  import type {
3
4
  ContentPart,
4
5
  MessagePart,
@@ -456,6 +457,108 @@ export function modelMessageToUIMessage(
456
457
  }
457
458
  }
458
459
 
460
+ /**
461
+ * Normalize a single AG-UI `MESSAGES_SNAPSHOT` message into a `UIMessage`.
462
+ *
463
+ * AG-UI snapshot messages use the wire shape `{ id, role, content }` and have
464
+ * no `parts` array. Casting them directly to `UIMessage` is unsafe: any code
465
+ * that later reads `message.parts` (e.g. the devtools `onToolCallStateChange`
466
+ * handler) crashes with "Cannot read properties of undefined (reading 'find')".
467
+ *
468
+ * Each role is mapped to the canonical `UIMessage` shape, reusing
469
+ * `modelMessageToUIMessage` for the roles that share `ModelMessage`'s structure.
470
+ * The original AG-UI `id` is preserved so later `TEXT_MESSAGE_CONTENT` /
471
+ * `TOOL_CALL_*` events still route by `messageId` (falling back to a generated
472
+ * id only when the snapshot omits one). Messages that already carry `parts`
473
+ * (e.g. a TanStack server echoing `UIMessage`s back over the wire) pass through
474
+ * unchanged apart from ensuring an id.
475
+ */
476
+ export function aguiSnapshotMessageToUIMessage(
477
+ message: AGUIMessage | UIMessage,
478
+ ): UIMessage {
479
+ if ('parts' in message) {
480
+ return { ...message, id: message.id || generateMessageId() }
481
+ }
482
+
483
+ const id = message.id || generateMessageId()
484
+
485
+ switch (message.role) {
486
+ case 'user':
487
+ return {
488
+ id,
489
+ role: 'user',
490
+ parts: aguiUserContentToParts(message.content),
491
+ }
492
+ case 'assistant':
493
+ return modelMessageToUIMessage(
494
+ {
495
+ role: 'assistant',
496
+ content: message.content ?? null,
497
+ ...(message.toolCalls && { toolCalls: message.toolCalls }),
498
+ },
499
+ id,
500
+ )
501
+ case 'tool':
502
+ return modelMessageToUIMessage(
503
+ {
504
+ role: 'tool',
505
+ content: message.content,
506
+ toolCallId: message.toolCallId,
507
+ },
508
+ id,
509
+ )
510
+ case 'system':
511
+ case 'developer':
512
+ // `ModelMessage` has no system/developer role; build the part directly.
513
+ return {
514
+ id,
515
+ role: 'system',
516
+ parts: message.content
517
+ ? [{ type: 'text', content: message.content }]
518
+ : [],
519
+ }
520
+ case 'reasoning':
521
+ return {
522
+ id,
523
+ role: 'assistant',
524
+ parts: message.content
525
+ ? [{ type: 'thinking', content: message.content }]
526
+ : [],
527
+ }
528
+ case 'activity':
529
+ default:
530
+ // `activity` (and any future role) has no text/parts equivalent today.
531
+ return { id, role: 'assistant', parts: [] }
532
+ }
533
+ }
534
+
535
+ /**
536
+ * Convert AG-UI user message content into `UIMessage` parts.
537
+ *
538
+ * AG-UI user content is either a plain string or a multimodal array whose text
539
+ * entries use `{ type: 'text', text }` (vs. TanStack's `{ type: 'text', content }`).
540
+ * Text entries are rewritten to the TanStack shape; image/audio/video/document
541
+ * entries already match `ContentPart` and pass through. `binary` entries have no
542
+ * TanStack equivalent and are dropped.
543
+ */
544
+ function aguiUserContentToParts(
545
+ content: Extract<AGUIMessage, { role: 'user' }>['content'],
546
+ ): Array<MessagePart> {
547
+ if (typeof content === 'string') {
548
+ return content ? [{ type: 'text', content }] : []
549
+ }
550
+
551
+ const parts: Array<MessagePart> = []
552
+ for (const part of content) {
553
+ if (part.type === 'text') {
554
+ parts.push({ type: 'text', content: part.text })
555
+ } else if (part.type !== 'binary') {
556
+ parts.push(part)
557
+ }
558
+ }
559
+ return parts
560
+ }
561
+
459
562
  /**
460
563
  * Convert an array of ModelMessages to UIMessages
461
564
  *
@@ -17,7 +17,11 @@
17
17
  * @see docs/chat-architecture.md — Canonical reference for AG-UI chunk ordering,
18
18
  * adapter contract, single-shot flows, and expected UIMessage output.
19
19
  */
20
- import { generateMessageId, uiMessageToModelMessages } from '../messages.js'
20
+ import {
21
+ aguiSnapshotMessageToUIMessage,
22
+ generateMessageId,
23
+ uiMessageToModelMessages,
24
+ } from '../messages.js'
21
25
  import { normalizeToolResult } from '../../../utilities/tool-result'
22
26
  import { defaultJSONParser } from './json-parser'
23
27
  import {
@@ -870,8 +874,12 @@ export class StreamProcessor {
870
874
  chunk: Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,
871
875
  ): void {
872
876
  this.resetStreamState()
873
- // AG-UI Message[] is compatible with UIMessage[] at runtime
874
- this.messages = [...chunk.messages] as Array<UIMessage>
877
+ // Normalize AG-UI snapshot messages to UIMessage[] so every message has a
878
+ // `parts` array. AG-UI messages carry `content` but no `parts`, so casting
879
+ // them directly to UIMessage[] is unsafe and causes "Cannot read properties
880
+ // of undefined (reading 'find')" when code later reads message.parts (e.g.
881
+ // the onToolCallStateChange devtools handler).
882
+ this.messages = chunk.messages.map(aguiSnapshotMessageToUIMessage)
875
883
  this.emitMessagesChange()
876
884
  }
877
885
 
package/src/client.ts CHANGED
@@ -120,6 +120,8 @@ export type {
120
120
 
121
121
  export type {
122
122
  AudioVisualization,
123
+ RealtimeAdapter,
124
+ RealtimeConnection,
123
125
  RealtimeError,
124
126
  RealtimeErrorCode,
125
127
  RealtimeEvent,
package/src/index.ts CHANGED
@@ -191,6 +191,8 @@ export type {
191
191
  RealtimeEventHandler,
192
192
  RealtimeErrorCode,
193
193
  RealtimeError,
194
+ RealtimeAdapter,
195
+ RealtimeConnection,
194
196
  } from './realtime/index'
195
197
 
196
198
  // Message converters
@@ -1,3 +1,5 @@
1
+ import type { AnyClientTool } from '../activities/chat/tools/tool-definition'
2
+
1
3
  // ============================================================================
2
4
  // Token Types
3
5
  // ============================================================================
@@ -292,3 +294,78 @@ export interface RealtimeError extends Error {
292
294
  provider?: string
293
295
  details?: unknown
294
296
  }
297
+
298
+ // ============================================================================
299
+ // Adapter Contract
300
+ // ============================================================================
301
+
302
+ /**
303
+ * Adapter interface for connecting to realtime providers.
304
+ * Each provider (OpenAI, ElevenLabs, etc.) implements this interface.
305
+ *
306
+ * Defined here in `@tanstack/ai` — the shared layer that both provider adapter
307
+ * packages and the client runtime (`@tanstack/ai-client`) already depend on —
308
+ * so a provider package can describe its realtime adapter without taking a
309
+ * dependency on the client-only `@tanstack/ai-client`. `@tanstack/ai-client`
310
+ * re-exports this type for backwards compatibility.
311
+ */
312
+ export interface RealtimeAdapter {
313
+ /** Provider identifier */
314
+ provider: string
315
+
316
+ /**
317
+ * Create a connection using the provided token
318
+ * @param token - The ephemeral token from the server
319
+ * @param clientTools - Optional client-side tools to register with the provider
320
+ * @returns A connection instance
321
+ */
322
+ connect: (
323
+ token: RealtimeToken,
324
+ clientTools?: ReadonlyArray<AnyClientTool>,
325
+ ) => Promise<RealtimeConnection>
326
+ }
327
+
328
+ /**
329
+ * Connection interface representing an active realtime session.
330
+ * Handles audio I/O, events, and session management.
331
+ */
332
+ export interface RealtimeConnection {
333
+ // Lifecycle
334
+ /** Disconnect from the realtime session */
335
+ disconnect: () => Promise<void>
336
+
337
+ // Audio I/O
338
+ /** Start capturing audio from the microphone */
339
+ startAudioCapture: () => Promise<void>
340
+ /** Stop capturing audio */
341
+ stopAudioCapture: () => void
342
+
343
+ // Text input
344
+ /** Send a text message (fallback for when voice isn't available) */
345
+ sendText: (text: string) => void
346
+
347
+ // Image input
348
+ /** Send an image to the conversation */
349
+ sendImage: (imageData: string, mimeType: string) => void
350
+
351
+ // Tool results
352
+ /** Send a tool execution result back to the provider */
353
+ sendToolResult: (callId: string, result: string) => void
354
+
355
+ // Session management
356
+ /** Update session configuration */
357
+ updateSession: (config: Partial<RealtimeSessionConfig>) => void
358
+ /** Interrupt the current response */
359
+ interrupt: () => void
360
+
361
+ // Events
362
+ /** Subscribe to connection events */
363
+ on: <TEvent extends RealtimeEvent>(
364
+ event: TEvent,
365
+ handler: RealtimeEventHandler<TEvent>,
366
+ ) => () => void
367
+
368
+ // Audio visualization
369
+ /** Get audio visualization data */
370
+ getAudioVisualization: () => AudioVisualization
371
+ }