@tanstack/ai 0.44.1 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/esm/activities/chat/adapter.d.ts +13 -1
  2. package/dist/esm/activities/chat/adapter.js.map +1 -1
  3. package/dist/esm/activities/chat/index.js +108 -68
  4. package/dist/esm/activities/chat/index.js.map +1 -1
  5. package/dist/esm/activities/chat/messages.js +24 -27
  6. package/dist/esm/activities/chat/messages.js.map +1 -1
  7. package/dist/esm/activities/chat/stream/processor.js +14 -13
  8. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  9. package/dist/esm/activities/chat/tools/approval-schema.js +11 -8
  10. package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -1
  11. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
  12. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  13. package/dist/esm/activities/chat/tools/tool-calls.js +40 -31
  14. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  15. package/dist/esm/activities/generateVideo/index.js +3 -2
  16. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  17. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  18. package/dist/esm/adapter-internals.d.ts +2 -0
  19. package/dist/esm/adapter-internals.js +3 -1
  20. package/dist/esm/interrupt-resume.js +24 -20
  21. package/dist/esm/interrupt-resume.js.map +1 -1
  22. package/dist/esm/interrupts.js +2 -1
  23. package/dist/esm/interrupts.js.map +1 -1
  24. package/dist/esm/logger/console-logger.js +1 -3
  25. package/dist/esm/logger/console-logger.js.map +1 -1
  26. package/dist/esm/logger/resolve.js +2 -1
  27. package/dist/esm/logger/resolve.js.map +1 -1
  28. package/dist/esm/stream-durability.js +1 -1
  29. package/dist/esm/stream-durability.js.map +1 -1
  30. package/dist/esm/types.d.ts +19 -16
  31. package/dist/esm/utilities/chat-params.js +1 -3
  32. package/dist/esm/utilities/chat-params.js.map +1 -1
  33. package/dist/esm/utilities/media-prompt.js +1 -3
  34. package/dist/esm/utilities/media-prompt.js.map +1 -1
  35. package/dist/esm/utilities/structured-output-events.d.ts +17 -0
  36. package/dist/esm/utilities/structured-output-events.js +32 -0
  37. package/dist/esm/utilities/structured-output-events.js.map +1 -0
  38. package/dist/esm/utilities/structured-output-text.d.ts +7 -0
  39. package/dist/esm/utilities/structured-output-text.js +50 -0
  40. package/dist/esm/utilities/structured-output-text.js.map +1 -0
  41. package/package.json +2 -2
  42. package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +6 -2
  43. package/skills/ai-core/media-generation/SKILL.md +33 -19
  44. package/skills/ai-core/structured-outputs/SKILL.md +73 -1
  45. package/skills/ai-core/tool-calling/SKILL.md +1 -1
  46. package/src/activities/chat/adapter.ts +16 -1
  47. package/src/activities/chat/index.ts +128 -47
  48. package/src/activities/chat/stream/processor.ts +6 -3
  49. package/src/activities/chat/tools/tool-calls.ts +12 -4
  50. package/src/adapter-internals.ts +8 -0
  51. package/src/types.ts +27 -19
  52. package/src/utilities/structured-output-events.ts +44 -0
  53. package/src/utilities/structured-output-text.ts +63 -0
