@tanstack/ai 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"processor.js","sources":["../../../../../src/activities/chat/stream/processor.ts"],"sourcesContent":["/**\n * Unified Stream Processor\n *\n * Core stream processing engine that manages the full UIMessage[] conversation.\n * Single source of truth for message state.\n *\n * Handles:\n * - Full conversation management (UIMessage[])\n * - Text content accumulation with configurable chunking strategies\n * - Parallel tool calls with lifecycle state tracking\n * - Tool results and approval flows\n * - Thinking/reasoning content\n * - Recording/replay for testing\n * - Event-driven architecture for UI updates\n */\nimport { generateMessageId, uiMessageToModelMessages } from '../messages.js'\nimport { defaultJSONParser } from './json-parser'\nimport {\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 ProcessorResult,\n ProcessorState,\n ToolCallState,\n ToolResultState,\n} from './types'\nimport type {\n ModelMessage,\n StreamChunk,\n ToolCall,\n ToolCallPart,\n UIMessage,\n} from '../../../types'\n\n/**\n * Events emitted by the StreamProcessor\n */\nexport interface StreamProcessorEvents {\n // State events - full array on any change\n onMessagesChange?: (messages: Array<UIMessage>) => void\n\n // Lifecycle events\n onStreamStart?: () => void\n onStreamEnd?: (message: UIMessage) => void\n onError?: (error: Error) => void\n\n // Interaction events - client must handle these\n onToolCall?: (args: {\n toolCallId: string\n toolName: string\n input: any\n }) => void\n onApprovalRequest?: (args: {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n }) => void\n\n // 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?: (messageId: string, content: string) => 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\n/**\n * StreamProcessor - State machine for processing AI response streams\n *\n * Manages the full UIMessage[] conversation and emits events on changes.\n *\n * State tracking:\n * - Full message array\n * - Current assistant message being streamed\n * - Text content accumulation\n * - Multiple parallel tool calls\n * - Tool call completion detection\n *\n * Tool call completion is detected when:\n * 1. A new tool call starts at a different index\n * 2. Text content arrives\n * 3. Stream ends\n */\nexport class StreamProcessor {\n private chunkStrategy: ChunkStrategy\n private events: StreamProcessorEvents\n private jsonParser: { parse: (jsonString: string) => any }\n private recordingEnabled: boolean\n\n // Message state\n private messages: Array<UIMessage> = []\n private currentAssistantMessageId: string | null = null\n\n // Stream state for current assistant message\n // Total accumulated text across all segments (for the final result)\n private totalTextContent = ''\n // Current segment's text content (for onTextUpdate callbacks)\n private currentSegmentText = ''\n private lastEmittedText = ''\n private thinkingContent = ''\n private toolCalls: Map<string, InternalToolCallState> = new Map()\n private toolCallOrder: Array<string> = []\n private finishReason: string | null = null\n private isDone = false\n // Track if we've had tool calls since the last text segment started\n private hasToolCallsSinceTextStart = 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 */\n addUserMessage(content: string): UIMessage {\n const userMessage: UIMessage = {\n id: generateMessageId(),\n role: 'user',\n parts: [{ type: 'text', content }],\n createdAt: new Date(),\n }\n\n this.messages = [...this.messages, userMessage]\n this.emitMessagesChange()\n\n return userMessage\n }\n\n /**\n * Start streaming a new assistant message\n * Returns the message ID\n */\n startAssistantMessage(): string {\n // Reset stream state for new message\n this.resetStreamState()\n\n const assistantMessage: UIMessage = {\n id: generateMessageId(),\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n\n this.currentAssistantMessageId = assistantMessage.id\n this.messages = [...this.messages, assistantMessage]\n\n // Emit events\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n\n return assistantMessage.id\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 ? 'input-complete' : undefined,\n error,\n )\n\n // Step 2: Create a tool-result part (for LLM conversation history)\n const content = typeof output === 'string' ? output : JSON.stringify(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 // All tool calls must be in a terminal state\n return toolParts.every(\n (part) =>\n part.state === 'approval-responded' ||\n (part.output !== undefined && !part.approval),\n )\n }\n\n /**\n * Remove messages after a certain index (for reload/retry)\n */\n removeMessagesAfter(index: number): void {\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.currentAssistantMessageId = 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 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 switch (chunk.type) {\n // AG-UI Events\n case 'TEXT_MESSAGE_CONTENT':\n this.handleTextMessageContentEvent(chunk)\n break\n\n case 'TOOL_CALL_START':\n this.handleToolCallStartEvent(chunk)\n break\n\n case 'TOOL_CALL_ARGS':\n this.handleToolCallArgsEvent(chunk)\n break\n\n case 'TOOL_CALL_END':\n this.handleToolCallEndEvent(chunk)\n break\n\n case 'RUN_FINISHED':\n this.handleRunFinishedEvent(chunk)\n break\n\n case 'RUN_ERROR':\n this.handleRunErrorEvent(chunk)\n break\n\n case 'STEP_FINISHED':\n this.handleStepFinishedEvent(chunk)\n break\n\n case 'CUSTOM':\n this.handleCustomEvent(chunk)\n break\n\n default:\n // RUN_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_END, STEP_STARTED,\n // STATE_SNAPSHOT, STATE_DELTA - no special handling needed\n break\n }\n }\n\n /**\n * Handle TEXT_MESSAGE_CONTENT event\n */\n private handleTextMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n ): void {\n // Content arriving means all current tool calls are complete\n this.completeAllToolCalls()\n\n const previousSegment = this.currentSegmentText\n\n // Detect if this is a NEW text segment (after tool calls) vs continuation\n const isNewSegment =\n this.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 !== this.lastEmittedText) {\n this.emitTextUpdate()\n }\n // Reset SEGMENT text accumulation for the new text segment after tool calls\n this.currentSegmentText = ''\n this.lastEmittedText = ''\n this.hasToolCallsSinceTextStart = false\n }\n\n const currentText = this.currentSegmentText\n let nextText = currentText\n\n // Prefer delta over content - delta is the incremental change\n if (chunk.delta !== '') {\n nextText = currentText + chunk.delta\n } else if (chunk.content && 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 this.currentSegmentText = nextText\n this.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 this.currentSegmentText,\n )\n if (shouldEmit && this.currentSegmentText !== this.lastEmittedText) {\n this.emitTextUpdate()\n }\n }\n\n /**\n * Handle TOOL_CALL_START event\n */\n private handleToolCallStartEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n ): void {\n // Mark that we've seen tool calls since the last text segment\n this.hasToolCallsSinceTextStart = true\n\n const toolCallId = chunk.toolCallId\n const existingToolCall = this.toolCalls.get(toolCallId)\n\n if (!existingToolCall) {\n // New tool call starting\n const initialState: ToolCallState = 'awaiting-input'\n\n const newToolCall: InternalToolCallState = {\n id: chunk.toolCallId,\n name: chunk.toolName,\n arguments: '',\n state: initialState,\n parsedArguments: undefined,\n index: chunk.index ?? this.toolCalls.size,\n }\n\n this.toolCalls.set(toolCallId, newToolCall)\n this.toolCallOrder.push(toolCallId)\n\n // Update UIMessage\n if (this.currentAssistantMessageId) {\n this.messages = updateToolCallPart(\n this.messages,\n this.currentAssistantMessageId,\n {\n id: chunk.toolCallId,\n name: chunk.toolName,\n arguments: '',\n state: initialState,\n },\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n this.currentAssistantMessageId,\n chunk.toolCallId,\n initialState,\n '',\n )\n }\n }\n }\n\n /**\n * Handle TOOL_CALL_ARGS event\n */\n private handleToolCallArgsEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n ): void {\n const toolCallId = chunk.toolCallId\n const existingToolCall = this.toolCalls.get(toolCallId)\n\n if (existingToolCall) {\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 if (this.currentAssistantMessageId) {\n this.messages = updateToolCallPart(\n this.messages,\n this.currentAssistantMessageId,\n {\n id: existingToolCall.id,\n name: existingToolCall.name,\n arguments: existingToolCall.arguments,\n state: existingToolCall.state,\n },\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n this.currentAssistantMessageId,\n existingToolCall.id,\n existingToolCall.state,\n existingToolCall.arguments,\n )\n }\n }\n }\n\n /**\n * Handle TOOL_CALL_END event\n */\n private handleToolCallEndEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n ): void {\n const state: ToolResultState = 'complete'\n\n // Update UIMessage if we have a current assistant message\n if (this.currentAssistantMessageId && chunk.result) {\n this.messages = updateToolResultPart(\n this.messages,\n this.currentAssistantMessageId,\n chunk.toolCallId,\n chunk.result,\n state,\n )\n this.emitMessagesChange()\n }\n }\n\n /**\n * Handle RUN_FINISHED event\n */\n private handleRunFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n ): void {\n this.finishReason = chunk.finishReason\n this.isDone = true\n this.completeAllToolCalls()\n }\n\n /**\n * Handle RUN_ERROR event\n */\n private handleRunErrorEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n ): void {\n // Emit error event\n this.events.onError?.(new Error(chunk.error.message || 'An error occurred'))\n }\n\n /**\n * Handle STEP_FINISHED event (for thinking/reasoning content)\n */\n private handleStepFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n ): void {\n const previous = this.thinkingContent\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 this.thinkingContent = nextThinking\n\n // Update UIMessage\n if (this.currentAssistantMessageId) {\n this.messages = updateThinkingPart(\n this.messages,\n this.currentAssistantMessageId,\n this.thinkingContent,\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onThinkingUpdate?.(\n this.currentAssistantMessageId,\n this.thinkingContent,\n )\n }\n }\n\n /**\n * Handle CUSTOM event\n * Handles special custom events like 'tool-input-available' for client-side tool execution\n * and 'approval-requested' for tool approval flows\n */\n private handleCustomEvent(\n chunk: Extract<StreamChunk, { type: 'CUSTOM' }>,\n ): void {\n // Handle client tool input availability - trigger client-side execution\n if (chunk.name === 'tool-input-available' && chunk.data) {\n const { toolCallId, toolName, input } = chunk.data 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 }\n\n // Handle approval requests\n if (chunk.name === 'approval-requested' && chunk.data) {\n const { toolCallId, toolName, input, approval } = chunk.data as {\n toolCallId: string\n toolName: string\n input: any\n approval: { id: string; needsApproval: boolean }\n }\n\n // Update the tool call part with approval state\n if (this.currentAssistantMessageId) {\n this.messages = updateToolCallApproval(\n this.messages,\n this.currentAssistantMessageId,\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 }\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\n */\n private completeAllToolCalls(): void {\n this.toolCalls.forEach((toolCall, id) => {\n if (toolCall.state !== 'input-complete') {\n const index = this.toolCallOrder.indexOf(id)\n this.completeToolCall(index, toolCall)\n }\n })\n }\n\n /**\n * Mark a tool call as complete and emit event\n */\n private completeToolCall(\n _index: number,\n toolCall: InternalToolCallState,\n ): void {\n toolCall.state = 'input-complete'\n\n // Try final parse\n toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments)\n\n // Update UIMessage\n if (this.currentAssistantMessageId) {\n this.messages = updateToolCallPart(\n this.messages,\n this.currentAssistantMessageId,\n {\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: 'input-complete',\n },\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n this.currentAssistantMessageId,\n toolCall.id,\n 'input-complete',\n toolCall.arguments,\n )\n }\n }\n\n /**\n * Emit pending text update\n */\n private emitTextUpdate(): void {\n this.lastEmittedText = this.currentSegmentText\n\n // Update UIMessage\n if (this.currentAssistantMessageId) {\n this.messages = updateTextPart(\n this.messages,\n this.currentAssistantMessageId,\n this.currentSegmentText,\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onTextUpdate?.(\n this.currentAssistantMessageId,\n this.currentSegmentText,\n )\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 finalizeStream(): void {\n // Complete any remaining tool calls\n this.completeAllToolCalls()\n\n // Emit any pending text if not already emitted\n if (this.currentSegmentText !== this.lastEmittedText) {\n this.emitTextUpdate()\n }\n\n // Emit stream end event\n if (this.currentAssistantMessageId) {\n const assistantMessage = this.messages.find(\n (m) => m.id === this.currentAssistantMessageId,\n )\n if (assistantMessage) {\n this.events.onStreamEnd?.(assistantMessage)\n }\n }\n }\n\n /**\n * Get completed tool calls in API format\n */\n private getCompletedToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCalls.values())\n .filter((tc) => tc.state === 'input-complete')\n .map((tc) => ({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.name,\n arguments: tc.arguments,\n },\n }))\n }\n\n /**\n * Get current result\n */\n private getResult(): ProcessorResult {\n const toolCalls = this.getCompletedToolCalls()\n return {\n content: this.totalTextContent,\n thinking: this.thinkingContent || undefined,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n finishReason: this.finishReason,\n }\n }\n\n /**\n * Get current processor state\n */\n getState(): ProcessorState {\n return {\n content: this.totalTextContent,\n thinking: this.thinkingContent,\n toolCalls: new Map(this.toolCalls),\n toolCallOrder: [...this.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.totalTextContent = ''\n this.currentSegmentText = ''\n this.lastEmittedText = ''\n this.thinkingContent = ''\n this.toolCalls.clear()\n this.toolCallOrder = []\n this.finishReason = null\n this.isDone = false\n this.hasToolCallsSinceTextStart = false\n this.chunkStrategy.reset?.()\n }\n\n /**\n * Full reset (including messages)\n */\n reset(): void {\n this.resetStreamState()\n this.messages = []\n this.currentAssistantMessageId = null\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\n async *[Symbol.asyncIterator]() {\n for (const { chunk } of recording.chunks) {\n yield chunk\n }\n },\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"processor.js","sources":["../../../../../src/activities/chat/stream/processor.ts"],"sourcesContent":["/**\n * Unified Stream Processor\n *\n * Core stream processing engine that manages the full UIMessage[] conversation.\n * Single source of truth for message state.\n *\n * Handles:\n * - Full conversation management (UIMessage[])\n * - Text content accumulation with configurable chunking strategies\n * - Parallel tool calls with lifecycle state tracking\n * - Tool results and approval flows\n * - Thinking/reasoning content\n * - Recording/replay for testing\n * - Event-driven architecture for UI updates\n */\nimport { generateMessageId, uiMessageToModelMessages } from '../messages.js'\nimport { defaultJSONParser } from './json-parser'\nimport {\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 ProcessorResult,\n ProcessorState,\n ToolCallState,\n ToolResultState,\n} from './types'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StreamChunk,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n} from '../../../types'\n\n/**\n * Events emitted by the StreamProcessor\n */\nexport interface StreamProcessorEvents {\n // State events - full array on any change\n onMessagesChange?: (messages: Array<UIMessage>) => void\n\n // Lifecycle events\n onStreamStart?: () => void\n onStreamEnd?: (message: UIMessage) => void\n onError?: (error: Error) => void\n\n // Interaction events - client must handle these\n onToolCall?: (args: {\n toolCallId: string\n toolName: string\n input: any\n }) => void\n onApprovalRequest?: (args: {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n }) => void\n\n // 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?: (messageId: string, content: string) => 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\n/**\n * StreamProcessor - State machine for processing AI response streams\n *\n * Manages the full UIMessage[] conversation and emits events on changes.\n *\n * State tracking:\n * - Full message array\n * - Current assistant message being streamed\n * - Text content accumulation\n * - Multiple parallel tool calls\n * - Tool call completion detection\n *\n * Tool call completion is detected when:\n * 1. A new tool call starts at a different index\n * 2. Text content arrives\n * 3. Stream ends\n */\nexport class StreamProcessor {\n private chunkStrategy: ChunkStrategy\n private events: StreamProcessorEvents\n private jsonParser: { parse: (jsonString: string) => any }\n private recordingEnabled: boolean\n\n // Message state\n private messages: Array<UIMessage> = []\n private currentAssistantMessageId: string | null = null\n\n // Stream state for current assistant message\n // Total accumulated text across all segments (for the final result)\n private totalTextContent = ''\n // Current segment's text content (for onTextUpdate callbacks)\n private currentSegmentText = ''\n private lastEmittedText = ''\n private thinkingContent = ''\n private toolCalls: Map<string, InternalToolCallState> = new Map()\n private toolCallOrder: Array<string> = []\n private finishReason: string | null = null\n private isDone = false\n // Track if we've had tool calls since the last text segment started\n private hasToolCallsSinceTextStart = 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 as MessagePart\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 * Start streaming a new assistant message\n * Returns the message ID\n */\n startAssistantMessage(): string {\n // Reset stream state for new message\n this.resetStreamState()\n\n const assistantMessage: UIMessage = {\n id: generateMessageId(),\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n\n this.currentAssistantMessageId = assistantMessage.id\n this.messages = [...this.messages, assistantMessage]\n\n // Emit events\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n\n return assistantMessage.id\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 ? 'input-complete' : undefined,\n error,\n )\n\n // Step 2: Create a tool-result part (for LLM conversation history)\n const content = typeof output === 'string' ? output : JSON.stringify(output)\n const toolResultState: ToolResultState = error ? 'error' : 'complete'\n\n updatedMessages = updateToolResultPart(\n updatedMessages,\n messageWithToolCall.id,\n toolCallId,\n content,\n toolResultState,\n error,\n )\n\n this.messages = updatedMessages\n this.emitMessagesChange()\n }\n\n /**\n * Add an approval response (called by client after handling onApprovalRequest)\n */\n addToolApprovalResponse(approvalId: string, approved: boolean): void {\n this.messages = updateToolCallApprovalResponse(\n this.messages,\n approvalId,\n approved,\n )\n this.emitMessagesChange()\n }\n\n /**\n * Get the conversation as ModelMessages (for sending to LLM)\n */\n toModelMessages(): Array<ModelMessage> {\n const modelMessages: Array<ModelMessage> = []\n for (const msg of this.messages) {\n modelMessages.push(...uiMessageToModelMessages(msg))\n }\n return modelMessages\n }\n\n /**\n * Get current messages\n */\n getMessages(): Array<UIMessage> {\n return this.messages\n }\n\n /**\n * Check if all tool calls in the last assistant message are complete\n * Useful for auto-continue logic\n */\n areAllToolsComplete(): boolean {\n const lastAssistant = this.messages.findLast(\n (m: UIMessage) => m.role === 'assistant',\n )\n\n if (!lastAssistant) return true\n\n const toolParts = lastAssistant.parts.filter(\n (p): p is ToolCallPart => p.type === 'tool-call',\n )\n\n if (toolParts.length === 0) return true\n\n // Get tool result parts to check for server tool completion\n const toolResultIds = new Set(\n lastAssistant.parts\n .filter((p): p is ToolResultPart => p.type === 'tool-result')\n .map((p) => p.toolCallId),\n )\n\n // All tool calls must be in a terminal state\n // A tool call is complete if:\n // 1. It was approved/denied (approval-responded state)\n // 2. It has an output field set (client tool completed via addToolResult)\n // 3. It has a corresponding tool-result part (server tool completed)\n return toolParts.every(\n (part) =>\n part.state === 'approval-responded' ||\n (part.output !== undefined && !part.approval) ||\n toolResultIds.has(part.id),\n )\n }\n\n /**\n * Remove messages after a certain index (for reload/retry)\n */\n removeMessagesAfter(index: number): void {\n 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.currentAssistantMessageId = 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 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 switch (chunk.type) {\n // AG-UI Events\n case 'TEXT_MESSAGE_CONTENT':\n this.handleTextMessageContentEvent(chunk)\n break\n\n case 'TOOL_CALL_START':\n this.handleToolCallStartEvent(chunk)\n break\n\n case 'TOOL_CALL_ARGS':\n this.handleToolCallArgsEvent(chunk)\n break\n\n case 'TOOL_CALL_END':\n this.handleToolCallEndEvent(chunk)\n break\n\n case 'RUN_FINISHED':\n this.handleRunFinishedEvent(chunk)\n break\n\n case 'RUN_ERROR':\n this.handleRunErrorEvent(chunk)\n break\n\n case 'STEP_FINISHED':\n this.handleStepFinishedEvent(chunk)\n break\n\n case 'CUSTOM':\n this.handleCustomEvent(chunk)\n break\n\n default:\n // RUN_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_END, STEP_STARTED,\n // STATE_SNAPSHOT, STATE_DELTA - no special handling needed\n break\n }\n }\n\n /**\n * Handle TEXT_MESSAGE_CONTENT event\n */\n private handleTextMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n ): void {\n // Content arriving means all current tool calls are complete\n this.completeAllToolCalls()\n\n const previousSegment = this.currentSegmentText\n\n // Detect if this is a NEW text segment (after tool calls) vs continuation\n const isNewSegment =\n this.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 !== this.lastEmittedText) {\n this.emitTextUpdate()\n }\n // Reset SEGMENT text accumulation for the new text segment after tool calls\n this.currentSegmentText = ''\n this.lastEmittedText = ''\n this.hasToolCallsSinceTextStart = false\n }\n\n const currentText = this.currentSegmentText\n let nextText = currentText\n\n // Prefer delta over content - delta is the incremental change\n if (chunk.delta !== '') {\n nextText = currentText + chunk.delta\n } else if (chunk.content && 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 this.currentSegmentText = nextText\n this.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 this.currentSegmentText,\n )\n if (shouldEmit && this.currentSegmentText !== this.lastEmittedText) {\n this.emitTextUpdate()\n }\n }\n\n /**\n * Handle TOOL_CALL_START event\n */\n private handleToolCallStartEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n ): void {\n // Mark that we've seen tool calls since the last text segment\n this.hasToolCallsSinceTextStart = true\n\n const toolCallId = chunk.toolCallId\n const existingToolCall = this.toolCalls.get(toolCallId)\n\n if (!existingToolCall) {\n // New tool call starting\n const initialState: ToolCallState = 'awaiting-input'\n\n const newToolCall: InternalToolCallState = {\n id: chunk.toolCallId,\n name: chunk.toolName,\n arguments: '',\n state: initialState,\n parsedArguments: undefined,\n index: chunk.index ?? this.toolCalls.size,\n }\n\n this.toolCalls.set(toolCallId, newToolCall)\n this.toolCallOrder.push(toolCallId)\n\n // Update UIMessage\n if (this.currentAssistantMessageId) {\n this.messages = updateToolCallPart(\n this.messages,\n this.currentAssistantMessageId,\n {\n id: chunk.toolCallId,\n name: chunk.toolName,\n arguments: '',\n state: initialState,\n },\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n this.currentAssistantMessageId,\n chunk.toolCallId,\n initialState,\n '',\n )\n }\n }\n }\n\n /**\n * Handle TOOL_CALL_ARGS event\n */\n private handleToolCallArgsEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n ): void {\n const toolCallId = chunk.toolCallId\n const existingToolCall = this.toolCalls.get(toolCallId)\n\n if (existingToolCall) {\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 if (this.currentAssistantMessageId) {\n this.messages = updateToolCallPart(\n this.messages,\n this.currentAssistantMessageId,\n {\n id: existingToolCall.id,\n name: existingToolCall.name,\n arguments: existingToolCall.arguments,\n state: existingToolCall.state,\n },\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n this.currentAssistantMessageId,\n existingToolCall.id,\n existingToolCall.state,\n existingToolCall.arguments,\n )\n }\n }\n }\n\n /**\n * Handle TOOL_CALL_END event\n */\n private handleToolCallEndEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n ): void {\n const state: ToolResultState = 'complete'\n\n // Update UIMessage if we have a current assistant message\n if (this.currentAssistantMessageId && chunk.result) {\n this.messages = updateToolResultPart(\n this.messages,\n this.currentAssistantMessageId,\n chunk.toolCallId,\n chunk.result,\n state,\n )\n this.emitMessagesChange()\n }\n }\n\n /**\n * Handle RUN_FINISHED event\n */\n private handleRunFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n ): void {\n this.finishReason = chunk.finishReason\n this.isDone = true\n this.completeAllToolCalls()\n }\n\n /**\n * Handle RUN_ERROR event\n */\n private handleRunErrorEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n ): void {\n // Emit error event\n this.events.onError?.(new Error(chunk.error.message || 'An error occurred'))\n }\n\n /**\n * Handle STEP_FINISHED event (for thinking/reasoning content)\n */\n private handleStepFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n ): void {\n const previous = this.thinkingContent\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 this.thinkingContent = nextThinking\n\n // Update UIMessage\n if (this.currentAssistantMessageId) {\n this.messages = updateThinkingPart(\n this.messages,\n this.currentAssistantMessageId,\n this.thinkingContent,\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onThinkingUpdate?.(\n this.currentAssistantMessageId,\n this.thinkingContent,\n )\n }\n }\n\n /**\n * Handle CUSTOM event\n * Handles special custom events like 'tool-input-available' for client-side tool execution\n * and 'approval-requested' for tool approval flows\n */\n private handleCustomEvent(\n chunk: Extract<StreamChunk, { type: 'CUSTOM' }>,\n ): void {\n // Handle client tool input availability - trigger client-side execution\n if (chunk.name === 'tool-input-available' && chunk.data) {\n const { toolCallId, toolName, input } = chunk.data 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 }\n\n // Handle approval requests\n if (chunk.name === 'approval-requested' && chunk.data) {\n const { toolCallId, toolName, input, approval } = chunk.data as {\n toolCallId: string\n toolName: string\n input: any\n approval: { id: string; needsApproval: boolean }\n }\n\n // Update the tool call part with approval state\n if (this.currentAssistantMessageId) {\n this.messages = updateToolCallApproval(\n this.messages,\n this.currentAssistantMessageId,\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 }\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\n */\n private completeAllToolCalls(): void {\n this.toolCalls.forEach((toolCall, id) => {\n if (toolCall.state !== 'input-complete') {\n const index = this.toolCallOrder.indexOf(id)\n this.completeToolCall(index, toolCall)\n }\n })\n }\n\n /**\n * Mark a tool call as complete and emit event\n */\n private completeToolCall(\n _index: number,\n toolCall: InternalToolCallState,\n ): void {\n toolCall.state = 'input-complete'\n\n // Try final parse\n toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments)\n\n // Update UIMessage\n if (this.currentAssistantMessageId) {\n this.messages = updateToolCallPart(\n this.messages,\n this.currentAssistantMessageId,\n {\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: 'input-complete',\n },\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n this.currentAssistantMessageId,\n toolCall.id,\n 'input-complete',\n toolCall.arguments,\n )\n }\n }\n\n /**\n * Emit pending text update\n */\n private emitTextUpdate(): void {\n this.lastEmittedText = this.currentSegmentText\n\n // Update UIMessage\n if (this.currentAssistantMessageId) {\n this.messages = updateTextPart(\n this.messages,\n this.currentAssistantMessageId,\n this.currentSegmentText,\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onTextUpdate?.(\n this.currentAssistantMessageId,\n this.currentSegmentText,\n )\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 finalizeStream(): void {\n // Complete any remaining tool calls\n this.completeAllToolCalls()\n\n // Emit any pending text if not already emitted\n if (this.currentSegmentText !== this.lastEmittedText) {\n this.emitTextUpdate()\n }\n\n // Emit stream end event\n if (this.currentAssistantMessageId) {\n const assistantMessage = this.messages.find(\n (m) => m.id === this.currentAssistantMessageId,\n )\n if (assistantMessage) {\n this.events.onStreamEnd?.(assistantMessage)\n }\n }\n }\n\n /**\n * Get completed tool calls in API format\n */\n private getCompletedToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCalls.values())\n .filter((tc) => tc.state === 'input-complete')\n .map((tc) => ({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.name,\n arguments: tc.arguments,\n },\n }))\n }\n\n /**\n * Get current result\n */\n private getResult(): ProcessorResult {\n const toolCalls = this.getCompletedToolCalls()\n return {\n content: this.totalTextContent,\n thinking: this.thinkingContent || undefined,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n finishReason: this.finishReason,\n }\n }\n\n /**\n * Get current processor state\n */\n getState(): ProcessorState {\n return {\n content: this.totalTextContent,\n thinking: this.thinkingContent,\n toolCalls: new Map(this.toolCalls),\n toolCallOrder: [...this.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.totalTextContent = ''\n this.currentSegmentText = ''\n this.lastEmittedText = ''\n this.thinkingContent = ''\n this.toolCalls.clear()\n this.toolCallOrder = []\n this.finishReason = null\n this.isDone = false\n this.hasToolCallsSinceTextStart = false\n this.chunkStrategy.reset?.()\n }\n\n /**\n * Full reset (including messages)\n */\n reset(): void {\n this.resetStreamState()\n this.messages = []\n this.currentAssistantMessageId = null\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\n async *[Symbol.asyncIterator]() {\n for (const { chunk } of recording.chunks) {\n yield chunk\n }\n },\n }\n}\n"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ export { streamToText, toServerSentEventsStream, toServerSentEventsResponse, toH
12
12
  export { ToolCallManager } from './activities/chat/tools/tool-calls.js';
13
13
  export { maxIterations, untilFinishReason, combineStrategies, } from './activities/chat/agent-loop-strategies.js';
14
14
  export * from './types.js';
15
+ export { detectImageMimeType } from './utils.js';
15
16
  export * from './event-client.js';
16
17
  export { convertMessagesToModelMessages, generateMessageId, uiMessageToModelMessages, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, } from './activities/chat/messages.js';
17
18
  export { StreamProcessor, createReplayStream, ImmediateStrategy, PunctuationStrategy, BatchStrategy, WordBoundaryStrategy, CompositeStrategy, PartialJSONParser, defaultJSONParser, parsePartialJSON, } from './activities/chat/stream/index.js';
package/dist/esm/index.js CHANGED
@@ -9,6 +9,7 @@ import { convertSchemaToJsonSchema } from "./activities/chat/tools/schema-conver
9
9
  import { streamToText, toHttpResponse, toHttpStream, toServerSentEventsResponse, toServerSentEventsStream } from "./stream-to-response.js";
10
10
  import { ToolCallManager } from "./activities/chat/tools/tool-calls.js";
11
11
  import { combineStrategies, maxIterations, untilFinishReason } from "./activities/chat/agent-loop-strategies.js";
12
+ import { detectImageMimeType } from "./utils.js";
12
13
  import { aiEventClient } from "./event-client.js";
13
14
  import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, uiMessageToModelMessages } from "./activities/chat/messages.js";