@@ -1 +1 @@
1
- {"version":3,"file":"processor.js","names":[],"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 { isProviderExecutedToolCall } from '../../../utilities/provider-executed'\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 Interrupt,\n MessagePart,\n ModelMessage,\n StreamChunk,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n UIResourceEvent,\n UIResourcePart,\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 // 4. It is provider-executed (e.g. Anthropic web_search) — already run by\n // the provider, so there is no client result to wait for.\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 isProviderExecutedToolCall(part),\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 //\n // The AG-UI `MESSAGES_SNAPSHOT` wire shape cannot reconstruct client-side\n // tool-call metadata a server may omit: a `role: 'tool'` message only carries\n // `toolCallId` + `content`, and an assistant message in the snapshot may\n // drop `toolCalls` the client already observed via `TOOL_CALL_*` events.\n // Without the matching `tool-call` part, later `addToolResult(toolCallId)`\n // calls cannot locate the call and warn + no-op (see #859). To keep the\n // UI representation consistent with the streaming fan-out and preserve the\n // unreconstructable metadata, reconcile the normalized snapshot against\n // the pre-snapshot state; see `reconcileSnapshotToolCalls`.\n const prevMessages = this.messages\n const normalized = chunk.messages.map(aguiSnapshotMessageToUIMessage)\n this.messages = this.reconcileSnapshotToolCalls(normalized, prevMessages)\n this.emitMessagesChange()\n }\n\n /**\n * Reconcile a freshly normalized snapshot with the pre-snapshot message\n * state so unreconstructable tool-call metadata is preserved.\n *\n * Post-pass (a): anchor `tool-result`-only assistant messages (the shape\n * `aguiSnapshotMessageToUIMessage` emits for AG-UI `role: 'tool'` wire\n * messages) into the message containing the matching `tool-call` part, or —\n * when the snapshot supplies no such part — the nearest earlier anchorable\n * assistant message, matching the in-stream fan-out shape\n * `assistant: [text, tool-call, tool-result, ...]`. Detached messages with\n * no earlier anchorable assistant are kept verbatim.\n *\n * Post-pass (b): when a `tool-result` part references a `toolCallId` whose\n * `tool-call` part is absent from the snapshot, carry the `tool-call` part\n * forward from the pre-snapshot state (state and output untouched) so a\n * subsequent `addToolResult(toolCallId)` can still locate the call.\n *\n * Post-pass (c): AG-UI wire snapshots rebuild `tool-call` parts as\n * `input-complete` without `output` (ModelMessage has no result field on\n * the call). After anchoring results, copy each `tool-result` onto its\n * matching `tool-call` (and prefer pre-snapshot complete/output when the\n * snapshot is poorer) so server tools keep the same UI shape as client tools.\n */\n private reconcileSnapshotToolCalls(\n snapshot: Array<UIMessage>,\n prevMessages: Array<UIMessage>,\n ): Array<UIMessage> {\n // Index tool-call parts observed before the snapshot by id so we can\n // restore metadata the snapshot cannot re-emit. Duplicate ids resolve\n // last-write-wins: the same tool call can appear in multiple messages\n // across reconnects, and the most recent part carries the freshest state.\n const prevToolCalls = new Map<string, ToolCallPart>()\n for (const msg of prevMessages) {\n for (const part of msg.parts) {\n if (part.type === 'tool-call') {\n prevToolCalls.set(part.id, part)\n }\n }\n }\n // Index tool-call parts already present in the snapshot so (b) only fills\n // genuine gaps rather than duplicating a tool-call the snapshot supplies.\n const snapshotToolCallIds = new Set<string>()\n for (const msg of snapshot) {\n for (const part of msg.parts) {\n if (part.type === 'tool-call') {\n snapshotToolCallIds.add(part.id)\n }\n }\n }\n\n const reconciled: Array<UIMessage> = []\n for (const msg of snapshot) {\n const toolResultPart =\n msg.role === 'assistant' && msg.parts.length === 1\n ? msg.parts.find((p): p is ToolResultPart => p.type === 'tool-result')\n : undefined\n\n if (!toolResultPart) {\n reconciled.push(msg)\n continue\n }\n\n // Prefer the message that actually contains the matching tool-call\n // part. AG-UI `reasoning`/`activity` messages also normalize to\n // `role: 'assistant'`, so anchoring into the nearest assistant alone\n // could separate a result from its call (and a later\n // `addToolResult(toolCallId)` would then append a duplicate result\n // next to the call).\n const target =\n reconciled.findLast((m) =>\n m.parts.some(\n (p) => p.type === 'tool-call' && p.id === toolResultPart.toolCallId,\n ),\n ) ??\n reconciled.findLast(\n (m) =>\n m.role === 'assistant' &&\n !(m.parts.length === 1 && m.parts[0]?.type === 'tool-result'),\n )\n\n if (!target) {\n // No assistant to anchor into — keep the detached message intact.\n if (!snapshotToolCallIds.has(toolResultPart.toolCallId)) {\n console.warn(\n `[StreamProcessor] MESSAGES_SNAPSHOT contains a tool-result for \"${toolResultPart.toolCallId}\" but no matching tool-call exists in the snapshot, and there is no assistant message to anchor into; addToolResult(\"${toolResultPart.toolCallId}\") will not be able to locate this call`,\n )\n }\n reconciled.push(msg)\n continue\n }\n\n const parts = [...target.parts]\n // (b) Fill in a missing tool-call part from the pre-snapshot state when\n // the snapshot references its id via a tool-result but supplies no\n // tool-call metadata of its own.\n if (\n !snapshotToolCallIds.has(toolResultPart.toolCallId) &&\n !parts.some(\n (p) => p.type === 'tool-call' && p.id === toolResultPart.toolCallId,\n )\n ) {\n const prev = prevToolCalls.get(toolResultPart.toolCallId)\n if (prev) {\n // Insert the carried-over tool-call before its tool-result (pushed\n // below) so call→result ordering matches the streaming fan-out.\n parts.push({ ...prev })\n snapshotToolCallIds.add(prev.id)\n } else {\n console.warn(\n `[StreamProcessor] MESSAGES_SNAPSHOT contains a tool-result for \"${toolResultPart.toolCallId}\" but no matching tool-call exists in the snapshot or the pre-snapshot state; addToolResult(\"${toolResultPart.toolCallId}\") will not be able to locate this call`,\n )\n }\n }\n parts.push(toolResultPart)\n // Replace rather than push into `target.parts`: a snapshot message that\n // arrived already carrying `parts` (TanStack server echoing UIMessages)\n // shares its array with the incoming chunk, and mutating it in place\n // would corrupt the caller's event object.\n target.parts = parts\n }\n\n return this.enrichSnapshotToolCallsFromResults(reconciled, prevToolCalls)\n }\n\n /**\n * Post-pass (c): fold `tool-result` content into sibling `tool-call` parts\n * and prefer pre-snapshot complete/output when the snapshot rebuilt a\n * poorer `input-complete` call (AG-UI ModelMessage has no result on calls).\n */\n private enrichSnapshotToolCallsFromResults(\n messages: Array<UIMessage>,\n prevToolCalls: Map<string, ToolCallPart>,\n ): Array<UIMessage> {\n const resultsByCallId = new Map<string, ToolResultPart>()\n for (const msg of messages) {\n for (const part of msg.parts) {\n if (part.type === 'tool-result') {\n resultsByCallId.set(part.toolCallId, part)\n }\n }\n }\n\n return messages.map((msg) => {\n const parts = msg.parts.map((part) => {\n if (part.type !== 'tool-call') return part\n\n const prev = prevToolCalls.get(part.id)\n const result = resultsByCallId.get(part.id)\n let next: ToolCallPart = part\n\n // Prefer a pre-snapshot call that already carried output/complete —\n // the client observed TOOL_CALL_END/RESULT before the snapshot wipe.\n if (\n prev &&\n (prev.output !== undefined ||\n prev.state === 'complete' ||\n prev.state === 'error') &&\n (part.output === undefined ||\n part.state === 'input-complete' ||\n part.state === 'input-streaming' ||\n part.state === 'awaiting-input')\n ) {\n next = {\n ...part,\n ...(prev.output !== undefined ? { output: prev.output } : {}),\n state: prev.state,\n ...(prev.approval !== undefined ? { approval: prev.approval } : {}),\n ...(prev.metadata !== undefined ? { metadata: prev.metadata } : {}),\n }\n }\n\n // Apply sibling tool-result when the call still has no output.\n if (result && next.output === undefined) {\n let output: unknown\n if (Array.isArray(result.content)) {\n output = result.content\n } else {\n try {\n output = JSON.parse(result.content)\n } catch {\n output = result.content\n }\n }\n const errorText =\n result.state === 'error'\n ? this.extractToolResultError(output)\n : undefined\n next = {\n ...next,\n output: errorText ? { error: errorText } : output,\n state: result.state === 'error' ? 'error' : 'complete',\n }\n }\n\n return next\n })\n // Rebuild only when a part object identity changed.\n const partsChanged = parts.some(\n (part, index) => part !== msg.parts[index],\n )\n return partsChanged ? { ...msg, parts } : msg\n })\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 try {\n existingToolCall.arguments = JSON.stringify(chunk.input)\n } catch {\n // circular refs, BigInt, etc. — leave arguments empty rather than\n // aborting stream processing\n }\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 // and refresh the rendered part's `input` so it reflects the canonical\n // value rather than the possibly-divergent accumulated-args parse that\n // completeToolCall wrote (e.g. an adapter that coerces values differently\n // between the streamed args and the final structured input).\n if (chunk.input !== undefined) {\n existingToolCall.parsedArguments = chunk.input\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: existingToolCall.id,\n name: existingToolCall.name,\n arguments: existingToolCall.arguments,\n state: 'input-complete',\n input: chunk.input,\n ...(existingToolCall.metadata !== undefined && {\n metadata: existingToolCall.metadata,\n }),\n })\n this.emitMessagesChange()\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 (chunk.outcome?.type === 'interrupt') {\n this.handleInterrupts(chunk.outcome.interrupts)\n }\n\n if (this.activeRuns.size === 0) {\n this.isDone = true\n this.completeAllToolCalls()\n this.finalizeStream()\n }\n }\n\n private handleInterrupts(interrupts: Array<Interrupt>): void {\n for (const interrupt of interrupts) {\n const metadata =\n interrupt.metadata && typeof interrupt.metadata === 'object'\n ? interrupt.metadata\n : {}\n const kind = typeof metadata.kind === 'string' ? metadata.kind : undefined\n const toolCallId = interrupt.toolCallId\n if (!toolCallId) continue\n\n const toolName =\n typeof metadata.toolName === 'string'\n ? metadata.toolName\n : this.findToolCallName(toolCallId)\n const input = Object.hasOwn(metadata, 'input') ? metadata.input : {}\n\n if (kind === 'approval' || interrupt.reason === 'approval_required') {\n // An interrupt terminal arrives after MESSAGES_SNAPSHOT, which may have\n // rebuilt the message list without the live active-message/tool-call\n // maps. Fall back to locating the assistant message that actually owns\n // the tool-call part so the approval UI (part.state) still updates.\n const resolvedMessageId =\n this.getActiveAssistantMessageId() ??\n this.toolCallToMessage.get(toolCallId) ??\n this.messages.find(\n (m) =>\n m.role === 'assistant' &&\n m.parts.some(\n (p) => p.type === 'tool-call' && p.id === toolCallId,\n ),\n )?.id\n if (resolvedMessageId) {\n this.messages = updateToolCallApproval(\n this.messages,\n resolvedMessageId,\n toolCallId,\n interrupt.id,\n )\n this.emitMessagesChange()\n }\n\n this.events.onApprovalRequest?.({\n toolCallId,\n toolName,\n input,\n approvalId: interrupt.id,\n })\n continue\n }\n\n if (kind === 'client_tool' || interrupt.reason === 'client_tool_input') {\n this.events.onToolCall?.({\n toolCallId,\n toolName,\n input,\n })\n }\n }\n }\n\n private findToolCallName(toolCallId: string): string {\n for (const state of this.messageStates.values()) {\n const toolCall = state.toolCalls.get(toolCallId)\n if (toolCall) return toolCall.name\n }\n return ''\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 custom events consumed by the processor:\n * - 'tool-input-available': Legacy/replay-compatible input for client tool\n * execution. Fires onToolCall.\n * - 'approval-requested': Legacy/replay-compatible input for tool approval.\n * Updates tool-call part state and fires onApprovalRequest.\n *\n * Current core streams represent user-actionable waits through\n * RUN_FINISHED.outcome.type === 'interrupt'; these custom events are not the\n * source of truth for new emissions.\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 // Handle MCP Apps ui-resource events — materialize a UIResourcePart on the\n // active assistant message. Never falls through to onCustomEvent because\n // ui-resource is a system event, not a user-defined custom event.\n if (chunk.name === 'ui-resource' && chunk.value) {\n const v: UIResourceEvent['value'] = chunk.value\n // Resolve the target assistant message. When a toolCallId is present, the\n // tool call's OWNER message is authoritative, so prefer it first; fall\n // back to the active assistant id only if the tool call isn't mapped.\n // This avoids misattaching the widget to a different active message in a\n // multi-message session.\n const resolvedMessageId =\n this.toolCallToMessage.get(v.toolCallId) ?? messageId\n if (resolvedMessageId) {\n const part: UIResourcePart = {\n type: 'ui-resource',\n resource: v.resource,\n toolCallId: v.toolCallId,\n toolName: v.toolName,\n ...(v.serverId !== undefined && { serverId: v.serverId }),\n ...(v.meta !== undefined && { meta: v.meta }),\n }\n this.messages = this.messages.map((msg) =>\n msg.id === resolvedMessageId\n ? { ...msg, parts: [...msg.parts, part] }\n : msg,\n )\n this.emitMessagesChange()\n } else {\n // No owner message and no active assistant id — the server read and\n // streamed a widget that has nowhere to attach (e.g. a toolCallId never\n // registered, or the event arrived after the run cleared its active\n // ids). Drop fail-soft, but warn: a vanished widget is otherwise\n // undebuggable from the client.\n console.warn(\n `[mcp-apps] dropped ui-resource: no target message for toolCallId \"${v.toolCallId}\" (toolName \"${v.toolName}\")`,\n )\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 // RUN_FINISHED interrupt handling can mark the rendered part as waiting on\n // user action before the completion safety net runs. Keep the parsed\n // argument bookkeeping above, but do not downgrade that visible state.\n if (this.isToolCallPartAwaitingUserAction(toolCall.id)) {\n return\n }\n\n // Update UIMessage. The arguments are complete now, so surface the parsed\n // input on the part. For adapters that skip TOOL_CALL_ARGS the arguments\n // string was back-filled from TOOL_CALL_END.input, so this parse matches\n // the canonical input. If a TOOL_CALL_END.input diverges from the\n // accumulated args, handleToolCallEndEvent re-updates the part with the\n // canonical value after this call.\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.parsedArguments !== undefined && {\n input: toolCall.parsedArguments,\n }),\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 private isToolCallPartAwaitingUserAction(toolCallId: string): boolean {\n return this.messages.some((msg) =>\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- `parts` is typed as required, but seeded ModelMessage-shaped messages can lack it at runtime.\n msg.parts?.some(\n (part) =>\n part.type === 'tool-call' &&\n part.id === toolCallId &&\n (part.state === 'approval-requested' ||\n part.state === 'approval-responded'),\n ),\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 // `initialMessages` may be ModelMessage-shaped (no `parts`) — e.g. the\n // common pattern of seeding a processor with the same messages passed to\n // `chat()`. Guard the access so iterating them never throws.\n return this.messages.some((msg) =>\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- `parts` is typed as required, but seeded ModelMessage-shaped messages can lack it at runtime.\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"],"mappings":"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"}
1
+ {"version":3,"file":"processor.js","names":[],"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 { isProviderExecutedToolCall } from '../../../utilities/provider-executed'\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 Interrupt,\n MessagePart,\n ModelMessage,\n StreamChunk,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n UIResourceEvent,\n UIResourcePart,\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 // 4. It is provider-executed (e.g. Anthropic web_search) — already run by\n // the provider, so there is no client result to wait for.\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 isProviderExecutedToolCall(part),\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 //\n // The AG-UI `MESSAGES_SNAPSHOT` wire shape cannot reconstruct client-side\n // tool-call metadata a server may omit: a `role: 'tool'` message only carries\n // `toolCallId` + `content`, and an assistant message in the snapshot may\n // drop `toolCalls` the client already observed via `TOOL_CALL_*` events.\n // Without the matching `tool-call` part, later `addToolResult(toolCallId)`\n // calls cannot locate the call and warn + no-op (see #859). To keep the\n // UI representation consistent with the streaming fan-out and preserve the\n // unreconstructable metadata, reconcile the normalized snapshot against\n // the pre-snapshot state; see `reconcileSnapshotToolCalls`.\n const prevMessages = this.messages\n const normalized = chunk.messages.map(aguiSnapshotMessageToUIMessage)\n this.messages = this.reconcileSnapshotToolCalls(normalized, prevMessages)\n this.emitMessagesChange()\n }\n\n /**\n * Reconcile a freshly normalized snapshot with the pre-snapshot message\n * state so unreconstructable tool-call metadata is preserved.\n *\n * Post-pass (a): anchor `tool-result`-only assistant messages (the shape\n * `aguiSnapshotMessageToUIMessage` emits for AG-UI `role: 'tool'` wire\n * messages) into the message containing the matching `tool-call` part, or —\n * when the snapshot supplies no such part — the nearest earlier anchorable\n * assistant message, matching the in-stream fan-out shape\n * `assistant: [text, tool-call, tool-result, ...]`. Detached messages with\n * no earlier anchorable assistant are kept verbatim.\n *\n * Post-pass (b): when a `tool-result` part references a `toolCallId` whose\n * `tool-call` part is absent from the snapshot, carry the `tool-call` part\n * forward from the pre-snapshot state (state and output untouched) so a\n * subsequent `addToolResult(toolCallId)` can still locate the call.\n *\n * Post-pass (c): AG-UI wire snapshots rebuild `tool-call` parts as\n * `input-complete` without `output` (ModelMessage has no result field on\n * the call). After anchoring results, copy each `tool-result` onto its\n * matching `tool-call` (and prefer pre-snapshot complete/output when the\n * snapshot is poorer) so server tools keep the same UI shape as client tools.\n */\n private reconcileSnapshotToolCalls(\n snapshot: Array<UIMessage>,\n prevMessages: Array<UIMessage>,\n ): Array<UIMessage> {\n // Index tool-call parts observed before the snapshot by id so we can\n // restore metadata the snapshot cannot re-emit. Duplicate ids resolve\n // last-write-wins: the same tool call can appear in multiple messages\n // across reconnects, and the most recent part carries the freshest state.\n const prevToolCalls = new Map<string, ToolCallPart>()\n for (const msg of prevMessages) {\n for (const part of msg.parts) {\n if (part.type === 'tool-call') {\n prevToolCalls.set(part.id, part)\n }\n }\n }\n // Index tool-call parts already present in the snapshot so (b) only fills\n // genuine gaps rather than duplicating a tool-call the snapshot supplies.\n const snapshotToolCallIds = new Set<string>()\n for (const msg of snapshot) {\n for (const part of msg.parts) {\n if (part.type === 'tool-call') {\n snapshotToolCallIds.add(part.id)\n }\n }\n }\n\n const reconciled: Array<UIMessage> = []\n for (const msg of snapshot) {\n const toolResultPart =\n msg.role === 'assistant' && msg.parts.length === 1\n ? msg.parts.find((p): p is ToolResultPart => p.type === 'tool-result')\n : undefined\n\n if (!toolResultPart) {\n reconciled.push(msg)\n continue\n }\n\n // Prefer the message that actually contains the matching tool-call\n // part. AG-UI `reasoning`/`activity` messages also normalize to\n // `role: 'assistant'`, so anchoring into the nearest assistant alone\n // could separate a result from its call (and a later\n // `addToolResult(toolCallId)` would then append a duplicate result\n // next to the call).\n const target =\n reconciled.findLast((m) =>\n m.parts.some(\n (p) => p.type === 'tool-call' && p.id === toolResultPart.toolCallId,\n ),\n ) ??\n reconciled.findLast(\n (m) =>\n m.role === 'assistant' &&\n !(m.parts.length === 1 && m.parts[0]?.type === 'tool-result'),\n )\n\n if (!target) {\n // No assistant to anchor into — keep the detached message intact.\n if (!snapshotToolCallIds.has(toolResultPart.toolCallId)) {\n console.warn(\n `[StreamProcessor] MESSAGES_SNAPSHOT contains a tool-result for \"${toolResultPart.toolCallId}\" but no matching tool-call exists in the snapshot, and there is no assistant message to anchor into; addToolResult(\"${toolResultPart.toolCallId}\") will not be able to locate this call`,\n )\n }\n reconciled.push(msg)\n continue\n }\n\n const parts = [...target.parts]\n // (b) Fill in a missing tool-call part from the pre-snapshot state when\n // the snapshot references its id via a tool-result but supplies no\n // tool-call metadata of its own.\n if (\n !snapshotToolCallIds.has(toolResultPart.toolCallId) &&\n !parts.some(\n (p) => p.type === 'tool-call' && p.id === toolResultPart.toolCallId,\n )\n ) {\n const prev = prevToolCalls.get(toolResultPart.toolCallId)\n if (prev) {\n // Insert the carried-over tool-call before its tool-result (pushed\n // below) so call→result ordering matches the streaming fan-out.\n parts.push({ ...prev })\n snapshotToolCallIds.add(prev.id)\n } else {\n console.warn(\n `[StreamProcessor] MESSAGES_SNAPSHOT contains a tool-result for \"${toolResultPart.toolCallId}\" but no matching tool-call exists in the snapshot or the pre-snapshot state; addToolResult(\"${toolResultPart.toolCallId}\") will not be able to locate this call`,\n )\n }\n }\n parts.push(toolResultPart)\n // Replace rather than push into `target.parts`: a snapshot message that\n // arrived already carrying `parts` (TanStack server echoing UIMessages)\n // shares its array with the incoming chunk, and mutating it in place\n // would corrupt the caller's event object.\n target.parts = parts\n }\n\n return this.enrichSnapshotToolCallsFromResults(reconciled, prevToolCalls)\n }\n\n /**\n * Post-pass (c): fold `tool-result` content into sibling `tool-call` parts\n * and prefer pre-snapshot complete/output when the snapshot rebuilt a\n * poorer `input-complete` call (AG-UI ModelMessage has no result on calls).\n */\n private enrichSnapshotToolCallsFromResults(\n messages: Array<UIMessage>,\n prevToolCalls: Map<string, ToolCallPart>,\n ): Array<UIMessage> {\n const resultsByCallId = new Map<string, ToolResultPart>()\n for (const msg of messages) {\n for (const part of msg.parts) {\n if (part.type === 'tool-result') {\n resultsByCallId.set(part.toolCallId, part)\n }\n }\n }\n\n return messages.map((msg) => {\n const parts = msg.parts.map((part) => {\n if (part.type !== 'tool-call') return part\n\n const prev = prevToolCalls.get(part.id)\n const result = resultsByCallId.get(part.id)\n let next: ToolCallPart = part\n\n // Prefer a pre-snapshot call that already carried output/complete —\n // the client observed TOOL_CALL_END/RESULT before the snapshot wipe.\n if (\n prev &&\n (prev.output !== undefined ||\n prev.state === 'complete' ||\n prev.state === 'error') &&\n (part.output === undefined ||\n part.state === 'input-complete' ||\n part.state === 'input-streaming' ||\n part.state === 'awaiting-input')\n ) {\n next = {\n ...part,\n ...(prev.output !== undefined ? { output: prev.output } : {}),\n state: prev.state,\n ...(prev.approval !== undefined ? { approval: prev.approval } : {}),\n ...(prev.metadata !== undefined ? { metadata: prev.metadata } : {}),\n }\n }\n\n // Apply sibling tool-result when the call still has no output.\n if (result && next.output === undefined) {\n let output: unknown\n if (Array.isArray(result.content)) {\n output = result.content\n } else {\n try {\n output = JSON.parse(result.content)\n } catch {\n output = result.content\n }\n }\n const errorText =\n result.state === 'error'\n ? this.extractToolResultError(output)\n : undefined\n next = {\n ...next,\n output: errorText ? { error: errorText } : output,\n state: result.state === 'error' ? 'error' : 'complete',\n }\n }\n\n return next\n })\n // Rebuild only when a part object identity changed.\n const partsChanged = parts.some(\n (part, index) => part !== msg.parts[index],\n )\n return partsChanged ? { ...msg, parts } : msg\n })\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 try {\n existingToolCall.arguments = JSON.stringify(chunk.input)\n } catch {\n // circular refs, BigInt, etc. — leave arguments empty rather than\n // aborting stream processing\n }\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 // and refresh the rendered part's `input` so it reflects the canonical\n // value rather than the possibly-divergent accumulated-args parse that\n // completeToolCall wrote (e.g. an adapter that coerces values differently\n // between the streamed args and the final structured input).\n if (chunk.input !== undefined) {\n existingToolCall.parsedArguments = chunk.input\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: existingToolCall.id,\n name: existingToolCall.name,\n arguments: existingToolCall.arguments,\n state: 'input-complete',\n input: chunk.input,\n ...(existingToolCall.metadata !== undefined && {\n metadata: existingToolCall.metadata,\n }),\n })\n this.emitMessagesChange()\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 (chunk.outcome?.type === 'interrupt') {\n this.handleInterrupts(chunk.outcome.interrupts)\n }\n\n if (this.activeRuns.size === 0) {\n this.isDone = true\n this.completeAllToolCalls()\n this.finalizeStream()\n }\n }\n\n private handleInterrupts(interrupts: Array<Interrupt>): void {\n for (const interrupt of interrupts) {\n const metadata =\n interrupt.metadata && typeof interrupt.metadata === 'object'\n ? interrupt.metadata\n : {}\n const kind = typeof metadata.kind === 'string' ? metadata.kind : undefined\n const toolCallId = interrupt.toolCallId\n if (!toolCallId) continue\n\n const toolName =\n typeof metadata.toolName === 'string'\n ? metadata.toolName\n : this.findToolCallName(toolCallId)\n const input = Object.hasOwn(metadata, 'input') ? metadata.input : {}\n\n if (kind === 'approval' || interrupt.reason === 'approval_required') {\n // An interrupt terminal arrives after MESSAGES_SNAPSHOT, which may have\n // rebuilt the message list without the live active-message/tool-call\n // maps. Fall back to locating the assistant message that actually owns\n // the tool-call part so the approval UI (part.state) still updates.\n const resolvedMessageId =\n this.getActiveAssistantMessageId() ??\n this.toolCallToMessage.get(toolCallId) ??\n this.messages.find(\n (m) =>\n m.role === 'assistant' &&\n m.parts.some(\n (p) => p.type === 'tool-call' && p.id === toolCallId,\n ),\n )?.id\n if (resolvedMessageId) {\n this.messages = updateToolCallApproval(\n this.messages,\n resolvedMessageId,\n toolCallId,\n interrupt.id,\n )\n this.emitMessagesChange()\n }\n\n this.events.onApprovalRequest?.({\n toolCallId,\n toolName,\n input,\n approvalId: interrupt.id,\n })\n continue\n }\n\n if (kind === 'client_tool' || interrupt.reason === 'client_tool_input') {\n this.events.onToolCall?.({\n toolCallId,\n toolName,\n input,\n })\n }\n }\n }\n\n private findToolCallName(toolCallId: string): string {\n for (const state of this.messageStates.values()) {\n const toolCall = state.toolCalls.get(toolCallId)\n if (toolCall) return toolCall.name\n }\n return ''\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 custom events consumed by the processor:\n * - 'tool-input-available': Legacy/replay-compatible input for client tool\n * execution. Fires onToolCall.\n * - 'approval-requested': Legacy/replay-compatible input for tool approval.\n * Updates tool-call part state and fires onApprovalRequest.\n *\n * Current core streams represent user-actionable waits through\n * RUN_FINISHED.outcome.type === 'interrupt'; these custom events are not the\n * source of truth for new emissions.\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 { messageId: targetId } = this.ensureAssistantMessage(\n v.messageId ?? messageId ?? undefined,\n )\n if (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 { messageId: targetId } = this.ensureAssistantMessage(\n v.messageId ?? messageId ?? undefined,\n )\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 // Handle MCP Apps ui-resource events — materialize a UIResourcePart on the\n // active assistant message. Never falls through to onCustomEvent because\n // ui-resource is a system event, not a user-defined custom event.\n if (chunk.name === 'ui-resource' && chunk.value) {\n const v: UIResourceEvent['value'] = chunk.value\n // Resolve the target assistant message. When a toolCallId is present, the\n // tool call's OWNER message is authoritative, so prefer it first; fall\n // back to the active assistant id only if the tool call isn't mapped.\n // This avoids misattaching the widget to a different active message in a\n // multi-message session.\n const resolvedMessageId =\n this.toolCallToMessage.get(v.toolCallId) ?? messageId\n if (resolvedMessageId) {\n const part: UIResourcePart = {\n type: 'ui-resource',\n resource: v.resource,\n toolCallId: v.toolCallId,\n toolName: v.toolName,\n ...(v.serverId !== undefined && { serverId: v.serverId }),\n ...(v.meta !== undefined && { meta: v.meta }),\n }\n this.messages = this.messages.map((msg) =>\n msg.id === resolvedMessageId\n ? { ...msg, parts: [...msg.parts, part] }\n : msg,\n )\n this.emitMessagesChange()\n } else {\n // No owner message and no active assistant id — the server read and\n // streamed a widget that has nowhere to attach (e.g. a toolCallId never\n // registered, or the event arrived after the run cleared its active\n // ids). Drop fail-soft, but warn: a vanished widget is otherwise\n // undebuggable from the client.\n console.warn(\n `[mcp-apps] dropped ui-resource: no target message for toolCallId \"${v.toolCallId}\" (toolName \"${v.toolName}\")`,\n )\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 // RUN_FINISHED interrupt handling can mark the rendered part as waiting on\n // user action before the completion safety net runs. Keep the parsed\n // argument bookkeeping above, but do not downgrade that visible state.\n if (this.isToolCallPartAwaitingUserAction(toolCall.id)) {\n return\n }\n\n // Update UIMessage. The arguments are complete now, so surface the parsed\n // input on the part. For adapters that skip TOOL_CALL_ARGS the arguments\n // string was back-filled from TOOL_CALL_END.input, so this parse matches\n // the canonical input. If a TOOL_CALL_END.input diverges from the\n // accumulated args, handleToolCallEndEvent re-updates the part with the\n // canonical value after this call.\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.parsedArguments !== undefined && {\n input: toolCall.parsedArguments,\n }),\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 private isToolCallPartAwaitingUserAction(toolCallId: string): boolean {\n return this.messages.some((msg) =>\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- `parts` is typed as required, but seeded ModelMessage-shaped messages can lack it at runtime.\n msg.parts?.some(\n (part) =>\n part.type === 'tool-call' &&\n part.id === toolCallId &&\n (part.state === 'approval-requested' ||\n part.state === 'approval-responded'),\n ),\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 // `initialMessages` may be ModelMessage-shaped (no `parts`) — e.g. the\n // common pattern of seeding a processor with the same messages passed to\n // `chat()`. Guard the access so iterating them never throws.\n return this.messages.some((msg) =>\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- `parts` is typed as required, but seeded ModelMessage-shaped messages can lack it at runtime.\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"],"mappings":"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"}
@@ -76,13 +76,15 @@ function normalizeApprovalSchema(approvalSchema, inputSchema) {
76
76
  const normalizedInput = inputSchema === void 0 ? null : schemaToWire(inputSchema);
77
77
  let approve = null;
78
78
  let reject = null;
79
- if (approvalSchema !== void 0) if (isStandardSchema(approvalSchema) || isRawJsonSchema(approvalSchema)) {
80
- approve = schemaToWire(approvalSchema);
81
- reject = approve;
82
- } else if (isApprovalBranchMap(approvalSchema)) {
83
- approve = approvalSchema.approve === void 0 ? null : schemaToWire(approvalSchema.approve);
84
- reject = approvalSchema.reject === void 0 ? null : schemaToWire(approvalSchema.reject);
85
- } else throw new TypeError("approvalSchema must be a SchemaInput or a nonempty map containing approve or reject.");
79
+ if (approvalSchema !== void 0) {
80
+ if (isStandardSchema(approvalSchema) || isRawJsonSchema(approvalSchema)) {
81
+ approve = schemaToWire(approvalSchema);
82
+ reject = approve;
83
+ } else if (isApprovalBranchMap(approvalSchema)) {
84
+ approve = approvalSchema.approve === void 0 ? null : schemaToWire(approvalSchema.approve);
85
+ reject = approvalSchema.reject === void 0 ? null : schemaToWire(approvalSchema.reject);
86
+ } else throw new TypeError("approvalSchema must be a SchemaInput or a nonempty map containing approve or reject.");
87
+ }
86
88
  const responseSchema = { oneOf: [decisionEnvelope({
87
89
  approved: true,
88
90
  payload: approve,
@@ -109,7 +111,8 @@ function normalizeApprovalSchema(approvalSchema, inputSchema) {
109
111
  }
110
112
  function hashSchemaInput(schema) {
111
113
  if (schema === void 0) return digestInterruptJson("undefined");
112
- return digestInterruptJson(canonicalInterruptJson(schemaToWire(schema).jsonSchema ?? { standardValidator: "unserialized" }));
114
+ const identity = schemaToWire(schema).jsonSchema ?? { standardValidator: "unserialized" };
115
+ return digestInterruptJson(canonicalInterruptJson(identity));
113
116
  }
114
117
  //#endregion
115
118
  export { hashSchemaInput, normalizeApprovalSchema };