14
15
  import { StreamProcessor, createReplayStream } from "./activities/chat/stream/processor.js";
@@ -36,6 +37,7 @@ export {
36
37
  createTranscriptionOptions,
37
38
  createVideoOptions,
38
39
  defaultJSONParser,
40
+ detectImageMimeType,
39
41
  generateImage,
40
42
  generateMessageId,
41
43
  generateSpeech,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;"}
@@ -86,23 +86,49 @@ export interface ToolCall {
86
86
  */
87
87
  export type Modality = 'text' | 'image' | 'audio' | 'video' | 'document';
88
88
  /**
89
- * Source specification for multimodal content.
90
- * Supports both inline data (base64) and URL-based content.
89
+ * Source specification for inline data content (base64).
90
+ * Requires a mimeType to ensure providers receive proper content type information.
91
+ */
92
+ export interface ContentPartDataSource {
93
+ /**
94
+ * Indicates this is inline data content.
95
+ */
96
+ type: 'data';
97
+ /**
98
+ * The base64-encoded content value.
99
+ */
100
+ value: string;
101
+ /**
102
+ * The MIME type of the content (e.g., 'image/png', 'audio/wav').
103
+ * Required for data sources to ensure proper handling by providers.
104
+ */
105
+ mimeType: string;
106
+ }
107
+ /**
108
+ * Source specification for URL-based content.
109
+ * mimeType is optional as it can often be inferred from the URL or response headers.
91
110
  */
92
- export interface ContentPartSource {
111
+ export interface ContentPartUrlSource {
93
112
  /**
94
- * The type of source:
95
- * - 'data': Inline data (typically base64 encoded)
96
- * - 'url': URL reference to the content
113
+ * Indicates this is URL-referenced content.
97
114
  */
98
- type: 'data' | 'url';
115
+ type: 'url';
99
116
  /**
100
- * The actual content value:
101
- * - For 'data': base64-encoded string
102
- * - For 'url': HTTP(S) URL or data URI
117
+ * HTTP(S) URL or data URI pointing to the content.
103
118
  */
104
119
  value: string;
120
+ /**
121
+ * Optional MIME type hint for cases where providers can't infer it from the URL.
122
+ */
123
+ mimeType?: string;
105
124
  }
125
+ /**
126
+ * Source specification for multimodal content.
127
+ * Discriminated union supporting both inline data (base64) and URL-based content.
128
+ * - For 'data' sources: mimeType is required
129
+ * - For 'url' sources: mimeType is optional
130
+ */
131
+ export type ContentPartSource = ContentPartDataSource | ContentPartUrlSource;
106
132
  /**
107
133
  * Image content part for multimodal messages.
108
134
  * @template TMetadata - Provider-specific metadata type (e.g., OpenAI's detail level)
@@ -213,7 +239,7 @@ export interface ThinkingPart {
213
239
  type: 'thinking';
214
240
  content: string;
215
241
  }
216
- export type MessagePart = TextPart | ToolCallPart | ToolResultPart | ThinkingPart;
242
+ export type MessagePart = TextPart | ImagePart | AudioPart | VideoPart | DocumentPart | ToolCallPart | ToolResultPart | ThinkingPart;
217
243
  /**
218
244
  * UIMessage - Domain-specific message format optimized for building chat UIs
219
245
  * Contains parts that can be text, tool calls, or tool results
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Detect image mime type from base64 data using magic bytes.
3
+ * Returns undefined if the format cannot be detected.
4
+ *
5
+ * This function analyzes the first few bytes of base64-encoded image data
6
+ * to determine the image format based on file signature (magic bytes).
7
+ *
8
+ * @param base64Data - The base64-encoded image data
9
+ * @returns The detected mime type, or undefined if unrecognized
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const mimeType = detectImageMimeType(imageBase64)
14
+ * // Returns 'image/jpeg', 'image/png', 'image/gif', 'image/webp', or undefined
15
+ * ```
16
+ */
17
+ export declare function detectImageMimeType(base64Data: string): 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | undefined;
@@ -0,0 +1,20 @@
1
+ function detectImageMimeType(base64Data) {
2
+ const prefix = base64Data.substring(0, 20);
3
+ if (prefix.startsWith("/9j/")) {
4
+ return "image/jpeg";
5
+ }
6
+ if (prefix.startsWith("iVBORw0KGgo")) {
7
+ return "image/png";
8
+ }
9
+ if (prefix.startsWith("R0lGOD")) {
10
+ return "image/gif";
11
+ }
12
+ if (prefix.startsWith("UklGR")) {
13
+ return "image/webp";
14
+ }
15
+ return void 0;
16
+ }
17
+ export {
18
+ detectImageMimeType
19
+ };
20
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Detect image mime type from base64 data using magic bytes.\n * Returns undefined if the format cannot be detected.\n *\n * This function analyzes the first few bytes of base64-encoded image data\n * to determine the image format based on file signature (magic bytes).\n *\n * @param base64Data - The base64-encoded image data\n * @returns The detected mime type, or undefined if unrecognized\n *\n * @example\n * ```ts\n * const mimeType = detectImageMimeType(imageBase64)\n * // Returns 'image/jpeg', 'image/png', 'image/gif', 'image/webp', or undefined\n * ```\n */\nexport function detectImageMimeType(\n base64Data: string,\n): 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | undefined {\n // Get first few bytes (base64 encoded)\n const prefix = base64Data.substring(0, 20)\n\n // JPEG: starts with /9j/ (FFD8FF in base64)\n if (prefix.startsWith('/9j/')) {\n return 'image/jpeg'\n }\n // PNG: starts with iVBORw0KGgo (89504E47 in base64)\n if (prefix.startsWith('iVBORw0KGgo')) {\n return 'image/png'\n }\n // GIF: starts with R0lGOD (474946 in base64)\n if (prefix.startsWith('R0lGOD')) {\n return 'image/gif'\n }\n // WebP: starts with UklGR (52494646 in base64, followed by WEBP)\n if (prefix.startsWith('UklGR')) {\n return 'image/webp'\n }\n\n return undefined\n}\n"],"names":[],"mappings":"AAgBO,SAAS,oBACd,YACqE;AAErE,QAAM,SAAS,WAAW,UAAU,GAAG,EAAE;AAGzC,MAAI,OAAO,WAAW,MAAM,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,WAAW,aAAa,GAAG;AACpC,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,WAAW,QAAQ,GAAG;AAC/B,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,WAAW,OAAO,GAAG;AAC9B,WAAO;AAAA,EACT;AAEA,SAAO;AACT;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Core TanStack AI library - Open source AI SDK",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -14,6 +14,7 @@ import {
14
14
  parseWithStandardSchema,
15
15
  } from './tools/schema-converter'
16
16
  import { maxIterations as maxIterationsStrategy } from './agent-loop-strategies'
17
+ import { convertMessagesToModelMessages } from './messages'
17
18
  import type {
18
19
  ApprovalRequest,
19
20
  ClientToolRequest,
@@ -224,6 +225,9 @@ class TextEngine<
224
225
  private earlyTermination = false
225
226
  private toolPhase: ToolPhaseResult = 'continue'
226
227
  private cyclePhase: CyclePhase = 'processText'
228
+ // Client state extracted from initial messages (before conversion to ModelMessage)
229
+ private readonly initialApprovals: Map<string, boolean>
230
+ private readonly initialClientToolResults: Map<string, any>
227
231
 
228
232
  constructor(config: TextEngineConfig<TAdapter, TParams>) {
229
233
  this.adapter = config.adapter
@@ -234,7 +238,21 @@ class TextEngine<
234
238
  config.params.agentLoopStrategy || maxIterationsStrategy(5)
235
239
  this.toolCallManager = new ToolCallManager(this.tools)
236
240
  this.initialMessageCount = config.params.messages.length
237
- this.messages = config.params.messages
241
+
242
+ // Extract client state (approvals, client tool results) from original messages BEFORE conversion
243
+ // This preserves UIMessage parts data that would be lost during conversion to ModelMessage
244
+ const { approvals, clientToolResults } =
245
+ this.extractClientStateFromOriginalMessages(
246
+ config.params.messages as Array<any>,
247
+ )
248
+ this.initialApprovals = approvals
249
+ this.initialClientToolResults = clientToolResults
250
+
251
+ // Convert messages to ModelMessage format (handles both UIMessage and ModelMessage input)
252
+ // This ensures consistent internal format regardless of what the client sends
253
+ this.messages = convertMessagesToModelMessages(
254
+ config.params.messages as Array<any>,
255
+ )
238
256
  this.requestId = this.createId('chat')
239
257
  this.streamId = this.createId('stream')
240
258
  this.effectiveRequest = config.params.abortController
@@ -723,34 +741,66 @@ class TextEngine<
723
741
  })
724
742
  }
725
743
 
726
- private collectClientState(): {
744
+ /**
745
+ * Extract client state (approvals and client tool results) from original messages.
746
+ * This is called in the constructor BEFORE converting to ModelMessage format,
747
+ * because the parts array (which contains approval state) is lost during conversion.
748
+ */
749
+ private extractClientStateFromOriginalMessages(
750
+ originalMessages: Array<any>,
751
+ ): {
727
752
  approvals: Map<string, boolean>
728
753
  clientToolResults: Map<string, any>
729
754
  } {
730
755
  const approvals = new Map<string, boolean>()
731
756
  const clientToolResults = new Map<string, any>()
732
757
 
733
- for (const message of this.messages) {
734
- // todo remove any and fix this
735
- if (message.role === 'assistant' && (message as any).parts) {
736
- const parts = (message as any).parts
737
- for (const part of parts) {
738
- if (
739
- part.type === 'tool-call' &&
740
- part.state === 'approval-responded' &&
741
- part.approval
742
- ) {
743
- approvals.set(part.approval.id, part.approval.approved)
758
+ for (const message of originalMessages) {
759
+ // Check for UIMessage format (parts array) - extract client tool results and approvals
760
+ if (message.role === 'assistant' && message.parts) {
761
+ for (const part of message.parts) {
762
+ if (part.type === 'tool-call') {
763
+ // Extract client tool results (tools without approval that have output)
764
+ if (part.output !== undefined && !part.approval) {
765
+ clientToolResults.set(part.id, part.output)
766
+ }
767
+ // Extract approval responses from UIMessage format parts
768
+ if (
769
+ part.approval?.id &&
770
+ part.approval?.approved !== undefined &&
771
+ part.state === 'approval-responded'
772
+ ) {
773
+ approvals.set(part.approval.id, part.approval.approved)
774
+ }
744
775
  }
776
+ }
777
+ }
778
+ }
745
779
 
746
- if (
747
- part.type === 'tool-call' &&
748
- part.output !== undefined &&
749
- !part.approval
750
- ) {
751
- clientToolResults.set(part.id, part.output)
752
- }
780
+ return { approvals, clientToolResults }
781
+ }
782
+
783
+ private collectClientState(): {
784
+ approvals: Map<string, boolean>
785
+ clientToolResults: Map<string, any>
786
+ } {
787
+ // Start with the initial client state extracted from original messages
788
+ const approvals = new Map(this.initialApprovals)
789
+ const clientToolResults = new Map(this.initialClientToolResults)
790
+
791
+ // Also check current messages for any additional tool results (from server tools)
792
+ for (const message of this.messages) {
793
+ // Check for ModelMessage format (role: 'tool' messages contain tool results)
794
+ // This handles results sent back from the client after executing client-side tools
795
+ if (message.role === 'tool' && message.toolCallId) {
796
+ // Parse content back to original output (was stringified by uiMessageToModelMessages)
797
+ let output: unknown
798
+ try {
799
+ output = JSON.parse(message.content as string)
800
+ } catch {
801
+ output = message.content
753
802
  }
803
+ clientToolResults.set(message.toolCallId, output)
754
804
  }
755
805
  }
756
806
 
@@ -879,11 +929,31 @@ class TextEngine<
879
929
  }
880
930
 
881
931
  private getPendingToolCallsFromMessages(): Array<ToolCall> {
882
- const completedToolIds = new Set(
883
- this.messages
884
- .filter((message) => message.role === 'tool' && message.toolCallId)
885
- .map((message) => message.toolCallId!), // toolCallId exists due to filter
886
- )
932
+ // Build a set of completed tool IDs, but exclude tools with pendingExecution marker
933
+ // (these are approved tools that still need to execute)
934
+ const completedToolIds = new Set<string>()
935
+
936
+ for (const message of this.messages) {
937
+ if (message.role === 'tool' && message.toolCallId) {
938
+ // Check if this is an approval response with pendingExecution marker
939
+ let hasPendingExecution = false
940
+ if (typeof message.content === 'string') {
941
+ try {
942
+ const parsed = JSON.parse(message.content)
943
+ if (parsed.pendingExecution === true) {
944
+ hasPendingExecution = true
945
+ }
946
+ } catch {
947
+ // Not JSON, treat as regular tool result
948
+ }
949
+ }
950
+
951
+ // Only mark as complete if NOT pending execution
952
+ if (!hasPendingExecution) {
953
+ completedToolIds.add(message.toolCallId)
954
+ }
955
+ }
956
+ }
887
957
 
888
958
  const pending: Array<ToolCall> = []
889
959