@tanstack/ai 0.20.1 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/index.d.ts +1 -1
- package/dist/esm/activities/chat/index.js +357 -258
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +10 -1
- package/dist/esm/activities/chat/middleware/compose.js +55 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/index.d.ts +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +35 -3
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +12 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +13 -4
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/extend-adapter.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -1
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/otel.js +1 -4
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/utilities/ag-ui-wire.js +4 -1
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-core/middleware/SKILL.md +124 -18
- package/skills/ai-core/structured-outputs/SKILL.md +13 -0
- package/src/activities/chat/index.ts +653 -370
- package/src/activities/chat/messages.ts +1 -1
- package/src/activities/chat/middleware/compose.ts +65 -5
- package/src/activities/chat/middleware/index.ts +1 -0
- package/src/activities/chat/middleware/types.ts +53 -2
- package/src/activities/chat/stream/processor.ts +2 -2
- package/src/activities/chat/tools/schema-converter.ts +22 -7
- package/src/activities/generateImage/index.ts +3 -3
- package/src/extend-adapter.ts +1 -1
- package/src/index.ts +5 -1
- package/src/middlewares/content-guard.ts +1 -1
- package/src/middlewares/otel.ts +7 -10
- package/src/strip-to-spec-middleware.ts +1 -4
- package/src/utilities/ag-ui-wire.ts +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processor.js","sources":["../../../../../src/activities/chat/stream/processor.ts"],"sourcesContent":["/**\n * Unified Stream Processor\n *\n * Core stream processing engine that manages the full UIMessage[] conversation.\n * Single source of truth for message state.\n *\n * Handles:\n * - Full conversation management (UIMessage[])\n * - Text content accumulation with configurable chunking strategies\n * - Parallel tool calls with lifecycle state tracking\n * - Tool results and approval flows\n * - Thinking/reasoning content\n * - Recording/replay for testing\n * - Event-driven architecture for UI updates\n * - Per-message stream state tracking for multi-message sessions\n *\n * @see docs/chat-architecture.md — Canonical reference for AG-UI chunk ordering,\n * adapter contract, single-shot flows, and expected UIMessage output.\n */\nimport { generateMessageId, uiMessageToModelMessages } from '../messages.js'\nimport { defaultJSONParser } from './json-parser'\nimport {\n appendStructuredOutputDelta,\n completeStructuredOutputPart,\n errorStructuredOutputPart,\n updateTextPart,\n updateThinkingPart,\n updateToolCallApproval,\n updateToolCallApprovalResponse,\n updateToolCallPart,\n updateToolCallWithOutput,\n updateToolResultPart,\n} from './message-updaters'\nimport { ImmediateStrategy } from './strategies'\nimport type {\n ChunkRecording,\n ChunkStrategy,\n InternalToolCallState,\n MessageStreamState,\n ProcessorResult,\n ProcessorState,\n ToolCallState,\n ToolResultState,\n} from './types'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StreamChunk,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n} from '../../../types'\n\n/**\n * Events emitted by the StreamProcessor\n */\nexport interface StreamProcessorEvents {\n // State events - full array on any change\n onMessagesChange?: (messages: Array<UIMessage>) => void\n\n // Lifecycle events\n onStreamStart?: () => void\n onStreamEnd?: (message: UIMessage) => void\n onError?: (error: Error) => void\n\n // Interaction events - client must handle these\n onToolCall?: (args: {\n toolCallId: string\n toolName: string\n input: any\n }) => void\n onApprovalRequest?: (args: {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n }) => void\n\n // Custom events from server-side tools\n onCustomEvent?: (\n eventType: string,\n data: unknown,\n context: { toolCallId?: string },\n ) => void\n\n // Granular events for UI optimization (character-by-character, state tracking)\n onTextUpdate?: (messageId: string, content: string) => void\n onToolCallStateChange?: (\n messageId: string,\n toolCallId: string,\n state: ToolCallState,\n args: string,\n ) => void\n onThinkingUpdate?: (\n messageId: string,\n stepId: string,\n content: string,\n ) => void\n}\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 * 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\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 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 * 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 ? '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 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.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.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 as StreamChunk & { type: string }\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType enum members vs string-literal case labels; default branch handles untraced events.\n switch (c.type) {\n // AG-UI Events\n case 'TEXT_MESSAGE_START':\n this.handleTextMessageStartEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,\n )\n break\n\n case 'TEXT_MESSAGE_CONTENT':\n this.handleTextMessageContentEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n )\n break\n\n case 'TEXT_MESSAGE_END':\n this.handleTextMessageEndEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_END' }>,\n )\n break\n\n case 'TOOL_CALL_START':\n this.handleToolCallStartEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n )\n break\n\n case 'TOOL_CALL_ARGS':\n this.handleToolCallArgsEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n )\n break\n\n case 'TOOL_CALL_END':\n this.handleToolCallEndEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n )\n break\n\n case 'RUN_FINISHED':\n this.handleRunFinishedEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n )\n break\n\n case 'RUN_ERROR':\n this.handleRunErrorEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n )\n break\n\n case 'STEP_FINISHED':\n this.handleStepFinishedEvent(\n chunk as Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n )\n break\n\n case 'MESSAGES_SNAPSHOT':\n this.handleMessagesSnapshotEvent(\n chunk as Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,\n )\n break\n\n case 'CUSTOM':\n this.handleCustomEvent(\n chunk as Extract<StreamChunk, { type: 'CUSTOM' }>,\n )\n break\n\n case 'RUN_STARTED':\n this.handleRunStartedEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_STARTED' }>,\n )\n break\n\n case 'REASONING_START':\n case 'REASONING_MESSAGE_START':\n case 'REASONING_MESSAGE_END':\n case 'REASONING_END':\n // No special handling needed\n break\n\n case 'REASONING_MESSAGE_CONTENT':\n this.handleReasoningMessageContentEvent(\n chunk as Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,\n )\n break\n\n case 'TOOL_CALL_RESULT':\n this.handleToolCallResultEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_RESULT' }>,\n )\n break\n\n case 'STEP_STARTED':\n this.handleStepStartedEvent(\n chunk as Extract<StreamChunk, { type: 'STEP_STARTED' }>,\n )\n break\n\n default:\n // STATE_SNAPSHOT, STATE_DELTA - no special handling needed\n break\n }\n }\n\n // ============================================\n // Per-Message State Helpers\n // ============================================\n\n /**\n * Create a new MessageStreamState for a message\n */\n private createMessageState(\n messageId: string,\n role: 'user' | 'assistant' | 'system',\n ): MessageStreamState {\n const state: MessageStreamState = {\n id: messageId,\n role,\n totalTextContent: '',\n currentSegmentText: '',\n lastEmittedText: '',\n hasSeenReasoningEvents: false,\n thinkingSteps: new Map(),\n thinkingStepSignatures: new Map(),\n thinkingStepOrder: [],\n currentThinkingStepId: null,\n toolCalls: new Map(),\n toolCallOrder: [],\n hasToolCallsSinceTextStart: false,\n isComplete: false,\n }\n this.messageStates.set(messageId, state)\n return state\n }\n\n /**\n * Get the MessageStreamState for a message\n */\n private getMessageState(messageId: string): MessageStreamState | undefined {\n return this.messageStates.get(messageId)\n }\n\n /**\n * Promote a pending stepId from a STEP_STARTED that fired before the\n * assistant message existed onto the given message state, so the next\n * thinking event (STEP_FINISHED or REASONING_MESSAGE_CONTENT) attributes\n * to the correct step.\n */\n private consumePendingThinkingStep(state: MessageStreamState): void {\n if (!this.pendingThinkingStepId) return\n const stepId = this.pendingThinkingStepId\n state.currentThinkingStepId = stepId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n }\n this.pendingThinkingStepId = null\n }\n\n /**\n * Get the most recent active assistant message ID.\n * Used as fallback for events that don't include a messageId.\n */\n private getActiveAssistantMessageId(): string | null {\n // Set iteration is insertion-order; reverse-iterate to search from the end\n const ids = Array.from(this.activeMessageIds).reverse()\n for (const id of ids) {\n const state = this.messageStates.get(id)\n if (state && state.role === 'assistant') {\n return id\n }\n }\n return null\n }\n\n /**\n * Ensure an active assistant message exists, creating one if needed.\n * Used for backward compat when events arrive without prior TEXT_MESSAGE_START.\n *\n * On reconnect/resume, a TEXT_MESSAGE_CONTENT may arrive for a message that\n * already exists in this.messages (e.g. from initialMessages or a prior\n * MESSAGES_SNAPSHOT) but whose transient state was cleared. In that case we\n * hydrate state from the existing message rather than creating a duplicate.\n */\n private ensureAssistantMessage(preferredId?: string): {\n messageId: string\n state: MessageStreamState\n } {\n // Try to find state by preferred ID\n if (preferredId) {\n const state = this.getMessageState(preferredId)\n if (state) return { messageId: preferredId, state }\n }\n\n // Try active assistant message\n const activeId = this.getActiveAssistantMessageId()\n if (activeId) {\n const state = this.getMessageState(activeId)\n if (state) return { messageId: activeId, state }\n }\n\n // Check if a message with preferredId already exists (reconnect/resume case).\n // Hydrate transient state from the existing message instead of duplicating it.\n if (preferredId) {\n const existingMsg = this.messages.find((m) => m.id === preferredId)\n if (existingMsg) {\n const state = this.createMessageState(preferredId, existingMsg.role)\n this.activeMessageIds.add(preferredId)\n\n // Seed segment text from the existing last text part so that\n // incoming deltas append correctly and updateTextPart produces\n // the right content (existing text + new delta).\n const lastPart =\n existingMsg.parts.length > 0\n ? existingMsg.parts[existingMsg.parts.length - 1]\n : null\n if (lastPart && lastPart.type === 'text') {\n state.currentSegmentText = lastPart.content\n state.lastEmittedText = lastPart.content\n state.totalTextContent = lastPart.content\n }\n\n return { messageId: preferredId, state }\n }\n }\n\n // Auto-create an assistant message (backward compat for process() without TEXT_MESSAGE_START)\n const id = preferredId || generateMessageId()\n const assistantMessage: UIMessage = {\n id,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n this.messages = [...this.messages, assistantMessage]\n const state = this.createMessageState(id, 'assistant')\n this.activeMessageIds.add(id)\n this.pendingManualMessageId = id\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n return { messageId: id, state }\n }\n\n // ============================================\n // Event Handlers\n // ============================================\n\n /**\n * Handle TEXT_MESSAGE_START event\n */\n private handleTextMessageStartEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,\n ): void {\n const { messageId, role } = chunk\n\n // Map 'tool' and 'developer' roles to 'assistant' for both UIMessage and MessageStreamState\n // (UIMessage doesn't support 'tool'/'developer' role, and lookups like\n // getActiveAssistantMessageId() check state.role === 'assistant')\n const uiRole: 'system' | 'user' | 'assistant' =\n role === 'user' || role === 'system' ? role : 'assistant'\n\n // Case 1: A manual message was created via startAssistantMessage()\n if (this.pendingManualMessageId) {\n const pendingId = this.pendingManualMessageId\n this.pendingManualMessageId = null\n\n if (pendingId !== messageId) {\n // Update the message's ID in the messages array\n this.messages = this.messages.map((msg) =>\n msg.id === pendingId ? { ...msg, id: messageId } : msg,\n )\n\n // Move state to the new key\n const existingState = this.messageStates.get(pendingId)\n if (existingState) {\n existingState.id = messageId\n this.messageStates.delete(pendingId)\n this.messageStates.set(messageId, existingState)\n }\n\n // Update activeMessageIds\n this.activeMessageIds.delete(pendingId)\n this.activeMessageIds.add(messageId)\n }\n\n // Ensure state exists\n if (!this.messageStates.has(messageId)) {\n this.createMessageState(messageId, uiRole)\n this.activeMessageIds.add(messageId)\n }\n\n this.emitMessagesChange()\n return\n }\n\n // Case 2: Message already exists (dedup)\n const existingMsg = this.messages.find((m) => m.id === messageId)\n if (existingMsg) {\n this.activeMessageIds.add(messageId)\n const existingState = this.messageStates.get(messageId)\n if (!existingState) {\n this.createMessageState(messageId, uiRole)\n } else {\n // If tool calls happened since last text, this TEXT_MESSAGE_START\n // signals a new text segment — reset segment accumulation\n if (existingState.hasToolCallsSinceTextStart) {\n if (\n existingState.currentSegmentText !== existingState.lastEmittedText\n ) {\n this.emitTextUpdateForMessage(messageId)\n }\n existingState.currentSegmentText = ''\n existingState.lastEmittedText = ''\n existingState.hasToolCallsSinceTextStart = false\n }\n }\n return\n }\n\n // Case 3: New message from the stream\n const newMessage: UIMessage = {\n id: messageId,\n role: uiRole,\n parts: [],\n createdAt: new Date(),\n }\n\n this.messages = [...this.messages, newMessage]\n this.createMessageState(messageId, uiRole)\n this.activeMessageIds.add(messageId)\n\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n }\n\n /**\n * Handle TEXT_MESSAGE_END event\n */\n private handleTextMessageEndEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_END' }>,\n ): void {\n const { messageId } = chunk\n const state = this.getMessageState(messageId)\n if (!state) return\n if (state.isComplete) return\n\n // Emit any pending text for this message\n if (state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n\n // Complete all tool calls for this message\n this.completeAllToolCallsForMessage(messageId)\n }\n\n /**\n * Handle MESSAGES_SNAPSHOT event\n */\n private handleMessagesSnapshotEvent(\n chunk: Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,\n ): void {\n this.resetStreamState()\n // AG-UI Message[] is compatible with UIMessage[] at runtime\n this.messages = [...chunk.messages] as Array<UIMessage>\n this.emitMessagesChange()\n }\n\n /**\n * Handle TEXT_MESSAGE_CONTENT event.\n *\n * Accumulates delta into both currentSegmentText (for UI emission) and\n * totalTextContent (for ProcessorResult). Lazily creates the assistant\n * UIMessage on first content. Uses updateTextPart() which replaces the\n * last TextPart or creates a new one depending on part ordering.\n *\n * @see docs/chat-architecture.md#single-shot-text-response — Text accumulation step-by-step\n * @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic\n */\n private handleTextMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(chunk.messageId)\n\n // Content arriving means all current tool calls for this message are complete\n this.completeAllToolCallsForMessage(messageId)\n\n if (this.structuredMessageIds.has(messageId)) {\n // `chunk.delta` is incremental; `chunk.content` is sometimes cumulative\n // (mirrors what the plain-text branch handles below). Reconcile against\n // the existing raw buffer so adapters that emit cumulative content\n // don't duplicate the JSON.\n let delta = chunk.delta || ''\n if (delta === '' && chunk.content !== undefined && chunk.content !== '') {\n const existingRaw = (\n this.messages\n .find((m) => m.id === messageId)\n ?.parts.find(\n (p): p is Extract<MessagePart, { type: 'structured-output' }> =>\n p.type === 'structured-output',\n ) ?? { raw: '' }\n ).raw\n if (chunk.content.startsWith(existingRaw)) {\n delta = chunk.content.slice(existingRaw.length)\n } else if (existingRaw.startsWith(chunk.content)) {\n delta = ''\n } else {\n delta = chunk.content\n }\n }\n if (delta !== '') {\n this.messages = appendStructuredOutputDelta(\n this.messages,\n messageId,\n delta,\n )\n state.totalTextContent += delta\n this.emitMessagesChange()\n }\n return\n }\n\n const previousSegment = state.currentSegmentText\n\n // Detect if this is a NEW text segment (after tool calls) vs continuation\n const isNewSegment =\n state.hasToolCallsSinceTextStart &&\n previousSegment.length > 0 &&\n this.isNewTextSegment(chunk, previousSegment)\n\n if (isNewSegment) {\n // Emit any accumulated text before starting new segment\n if (previousSegment !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n // Reset SEGMENT text accumulation for the new text segment after tool calls\n state.currentSegmentText = ''\n state.lastEmittedText = ''\n state.hasToolCallsSinceTextStart = false\n }\n\n const currentText = state.currentSegmentText\n let nextText = currentText\n\n // Prefer delta over content - delta is the incremental change\n // Normalize to empty string to avoid \"undefined\" string concatenation\n const delta = chunk.delta || ''\n if (delta !== '') {\n nextText = currentText + delta\n } else if (chunk.content !== undefined && chunk.content !== '') {\n // Fallback: use content if delta is not provided\n if (chunk.content.startsWith(currentText)) {\n nextText = chunk.content\n } else if (currentText.startsWith(chunk.content)) {\n nextText = currentText\n } else {\n nextText = currentText + chunk.content\n }\n }\n\n // Calculate the delta for totalTextContent\n const textDelta = nextText.slice(currentText.length)\n state.currentSegmentText = nextText\n state.totalTextContent += textDelta\n\n // Use delta for chunk strategy if available\n const chunkPortion = chunk.delta || chunk.content || ''\n const shouldEmit = this.chunkStrategy.shouldEmit(\n chunkPortion,\n state.currentSegmentText,\n )\n if (shouldEmit && state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n }\n\n /**\n * Handle TOOL_CALL_START event.\n *\n * Creates a new InternalToolCallState entry in the toolCalls Map and appends\n * a ToolCallPart to the UIMessage. Duplicate toolCallId is a no-op.\n *\n * CRITICAL: This MUST be received before any TOOL_CALL_ARGS for the same\n * toolCallId. Args for unknown IDs are silently dropped.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Tool call state transitions\n * @see docs/chat-architecture.md#parallel-tool-calls-single-shot — Parallel tracking by ID\n * @see docs/chat-architecture.md#adapter-contract — Ordering requirements\n */\n private handleToolCallStartEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n ): void {\n // Determine the message this tool call belongs to\n const targetMessageId =\n chunk.parentMessageId ?? this.getActiveAssistantMessageId()\n const { messageId, state } = this.ensureAssistantMessage(\n targetMessageId ?? undefined,\n )\n\n // Mark that we've seen tool calls since the last text segment\n state.hasToolCallsSinceTextStart = true\n\n const toolCallId = chunk.toolCallId\n const existingToolCall = state.toolCalls.get(toolCallId)\n\n if (!existingToolCall) {\n // New tool call starting\n const initialState: ToolCallState = 'awaiting-input'\n\n // `toolName` is a deprecated alias for `toolCallName` (see ToolCallStartEvent\n // in types.ts). Accept either so chunks from older code paths or any\n // adapter that only sets the deprecated field still produce a named part.\n // The type marks both as required strings, but in practice some emitters\n // only set one — fall back via the runtime value rather than the type.\n const toolName =\n (chunk as { toolCallName?: string }).toolCallName ?? chunk.toolName\n\n // Capture provider metadata that arrived on TOOL_CALL_START so it\n // round-trips back through the assistant message on the next turn\n // (e.g. Gemini's thoughtSignature).\n const chunkMetadata = chunk.metadata\n\n const newToolCall: InternalToolCallState = {\n id: chunk.toolCallId,\n name: toolName,\n arguments: '',\n state: initialState,\n parsedArguments: undefined,\n index: chunk.index ?? state.toolCalls.size,\n ...(chunkMetadata !== undefined && { metadata: chunkMetadata }),\n }\n\n state.toolCalls.set(toolCallId, newToolCall)\n state.toolCallOrder.push(toolCallId)\n\n // Store mapping for TOOL_CALL_ARGS/END routing\n this.toolCallToMessage.set(toolCallId, messageId)\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: chunk.toolCallId,\n name: toolName,\n arguments: '',\n state: initialState,\n ...(chunkMetadata !== undefined && { metadata: chunkMetadata }),\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n chunk.toolCallId,\n initialState,\n '',\n )\n }\n }\n\n /**\n * Handle TOOL_CALL_ARGS event.\n *\n * Appends the delta to the tool call's accumulated arguments string.\n * Transitions state from awaiting-input → input-streaming on first non-empty delta.\n * Attempts partial JSON parse on each update for UI preview.\n *\n * If toolCallId is not found in the Map (no preceding TOOL_CALL_START),\n * this event is silently dropped.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Step-by-step tool call processing\n */\n private handleToolCallArgsEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n ): void {\n const toolCallId = chunk.toolCallId\n const messageId = this.toolCallToMessage.get(toolCallId)\n if (!messageId) return\n\n const state = this.getMessageState(messageId)\n if (!state) return\n\n const existingToolCall = state.toolCalls.get(toolCallId)\n if (!existingToolCall) return\n\n const wasAwaitingInput = existingToolCall.state === 'awaiting-input'\n\n // Accumulate arguments from delta\n existingToolCall.arguments += chunk.delta || ''\n\n // Update state\n if (wasAwaitingInput && chunk.delta) {\n existingToolCall.state = 'input-streaming'\n }\n\n // Try to parse the updated arguments\n existingToolCall.parsedArguments = this.jsonParser.parse(\n existingToolCall.arguments,\n )\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: existingToolCall.id,\n name: existingToolCall.name,\n arguments: existingToolCall.arguments,\n state: existingToolCall.state,\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n existingToolCall.id,\n existingToolCall.state,\n existingToolCall.arguments,\n )\n }\n\n /**\n * Handle TOOL_CALL_END event — authoritative signal that a tool call's input is finalized.\n *\n * This event has a DUAL ROLE:\n * - Without `result`: Signals arguments are done (from adapter). Transitions to input-complete.\n * - With `result`: Signals tool was executed and result is available (from TextEngine).\n * Creates both output on the tool-call part AND a tool-result part.\n *\n * If `input` is provided, it overrides the accumulated string parse as the\n * canonical parsed arguments.\n *\n * @see docs/chat-architecture.md#tool-results-and-the-tool_call_end-dual-role — Full explanation\n * @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow\n */\n private handleToolCallEndEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n ): void {\n const messageId = this.toolCallToMessage.get(chunk.toolCallId)\n if (!messageId) return\n\n const msgState = this.getMessageState(messageId)\n if (!msgState) return\n\n // Transition the tool call to input-complete (the authoritative completion signal)\n const existingToolCall = msgState.toolCalls.get(chunk.toolCallId)\n if (existingToolCall && existingToolCall.state !== 'input-complete') {\n // If TOOL_CALL_END provides parsed input and no TOOL_CALL_ARGS were\n // received, back-fill the arguments string so the UIMessage ToolCallPart\n // carries the correct value (defensive against adapters that skip ARGS).\n if (chunk.input !== undefined && !existingToolCall.arguments) {\n existingToolCall.arguments = JSON.stringify(chunk.input)\n }\n\n const index = msgState.toolCallOrder.indexOf(chunk.toolCallId)\n this.completeToolCall(messageId, index, existingToolCall)\n // If TOOL_CALL_END provides parsed input, use it as the canonical parsed\n // arguments (overrides the accumulated string parse from completeToolCall)\n if (chunk.input !== undefined) {\n existingToolCall.parsedArguments = chunk.input\n }\n }\n\n // Update UIMessage if there's a result\n if (chunk.result) {\n // Step 1: Update the tool-call part's output field (for UI consistency\n // with client tools — see GitHub issue #176)\n let output: unknown\n try {\n output = JSON.parse(chunk.result)\n } catch {\n output = chunk.result\n }\n this.messages = updateToolCallWithOutput(\n this.messages,\n chunk.toolCallId,\n output,\n )\n\n // Step 2: Create/update the tool-result part (for LLM conversation history)\n const resultState: ToolResultState = 'complete'\n this.messages = updateToolResultPart(\n this.messages,\n messageId,\n chunk.toolCallId,\n chunk.result,\n resultState,\n )\n this.emitMessagesChange()\n }\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 )\n\n // Step 2: Create/update the tool-result part\n const resultState: ToolResultState = 'complete'\n this.messages = updateToolResultPart(\n this.messages,\n messageId,\n chunk.toolCallId,\n chunk.content,\n resultState,\n )\n this.emitMessagesChange()\n }\n\n /**\n * Handle RUN_STARTED event.\n *\n * Registers the run so that RUN_FINISHED can determine whether other\n * runs are still active before finalizing.\n */\n private handleRunStartedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_STARTED' }>,\n ): void {\n this.activeRuns.add(chunk.runId)\n }\n\n /**\n * Handle RUN_FINISHED event.\n *\n * Records the finishReason and removes the run from activeRuns.\n * Only finalizes when no more runs are active, so that concurrent\n * runs don't interfere with each other.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — finishReason semantics\n * @see docs/chat-architecture.md#adapter-contract — Why RUN_FINISHED is mandatory\n */\n private handleRunFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n ): void {\n this.finishReason = chunk.finishReason ?? null\n this.activeRuns.delete(chunk.runId)\n\n if (this.activeRuns.size === 0) {\n this.isDone = true\n this.completeAllToolCalls()\n this.finalizeStream()\n }\n }\n\n /**\n * Handle RUN_ERROR event\n */\n private handleRunErrorEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n ): void {\n this.hasError = true\n const runId = (chunk as any).runId as string | 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.messages = errorStructuredOutputPart(\n this.messages,\n messageId,\n errorMessage,\n )\n this.structuredMessageIds.delete(messageId)\n this.emitMessagesChange()\n }\n\n this.events.onError?.(new Error(errorMessage))\n }\n\n /**\n * Handle STEP_STARTED event (for thinking/reasoning content).\n *\n * Records the stepId so that subsequent STEP_FINISHED deltas accumulate\n * into their own ThinkingPart. Does not create a message — the message\n * is lazily created when the first STEP_FINISHED content arrives.\n */\n private handleStepStartedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_STARTED' }>,\n ): void {\n const stepId = chunk.stepId ?? generateMessageId()\n const activeId = this.getActiveAssistantMessageId()\n if (activeId) {\n const state = this.getMessageState(activeId)\n if (state) {\n state.currentThinkingStepId = stepId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n }\n // Clear any pending stepId from a prior STEP_STARTED that fired\n // before the assistant message existed. Now that we're tracking\n // the step directly on message state, the pending value is stale\n // and must not leak into the next STEP_FINISHED (which would\n // misattribute its delta to the stale step).\n this.pendingThinkingStepId = null\n return\n }\n }\n\n // No active message yet — defer until ensureAssistantMessage in STEP_FINISHED\n this.pendingThinkingStepId = stepId\n }\n\n /**\n * Handle STEP_FINISHED event (for thinking/reasoning content).\n *\n * Accumulates delta into the current thinking step's content and updates\n * the corresponding ThinkingPart in the UIMessage.\n *\n * @see docs/chat-architecture.md#thinkingreasoning-content — Thinking flow\n */\n private handleStepFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(\n this.getActiveAssistantMessageId() ?? undefined,\n )\n\n // During the transition period, adapters emit BOTH STEP_FINISHED and\n // REASONING_MESSAGE_CONTENT with the same delta. If we've already processed\n // REASONING_MESSAGE_CONTENT events for this message, skip the duplicate\n // thinking content from STEP_FINISHED to avoid doubled content.\n if (state.hasSeenReasoningEvents) {\n if (chunk.signature) {\n const stepId = state.currentThinkingStepId ?? chunk.stepId\n if (!stepId) return\n const thinking = state.thinkingSteps.get(stepId)\n if (thinking !== undefined) {\n state.thinkingStepSignatures.set(stepId, chunk.signature)\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n thinking,\n chunk.signature,\n )\n this.emitMessagesChange()\n }\n }\n return\n }\n\n this.consumePendingThinkingStep(state)\n\n const stepId =\n state.currentThinkingStepId ?? chunk.stepId ?? generateMessageId()\n\n // Auto-initialize if no prior STEP_STARTED (backward compat)\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n state.currentThinkingStepId = stepId\n }\n\n const previous = state.thinkingSteps.get(stepId) ?? ''\n let nextThinking = previous\n\n // Prefer delta over content\n if (chunk.delta && chunk.delta !== '') {\n nextThinking = previous + chunk.delta\n } else if (chunk.content && chunk.content !== '') {\n if (chunk.content.startsWith(previous)) {\n nextThinking = chunk.content\n } else if (previous.startsWith(chunk.content)) {\n nextThinking = previous\n } else {\n nextThinking = previous + chunk.content\n }\n }\n\n state.thinkingSteps.set(stepId, nextThinking)\n\n if (chunk.signature) {\n state.thinkingStepSignatures.set(stepId, chunk.signature)\n }\n\n // Update UIMessage\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n nextThinking,\n state.thinkingStepSignatures.get(stepId),\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)\n }\n\n /**\n * Handle REASONING_MESSAGE_CONTENT event (AG-UI reasoning protocol).\n *\n * Accumulates reasoning delta into thinking content and updates the\n * corresponding ThinkingPart in the UIMessage.\n */\n private handleReasoningMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(\n this.getActiveAssistantMessageId() ?? undefined,\n )\n\n state.hasSeenReasoningEvents = true\n const delta = chunk.delta || ''\n\n this.consumePendingThinkingStep(state)\n\n const stepId = state.currentThinkingStepId ?? chunk.messageId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n state.currentThinkingStepId = stepId\n }\n\n const nextThinking = (state.thinkingSteps.get(stepId) ?? '') + delta\n state.thinkingSteps.set(stepId, nextThinking)\n\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n nextThinking,\n state.thinkingStepSignatures.get(stepId),\n )\n this.emitMessagesChange()\n\n this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)\n }\n\n /**\n * Handle CUSTOM event.\n *\n * Handles special custom events emitted by the TextEngine (not adapters):\n * - 'tool-input-available': Client tool needs execution. Fires onToolCall.\n * - 'approval-requested': Tool needs user approval. Updates tool-call part\n * state and fires onApprovalRequest.\n *\n * @see docs/chat-architecture.md#client-tools-and-approval-flows — Full flow details\n */\n private handleCustomEvent(\n chunk: Extract<StreamChunk, { type: 'CUSTOM' }>,\n ): void {\n const messageId = this.getActiveAssistantMessageId()\n\n if (chunk.name === 'structured-output.start' && chunk.value) {\n const v = chunk.value as { messageId?: string }\n const targetId = v.messageId ?? messageId\n if (targetId) {\n this.ensureAssistantMessage(targetId)\n this.structuredMessageIds.add(targetId)\n }\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.messages = completeStructuredOutputPart(\n this.messages,\n targetId,\n v.object,\n v.raw ?? '',\n v.reasoning,\n )\n this.structuredMessageIds.delete(targetId)\n this.emitMessagesChange()\n }\n // Fall through so user `onCustomEvent` callbacks still observe the event.\n }\n\n // Handle client tool input availability - trigger client-side execution\n if (chunk.name === 'tool-input-available' && chunk.value) {\n const { toolCallId, toolName, input } = chunk.value as {\n toolCallId: string\n toolName: string\n input: any\n }\n\n // Emit onToolCall event for the client to execute the tool\n this.events.onToolCall?.({\n toolCallId,\n toolName,\n input,\n })\n return\n }\n\n // Handle approval requests\n if (chunk.name === 'approval-requested' && chunk.value) {\n const { toolCallId, toolName, input, approval } = chunk.value as {\n toolCallId: string\n toolName: string\n input: any\n approval: { id: string; needsApproval: boolean }\n }\n\n // Resolve the message containing this tool call. After RUN_FINISHED,\n // activeMessageIds is cleared, so fall back to the toolCallToMessage map\n // which is populated during TOOL_CALL_START and preserved across finalize.\n const resolvedMessageId =\n messageId ?? this.toolCallToMessage.get(toolCallId)\n if (resolvedMessageId) {\n this.messages = updateToolCallApproval(\n this.messages,\n resolvedMessageId,\n toolCallId,\n approval.id,\n )\n this.emitMessagesChange()\n }\n\n // Emit approval request event\n this.events.onApprovalRequest?.({\n toolCallId,\n toolName,\n input,\n approvalId: approval.id,\n })\n return\n }\n\n // Forward non-system custom events to onCustomEvent callback\n if (this.events.onCustomEvent) {\n const toolCallId =\n chunk.value && typeof chunk.value === 'object'\n ? chunk.value.toolCallId\n : undefined\n this.events.onCustomEvent(chunk.name, chunk.value, { toolCallId })\n }\n }\n\n // ============================================\n // Internal Helpers\n // ============================================\n\n /**\n * Detect if an incoming content chunk represents a NEW text segment\n */\n private isNewTextSegment(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n previous: string,\n ): boolean {\n // Check if content is present (delta is always defined but may be empty string)\n if (chunk.content !== undefined) {\n if (chunk.content.length < previous.length) {\n return true\n }\n if (\n !chunk.content.startsWith(previous) &&\n !previous.startsWith(chunk.content)\n ) {\n return true\n }\n }\n return false\n }\n\n /**\n * Complete all tool calls across all active messages — safety net for stream termination.\n *\n * Called by RUN_FINISHED and finalizeStream(). Force-transitions any tool call\n * not yet in input-complete state. Handles cases where TOOL_CALL_END was\n * missed (adapter bug, network error, aborted stream).\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Safety net behavior\n */\n private completeAllToolCalls(): void {\n for (const messageId of this.activeMessageIds) {\n this.completeAllToolCallsForMessage(messageId)\n }\n }\n\n /**\n * Complete all tool calls for a specific message\n */\n private completeAllToolCallsForMessage(messageId: string): void {\n const state = this.getMessageState(messageId)\n if (!state) return\n\n state.toolCalls.forEach((toolCall, id) => {\n if (toolCall.state !== 'input-complete') {\n const index = state.toolCallOrder.indexOf(id)\n this.completeToolCall(messageId, index, toolCall)\n }\n })\n }\n\n /**\n * Mark a tool call as complete and emit event\n */\n private completeToolCall(\n messageId: string,\n _index: number,\n toolCall: InternalToolCallState,\n ): void {\n toolCall.state = 'input-complete'\n\n // Try final parse\n toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments)\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: 'input-complete',\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n toolCall.id,\n 'input-complete',\n toolCall.arguments,\n )\n }\n\n /**\n * 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 /**\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.messages = errorStructuredOutputPart(\n this.messages,\n messageId,\n 'Stream ended without structured-output.complete',\n )\n }\n this.structuredMessageIds.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.pendingManualMessageId = null\n this.pendingThinkingStepId = null\n this.finishReason = null\n this.hasError = false\n this.isDone = false\n this.chunkStrategy.reset?.()\n }\n\n /**\n * Full reset (including messages)\n */\n reset(): void {\n this.resetStreamState()\n this.messages = []\n }\n\n /**\n * Check if a message contains only whitespace text and no other meaningful parts\n * (no tool calls, tool results, thinking, etc.)\n */\n private isWhitespaceOnlyMessage(message: UIMessage): boolean {\n if (message.parts.length === 0) return false\n return message.parts.every(\n (part) => part.type === 'text' && part.content.trim() === '',\n )\n }\n\n /**\n * Replay a recording through the processor\n */\n static async replay(\n recording: ChunkRecording,\n options?: StreamProcessorOptions,\n ): Promise<ProcessorResult> {\n const processor = new StreamProcessor(options)\n return processor.process(createReplayStream(recording))\n }\n}\n\n/**\n * Create an async iterable from a recording\n */\nexport function createReplayStream(\n recording: ChunkRecording,\n): AsyncIterable<StreamChunk> {\n return {\n // eslint-disable-next-line @typescript-eslint/require-await -- async generator required by AsyncIterable contract; body has no await\n async *[Symbol.asyncIterator]() {\n for (const { chunk } of recording.chunks) {\n yield chunk\n }\n },\n }\n}\n"],"names":["state","delta","stepId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"processor.js","sources":["../../../../../src/activities/chat/stream/processor.ts"],"sourcesContent":["/**\n * Unified Stream Processor\n *\n * Core stream processing engine that manages the full UIMessage[] conversation.\n * Single source of truth for message state.\n *\n * Handles:\n * - Full conversation management (UIMessage[])\n * - Text content accumulation with configurable chunking strategies\n * - Parallel tool calls with lifecycle state tracking\n * - Tool results and approval flows\n * - Thinking/reasoning content\n * - Recording/replay for testing\n * - Event-driven architecture for UI updates\n * - Per-message stream state tracking for multi-message sessions\n *\n * @see docs/chat-architecture.md — Canonical reference for AG-UI chunk ordering,\n * adapter contract, single-shot flows, and expected UIMessage output.\n */\nimport { generateMessageId, uiMessageToModelMessages } from '../messages.js'\nimport { defaultJSONParser } from './json-parser'\nimport {\n appendStructuredOutputDelta,\n completeStructuredOutputPart,\n errorStructuredOutputPart,\n updateTextPart,\n updateThinkingPart,\n updateToolCallApproval,\n updateToolCallApprovalResponse,\n updateToolCallPart,\n updateToolCallWithOutput,\n updateToolResultPart,\n} from './message-updaters'\nimport { ImmediateStrategy } from './strategies'\nimport type {\n ChunkRecording,\n ChunkStrategy,\n InternalToolCallState,\n MessageStreamState,\n ProcessorResult,\n ProcessorState,\n ToolCallState,\n ToolResultState,\n} from './types'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StreamChunk,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n} from '../../../types'\n\n/**\n * Events emitted by the StreamProcessor\n */\nexport interface StreamProcessorEvents {\n // State events - full array on any change\n onMessagesChange?: (messages: Array<UIMessage>) => void\n\n // Lifecycle events\n onStreamStart?: () => void\n onStreamEnd?: (message: UIMessage) => void\n onError?: (error: Error) => void\n\n // Interaction events - client must handle these\n onToolCall?: (args: {\n toolCallId: string\n toolName: string\n input: any\n }) => void\n onApprovalRequest?: (args: {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n }) => void\n\n // Custom events from server-side tools\n onCustomEvent?: (\n eventType: string,\n data: unknown,\n context: { toolCallId?: string },\n ) => void\n\n // Granular events for UI optimization (character-by-character, state tracking)\n onTextUpdate?: (messageId: string, content: string) => void\n onToolCallStateChange?: (\n messageId: string,\n toolCallId: string,\n state: ToolCallState,\n args: string,\n ) => void\n onThinkingUpdate?: (\n messageId: string,\n stepId: string,\n content: string,\n ) => void\n}\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 * 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\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 ? '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 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.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.pendingManualMessageId = null\n this.emitMessagesChange()\n }\n\n // ============================================\n // Stream Processing Methods\n // ============================================\n\n /**\n * Process a stream and emit events through handlers\n */\n async process(stream: AsyncIterable<any>): Promise<ProcessorResult> {\n // Reset stream state (but keep messages)\n this.resetStreamState()\n\n // Start recording if enabled\n if (this.recordingEnabled) {\n this.startRecording()\n }\n\n // Process each chunk\n for await (const chunk of stream) {\n this.processChunk(chunk)\n }\n\n // Stream ended - finalize everything\n this.finalizeStream()\n\n // Finalize recording\n if (this.recording) {\n this.recording.result = this.getResult()\n }\n\n return this.getResult()\n }\n\n /**\n * Process a single chunk from the stream.\n *\n * Central dispatch for all AG-UI events. Each event type maps to a specific\n * handler. Events not listed in the switch are intentionally ignored\n * (STEP_STARTED, STATE_SNAPSHOT, STATE_DELTA).\n *\n * @see docs/chat-architecture.md#adapter-contract — Expected event types and ordering\n */\n processChunk(chunk: StreamChunk): void {\n // Record chunk if enabled\n if (this.recording) {\n this.recording.chunks.push({\n chunk,\n timestamp: Date.now(),\n index: this.recording.chunks.length,\n })\n }\n\n // Cast needed: @ag-ui/core Zod passthrough types add `& { [k: string]: unknown }`\n // which prevents TypeScript from narrowing the `type` discriminant in switch.\n const c = chunk\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType enum members vs string-literal case labels; default branch handles untraced events.\n switch (c.type) {\n // AG-UI Events\n case 'TEXT_MESSAGE_START':\n this.handleTextMessageStartEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,\n )\n break\n\n case 'TEXT_MESSAGE_CONTENT':\n this.handleTextMessageContentEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n )\n break\n\n case 'TEXT_MESSAGE_END':\n this.handleTextMessageEndEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_END' }>,\n )\n break\n\n case 'TOOL_CALL_START':\n this.handleToolCallStartEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n )\n break\n\n case 'TOOL_CALL_ARGS':\n this.handleToolCallArgsEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n )\n break\n\n case 'TOOL_CALL_END':\n this.handleToolCallEndEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n )\n break\n\n case 'RUN_FINISHED':\n this.handleRunFinishedEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n )\n break\n\n case 'RUN_ERROR':\n this.handleRunErrorEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n )\n break\n\n case 'STEP_FINISHED':\n this.handleStepFinishedEvent(\n chunk as Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n )\n break\n\n case 'MESSAGES_SNAPSHOT':\n this.handleMessagesSnapshotEvent(\n chunk as Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,\n )\n break\n\n case 'CUSTOM':\n this.handleCustomEvent(\n chunk as Extract<StreamChunk, { type: 'CUSTOM' }>,\n )\n break\n\n case 'RUN_STARTED':\n this.handleRunStartedEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_STARTED' }>,\n )\n break\n\n case 'REASONING_START':\n case 'REASONING_MESSAGE_START':\n case 'REASONING_MESSAGE_END':\n case 'REASONING_END':\n // No special handling needed\n break\n\n case 'REASONING_MESSAGE_CONTENT':\n this.handleReasoningMessageContentEvent(\n chunk as Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,\n )\n break\n\n case 'TOOL_CALL_RESULT':\n this.handleToolCallResultEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_RESULT' }>,\n )\n break\n\n case 'STEP_STARTED':\n this.handleStepStartedEvent(\n chunk as Extract<StreamChunk, { type: 'STEP_STARTED' }>,\n )\n break\n\n default:\n // STATE_SNAPSHOT, STATE_DELTA - no special handling needed\n break\n }\n }\n\n // ============================================\n // Per-Message State Helpers\n // ============================================\n\n /**\n * Create a new MessageStreamState for a message\n */\n private createMessageState(\n messageId: string,\n role: 'user' | 'assistant' | 'system',\n ): MessageStreamState {\n const state: MessageStreamState = {\n id: messageId,\n role,\n totalTextContent: '',\n currentSegmentText: '',\n lastEmittedText: '',\n hasSeenReasoningEvents: false,\n thinkingSteps: new Map(),\n thinkingStepSignatures: new Map(),\n thinkingStepOrder: [],\n currentThinkingStepId: null,\n toolCalls: new Map(),\n toolCallOrder: [],\n hasToolCallsSinceTextStart: false,\n isComplete: false,\n }\n this.messageStates.set(messageId, state)\n return state\n }\n\n /**\n * Get the MessageStreamState for a message\n */\n private getMessageState(messageId: string): MessageStreamState | undefined {\n return this.messageStates.get(messageId)\n }\n\n /**\n * Promote a pending stepId from a STEP_STARTED that fired before the\n * assistant message existed onto the given message state, so the next\n * thinking event (STEP_FINISHED or REASONING_MESSAGE_CONTENT) attributes\n * to the correct step.\n */\n private consumePendingThinkingStep(state: MessageStreamState): void {\n if (!this.pendingThinkingStepId) return\n const stepId = this.pendingThinkingStepId\n state.currentThinkingStepId = stepId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n }\n this.pendingThinkingStepId = null\n }\n\n /**\n * Get the most recent active assistant message ID.\n * Used as fallback for events that don't include a messageId.\n */\n private getActiveAssistantMessageId(): string | null {\n // Set iteration is insertion-order; reverse-iterate to search from the end\n const ids = Array.from(this.activeMessageIds).reverse()\n for (const id of ids) {\n const state = this.messageStates.get(id)\n if (state && state.role === 'assistant') {\n return id\n }\n }\n return null\n }\n\n /**\n * Ensure an active assistant message exists, creating one if needed.\n * Used for backward compat when events arrive without prior TEXT_MESSAGE_START.\n *\n * On reconnect/resume, a TEXT_MESSAGE_CONTENT may arrive for a message that\n * already exists in this.messages (e.g. from initialMessages or a prior\n * MESSAGES_SNAPSHOT) but whose transient state was cleared. In that case we\n * hydrate state from the existing message rather than creating a duplicate.\n */\n private ensureAssistantMessage(preferredId?: string): {\n messageId: string\n state: MessageStreamState\n } {\n // Try to find state by preferred ID\n if (preferredId) {\n const state = this.getMessageState(preferredId)\n if (state) return { messageId: preferredId, state }\n }\n\n // Try active assistant message\n const activeId = this.getActiveAssistantMessageId()\n if (activeId) {\n const state = this.getMessageState(activeId)\n if (state) return { messageId: activeId, state }\n }\n\n // Check if a message with preferredId already exists (reconnect/resume case).\n // Hydrate transient state from the existing message instead of duplicating it.\n if (preferredId) {\n const existingMsg = this.messages.find((m) => m.id === preferredId)\n if (existingMsg) {\n const state = this.createMessageState(preferredId, existingMsg.role)\n this.activeMessageIds.add(preferredId)\n\n // Seed segment text from the existing last text part so that\n // incoming deltas append correctly and updateTextPart produces\n // the right content (existing text + new delta).\n const lastPart =\n existingMsg.parts.length > 0\n ? existingMsg.parts[existingMsg.parts.length - 1]\n : null\n if (lastPart && lastPart.type === 'text') {\n state.currentSegmentText = lastPart.content\n state.lastEmittedText = lastPart.content\n state.totalTextContent = lastPart.content\n }\n\n return { messageId: preferredId, state }\n }\n }\n\n // Auto-create an assistant message (backward compat for process() without TEXT_MESSAGE_START)\n const id = preferredId || generateMessageId()\n const assistantMessage: UIMessage = {\n id,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n this.messages = [...this.messages, assistantMessage]\n const state = this.createMessageState(id, 'assistant')\n this.activeMessageIds.add(id)\n this.pendingManualMessageId = id\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n return { messageId: id, state }\n }\n\n // ============================================\n // Event Handlers\n // ============================================\n\n /**\n * Handle TEXT_MESSAGE_START event\n */\n private handleTextMessageStartEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,\n ): void {\n const { messageId, role } = chunk\n\n // Map 'tool' and 'developer' roles to 'assistant' for both UIMessage and MessageStreamState\n // (UIMessage doesn't support 'tool'/'developer' role, and lookups like\n // getActiveAssistantMessageId() check state.role === 'assistant')\n const uiRole: 'system' | 'user' | 'assistant' =\n role === 'user' || role === 'system' ? role : 'assistant'\n\n // Case 1: A manual message was created via startAssistantMessage()\n if (this.pendingManualMessageId) {\n const pendingId = this.pendingManualMessageId\n this.pendingManualMessageId = null\n\n if (pendingId !== messageId) {\n // Update the message's ID in the messages array\n this.messages = this.messages.map((msg) =>\n msg.id === pendingId ? { ...msg, id: messageId } : msg,\n )\n\n // Move state to the new key\n const existingState = this.messageStates.get(pendingId)\n if (existingState) {\n existingState.id = messageId\n this.messageStates.delete(pendingId)\n this.messageStates.set(messageId, existingState)\n }\n\n // Update activeMessageIds\n this.activeMessageIds.delete(pendingId)\n this.activeMessageIds.add(messageId)\n }\n\n // Ensure state exists\n if (!this.messageStates.has(messageId)) {\n this.createMessageState(messageId, uiRole)\n this.activeMessageIds.add(messageId)\n }\n\n this.emitMessagesChange()\n return\n }\n\n // Case 2: Message already exists (dedup)\n const existingMsg = this.messages.find((m) => m.id === messageId)\n if (existingMsg) {\n this.activeMessageIds.add(messageId)\n const existingState = this.messageStates.get(messageId)\n if (!existingState) {\n this.createMessageState(messageId, uiRole)\n } else {\n // If tool calls happened since last text, this TEXT_MESSAGE_START\n // signals a new text segment — reset segment accumulation\n if (existingState.hasToolCallsSinceTextStart) {\n if (\n existingState.currentSegmentText !== existingState.lastEmittedText\n ) {\n this.emitTextUpdateForMessage(messageId)\n }\n existingState.currentSegmentText = ''\n existingState.lastEmittedText = ''\n existingState.hasToolCallsSinceTextStart = false\n }\n }\n return\n }\n\n // Case 3: New message from the stream\n const newMessage: UIMessage = {\n id: messageId,\n role: uiRole,\n parts: [],\n createdAt: new Date(),\n }\n\n this.messages = [...this.messages, newMessage]\n this.createMessageState(messageId, uiRole)\n this.activeMessageIds.add(messageId)\n\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n }\n\n /**\n * Handle TEXT_MESSAGE_END event\n */\n private handleTextMessageEndEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_END' }>,\n ): void {\n const { messageId } = chunk\n const state = this.getMessageState(messageId)\n if (!state) return\n if (state.isComplete) return\n\n // Emit any pending text for this message\n if (state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n\n // Complete all tool calls for this message\n this.completeAllToolCallsForMessage(messageId)\n }\n\n /**\n * Handle MESSAGES_SNAPSHOT event\n */\n private handleMessagesSnapshotEvent(\n chunk: Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,\n ): void {\n this.resetStreamState()\n // AG-UI Message[] is compatible with UIMessage[] at runtime\n this.messages = [...chunk.messages] as Array<UIMessage>\n this.emitMessagesChange()\n }\n\n /**\n * Handle TEXT_MESSAGE_CONTENT event.\n *\n * Accumulates delta into both currentSegmentText (for UI emission) and\n * totalTextContent (for ProcessorResult). Lazily creates the assistant\n * UIMessage on first content. Uses updateTextPart() which replaces the\n * last TextPart or creates a new one depending on part ordering.\n *\n * @see docs/chat-architecture.md#single-shot-text-response — Text accumulation step-by-step\n * @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic\n */\n private handleTextMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(chunk.messageId)\n\n // Content arriving means all current tool calls for this message are complete\n this.completeAllToolCallsForMessage(messageId)\n\n if (this.structuredMessageIds.has(messageId)) {\n // `chunk.delta` is incremental; `chunk.content` is sometimes cumulative\n // (mirrors what the plain-text branch handles below). Reconcile against\n // the existing raw buffer so adapters that emit cumulative content\n // don't duplicate the JSON.\n let delta = chunk.delta || ''\n if (delta === '' && chunk.content !== undefined && chunk.content !== '') {\n const existingRaw = (\n this.messages\n .find((m) => m.id === messageId)\n ?.parts.find(\n (p): p is Extract<MessagePart, { type: 'structured-output' }> =>\n p.type === 'structured-output',\n ) ?? { raw: '' }\n ).raw\n if (chunk.content.startsWith(existingRaw)) {\n delta = chunk.content.slice(existingRaw.length)\n } else if (existingRaw.startsWith(chunk.content)) {\n delta = ''\n } else {\n delta = chunk.content\n }\n }\n if (delta !== '') {\n this.messages = appendStructuredOutputDelta(\n this.messages,\n messageId,\n delta,\n )\n state.totalTextContent += delta\n this.emitMessagesChange()\n }\n return\n }\n\n const previousSegment = state.currentSegmentText\n\n // Detect if this is a NEW text segment (after tool calls) vs continuation\n const isNewSegment =\n state.hasToolCallsSinceTextStart &&\n previousSegment.length > 0 &&\n this.isNewTextSegment(chunk, previousSegment)\n\n if (isNewSegment) {\n // Emit any accumulated text before starting new segment\n if (previousSegment !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n // Reset SEGMENT text accumulation for the new text segment after tool calls\n state.currentSegmentText = ''\n state.lastEmittedText = ''\n state.hasToolCallsSinceTextStart = false\n }\n\n const currentText = state.currentSegmentText\n let nextText = currentText\n\n // Prefer delta over content - delta is the incremental change\n // Normalize to empty string to avoid \"undefined\" string concatenation\n const delta = chunk.delta || ''\n if (delta !== '') {\n nextText = currentText + delta\n } else if (chunk.content !== undefined && chunk.content !== '') {\n // Fallback: use content if delta is not provided\n if (chunk.content.startsWith(currentText)) {\n nextText = chunk.content\n } else if (currentText.startsWith(chunk.content)) {\n nextText = currentText\n } else {\n nextText = currentText + chunk.content\n }\n }\n\n // Calculate the delta for totalTextContent\n const textDelta = nextText.slice(currentText.length)\n state.currentSegmentText = nextText\n state.totalTextContent += textDelta\n\n // Use delta for chunk strategy if available\n const chunkPortion = chunk.delta || chunk.content || ''\n const shouldEmit = this.chunkStrategy.shouldEmit(\n chunkPortion,\n state.currentSegmentText,\n )\n if (shouldEmit && state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n }\n\n /**\n * Handle TOOL_CALL_START event.\n *\n * Creates a new InternalToolCallState entry in the toolCalls Map and appends\n * a ToolCallPart to the UIMessage. Duplicate toolCallId is a no-op.\n *\n * CRITICAL: This MUST be received before any TOOL_CALL_ARGS for the same\n * toolCallId. Args for unknown IDs are silently dropped.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Tool call state transitions\n * @see docs/chat-architecture.md#parallel-tool-calls-single-shot — Parallel tracking by ID\n * @see docs/chat-architecture.md#adapter-contract — Ordering requirements\n */\n private handleToolCallStartEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n ): void {\n // Determine the message this tool call belongs to\n const targetMessageId =\n chunk.parentMessageId ?? this.getActiveAssistantMessageId()\n const { messageId, state } = this.ensureAssistantMessage(\n targetMessageId ?? undefined,\n )\n\n // Mark that we've seen tool calls since the last text segment\n state.hasToolCallsSinceTextStart = true\n\n const toolCallId = chunk.toolCallId\n const existingToolCall = state.toolCalls.get(toolCallId)\n\n if (!existingToolCall) {\n // New tool call starting\n const initialState: ToolCallState = 'awaiting-input'\n\n // `toolName` is a deprecated alias for `toolCallName` (see ToolCallStartEvent\n // in types.ts). Accept either so chunks from older code paths or any\n // adapter that only sets the deprecated field still produce a named part.\n // The type marks both as required strings, but in practice some emitters\n // only set one — fall back via the runtime value rather than the type.\n const toolName =\n (chunk as { toolCallName?: string }).toolCallName ?? chunk.toolName\n\n // Capture provider metadata that arrived on TOOL_CALL_START so it\n // round-trips back through the assistant message on the next turn\n // (e.g. Gemini's thoughtSignature).\n const chunkMetadata = chunk.metadata\n\n const newToolCall: InternalToolCallState = {\n id: chunk.toolCallId,\n name: toolName,\n arguments: '',\n state: initialState,\n parsedArguments: undefined,\n index: chunk.index ?? state.toolCalls.size,\n ...(chunkMetadata !== undefined && { metadata: chunkMetadata }),\n }\n\n state.toolCalls.set(toolCallId, newToolCall)\n state.toolCallOrder.push(toolCallId)\n\n // Store mapping for TOOL_CALL_ARGS/END routing\n this.toolCallToMessage.set(toolCallId, messageId)\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: chunk.toolCallId,\n name: toolName,\n arguments: '',\n state: initialState,\n ...(chunkMetadata !== undefined && { metadata: chunkMetadata }),\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n chunk.toolCallId,\n initialState,\n '',\n )\n }\n }\n\n /**\n * Handle TOOL_CALL_ARGS event.\n *\n * Appends the delta to the tool call's accumulated arguments string.\n * Transitions state from awaiting-input → input-streaming on first non-empty delta.\n * Attempts partial JSON parse on each update for UI preview.\n *\n * If toolCallId is not found in the Map (no preceding TOOL_CALL_START),\n * this event is silently dropped.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Step-by-step tool call processing\n */\n private handleToolCallArgsEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n ): void {\n const toolCallId = chunk.toolCallId\n const messageId = this.toolCallToMessage.get(toolCallId)\n if (!messageId) return\n\n const state = this.getMessageState(messageId)\n if (!state) return\n\n const existingToolCall = state.toolCalls.get(toolCallId)\n if (!existingToolCall) return\n\n const wasAwaitingInput = existingToolCall.state === 'awaiting-input'\n\n // Accumulate arguments from delta\n existingToolCall.arguments += chunk.delta || ''\n\n // Update state\n if (wasAwaitingInput && chunk.delta) {\n existingToolCall.state = 'input-streaming'\n }\n\n // Try to parse the updated arguments\n existingToolCall.parsedArguments = this.jsonParser.parse(\n existingToolCall.arguments,\n )\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: existingToolCall.id,\n name: existingToolCall.name,\n arguments: existingToolCall.arguments,\n state: existingToolCall.state,\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n existingToolCall.id,\n existingToolCall.state,\n existingToolCall.arguments,\n )\n }\n\n /**\n * Handle TOOL_CALL_END event — authoritative signal that a tool call's input is finalized.\n *\n * This event has a DUAL ROLE:\n * - Without `result`: Signals arguments are done (from adapter). Transitions to input-complete.\n * - With `result`: Signals tool was executed and result is available (from TextEngine).\n * Creates both output on the tool-call part AND a tool-result part.\n *\n * If `input` is provided, it overrides the accumulated string parse as the\n * canonical parsed arguments.\n *\n * @see docs/chat-architecture.md#tool-results-and-the-tool_call_end-dual-role — Full explanation\n * @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow\n */\n private handleToolCallEndEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n ): void {\n const messageId = this.toolCallToMessage.get(chunk.toolCallId)\n if (!messageId) return\n\n const msgState = this.getMessageState(messageId)\n if (!msgState) return\n\n // Transition the tool call to input-complete (the authoritative completion signal)\n const existingToolCall = msgState.toolCalls.get(chunk.toolCallId)\n if (existingToolCall && existingToolCall.state !== 'input-complete') {\n // If TOOL_CALL_END provides parsed input and no TOOL_CALL_ARGS were\n // received, back-fill the arguments string so the UIMessage ToolCallPart\n // carries the correct value (defensive against adapters that skip ARGS).\n if (chunk.input !== undefined && !existingToolCall.arguments) {\n existingToolCall.arguments = JSON.stringify(chunk.input)\n }\n\n const index = msgState.toolCallOrder.indexOf(chunk.toolCallId)\n this.completeToolCall(messageId, index, existingToolCall)\n // If TOOL_CALL_END provides parsed input, use it as the canonical parsed\n // arguments (overrides the accumulated string parse from completeToolCall)\n if (chunk.input !== undefined) {\n existingToolCall.parsedArguments = chunk.input\n }\n }\n\n // Update UIMessage if there's a result\n if (chunk.result) {\n // Step 1: Update the tool-call part's output field (for UI consistency\n // with client tools — see GitHub issue #176)\n let output: unknown\n try {\n output = JSON.parse(chunk.result)\n } catch {\n output = chunk.result\n }\n this.messages = updateToolCallWithOutput(\n this.messages,\n chunk.toolCallId,\n output,\n )\n\n // Step 2: Create/update the tool-result part (for LLM conversation history)\n const resultState: ToolResultState = 'complete'\n this.messages = updateToolResultPart(\n this.messages,\n messageId,\n chunk.toolCallId,\n chunk.result,\n resultState,\n )\n this.emitMessagesChange()\n }\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 )\n\n // Step 2: Create/update the tool-result part\n const resultState: ToolResultState = 'complete'\n this.messages = updateToolResultPart(\n this.messages,\n messageId,\n chunk.toolCallId,\n chunk.content,\n resultState,\n )\n this.emitMessagesChange()\n }\n\n /**\n * Handle RUN_STARTED event.\n *\n * Registers the run so that RUN_FINISHED can determine whether other\n * runs are still active before finalizing.\n */\n private handleRunStartedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_STARTED' }>,\n ): void {\n this.activeRuns.add(chunk.runId)\n }\n\n /**\n * Handle RUN_FINISHED event.\n *\n * Records the finishReason and removes the run from activeRuns.\n * Only finalizes when no more runs are active, so that concurrent\n * runs don't interfere with each other.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — finishReason semantics\n * @see docs/chat-architecture.md#adapter-contract — Why RUN_FINISHED is mandatory\n */\n private handleRunFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n ): void {\n this.finishReason = chunk.finishReason ?? null\n this.activeRuns.delete(chunk.runId)\n\n if (this.activeRuns.size === 0) {\n this.isDone = true\n this.completeAllToolCalls()\n this.finalizeStream()\n }\n }\n\n /**\n * Handle RUN_ERROR event\n */\n private handleRunErrorEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n ): void {\n this.hasError = true\n const runId = (chunk as any).runId as string | 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.messages = errorStructuredOutputPart(\n this.messages,\n messageId,\n errorMessage,\n )\n this.structuredMessageIds.delete(messageId)\n this.emitMessagesChange()\n }\n\n this.events.onError?.(new Error(errorMessage))\n }\n\n /**\n * Handle STEP_STARTED event (for thinking/reasoning content).\n *\n * Records the stepId so that subsequent STEP_FINISHED deltas accumulate\n * into their own ThinkingPart. Does not create a message — the message\n * is lazily created when the first STEP_FINISHED content arrives.\n */\n private handleStepStartedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_STARTED' }>,\n ): void {\n const stepId = chunk.stepId ?? generateMessageId()\n const activeId = this.getActiveAssistantMessageId()\n if (activeId) {\n const state = this.getMessageState(activeId)\n if (state) {\n state.currentThinkingStepId = stepId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n }\n // Clear any pending stepId from a prior STEP_STARTED that fired\n // before the assistant message existed. Now that we're tracking\n // the step directly on message state, the pending value is stale\n // and must not leak into the next STEP_FINISHED (which would\n // misattribute its delta to the stale step).\n this.pendingThinkingStepId = null\n return\n }\n }\n\n // No active message yet — defer until ensureAssistantMessage in STEP_FINISHED\n this.pendingThinkingStepId = stepId\n }\n\n /**\n * Handle STEP_FINISHED event (for thinking/reasoning content).\n *\n * Accumulates delta into the current thinking step's content and updates\n * the corresponding ThinkingPart in the UIMessage.\n *\n * @see docs/chat-architecture.md#thinkingreasoning-content — Thinking flow\n */\n private handleStepFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(\n this.getActiveAssistantMessageId() ?? undefined,\n )\n\n // During the transition period, adapters emit BOTH STEP_FINISHED and\n // REASONING_MESSAGE_CONTENT with the same delta. If we've already processed\n // REASONING_MESSAGE_CONTENT events for this message, skip the duplicate\n // thinking content from STEP_FINISHED to avoid doubled content.\n if (state.hasSeenReasoningEvents) {\n if (chunk.signature) {\n const stepId = state.currentThinkingStepId ?? chunk.stepId\n if (!stepId) return\n const thinking = state.thinkingSteps.get(stepId)\n if (thinking !== undefined) {\n state.thinkingStepSignatures.set(stepId, chunk.signature)\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n thinking,\n chunk.signature,\n )\n this.emitMessagesChange()\n }\n }\n return\n }\n\n this.consumePendingThinkingStep(state)\n\n const stepId =\n state.currentThinkingStepId ?? chunk.stepId ?? generateMessageId()\n\n // Auto-initialize if no prior STEP_STARTED (backward compat)\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n state.currentThinkingStepId = stepId\n }\n\n const previous = state.thinkingSteps.get(stepId) ?? ''\n let nextThinking = previous\n\n // Prefer delta over content\n if (chunk.delta && chunk.delta !== '') {\n nextThinking = previous + chunk.delta\n } else if (chunk.content && chunk.content !== '') {\n if (chunk.content.startsWith(previous)) {\n nextThinking = chunk.content\n } else if (previous.startsWith(chunk.content)) {\n nextThinking = previous\n } else {\n nextThinking = previous + chunk.content\n }\n }\n\n state.thinkingSteps.set(stepId, nextThinking)\n\n if (chunk.signature) {\n state.thinkingStepSignatures.set(stepId, chunk.signature)\n }\n\n // Update UIMessage\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n nextThinking,\n state.thinkingStepSignatures.get(stepId),\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)\n }\n\n /**\n * Handle REASONING_MESSAGE_CONTENT event (AG-UI reasoning protocol).\n *\n * Accumulates reasoning delta into thinking content and updates the\n * corresponding ThinkingPart in the UIMessage.\n */\n private handleReasoningMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(\n this.getActiveAssistantMessageId() ?? undefined,\n )\n\n state.hasSeenReasoningEvents = true\n const delta = chunk.delta || ''\n\n this.consumePendingThinkingStep(state)\n\n const stepId = state.currentThinkingStepId ?? chunk.messageId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n state.currentThinkingStepId = stepId\n }\n\n const nextThinking = (state.thinkingSteps.get(stepId) ?? '') + delta\n state.thinkingSteps.set(stepId, nextThinking)\n\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n nextThinking,\n state.thinkingStepSignatures.get(stepId),\n )\n this.emitMessagesChange()\n\n this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)\n }\n\n /**\n * Handle CUSTOM event.\n *\n * Handles special custom events emitted by the TextEngine (not adapters):\n * - 'tool-input-available': Client tool needs execution. Fires onToolCall.\n * - 'approval-requested': Tool needs user approval. Updates tool-call part\n * state and fires onApprovalRequest.\n *\n * @see docs/chat-architecture.md#client-tools-and-approval-flows — Full flow details\n */\n private handleCustomEvent(\n chunk: Extract<StreamChunk, { type: 'CUSTOM' }>,\n ): void {\n const messageId = this.getActiveAssistantMessageId()\n\n if (chunk.name === 'structured-output.start' && chunk.value) {\n const v = chunk.value as { messageId?: string }\n const targetId = v.messageId ?? messageId\n if (targetId) {\n this.ensureAssistantMessage(targetId)\n this.structuredMessageIds.add(targetId)\n }\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.messages = completeStructuredOutputPart(\n this.messages,\n targetId,\n v.object,\n v.raw ?? '',\n v.reasoning,\n )\n this.structuredMessageIds.delete(targetId)\n this.emitMessagesChange()\n }\n // Fall through so user `onCustomEvent` callbacks still observe the event.\n }\n\n // Handle client tool input availability - trigger client-side execution\n if (chunk.name === 'tool-input-available' && chunk.value) {\n const { toolCallId, toolName, input } = chunk.value as {\n toolCallId: string\n toolName: string\n input: any\n }\n\n // Emit onToolCall event for the client to execute the tool\n this.events.onToolCall?.({\n toolCallId,\n toolName,\n input,\n })\n return\n }\n\n // Handle approval requests\n if (chunk.name === 'approval-requested' && chunk.value) {\n const { toolCallId, toolName, input, approval } = chunk.value as {\n toolCallId: string\n toolName: string\n input: any\n approval: { id: string; needsApproval: boolean }\n }\n\n // Resolve the message containing this tool call. After RUN_FINISHED,\n // activeMessageIds is cleared, so fall back to the toolCallToMessage map\n // which is populated during TOOL_CALL_START and preserved across finalize.\n const resolvedMessageId =\n messageId ?? this.toolCallToMessage.get(toolCallId)\n if (resolvedMessageId) {\n this.messages = updateToolCallApproval(\n this.messages,\n resolvedMessageId,\n toolCallId,\n approval.id,\n )\n this.emitMessagesChange()\n }\n\n // Emit approval request event\n this.events.onApprovalRequest?.({\n toolCallId,\n toolName,\n input,\n approvalId: approval.id,\n })\n return\n }\n\n // Forward non-system custom events to onCustomEvent callback\n if (this.events.onCustomEvent) {\n const toolCallId =\n chunk.value && typeof chunk.value === 'object'\n ? chunk.value.toolCallId\n : undefined\n this.events.onCustomEvent(chunk.name, chunk.value, { toolCallId })\n }\n }\n\n // ============================================\n // Internal Helpers\n // ============================================\n\n /**\n * Detect if an incoming content chunk represents a NEW text segment\n */\n private isNewTextSegment(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n previous: string,\n ): boolean {\n // Check if content is present (delta is always defined but may be empty string)\n if (chunk.content !== undefined) {\n if (chunk.content.length < previous.length) {\n return true\n }\n if (\n !chunk.content.startsWith(previous) &&\n !previous.startsWith(chunk.content)\n ) {\n return true\n }\n }\n return false\n }\n\n /**\n * Complete all tool calls across all active messages — safety net for stream termination.\n *\n * Called by RUN_FINISHED and finalizeStream(). Force-transitions any tool call\n * not yet in input-complete state. Handles cases where TOOL_CALL_END was\n * missed (adapter bug, network error, aborted stream).\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Safety net behavior\n */\n private completeAllToolCalls(): void {\n for (const messageId of this.activeMessageIds) {\n this.completeAllToolCallsForMessage(messageId)\n }\n }\n\n /**\n * Complete all tool calls for a specific message\n */\n private completeAllToolCallsForMessage(messageId: string): void {\n const state = this.getMessageState(messageId)\n if (!state) return\n\n state.toolCalls.forEach((toolCall, id) => {\n if (toolCall.state !== 'input-complete') {\n const index = state.toolCallOrder.indexOf(id)\n this.completeToolCall(messageId, index, toolCall)\n }\n })\n }\n\n /**\n * Mark a tool call as complete and emit event\n */\n private completeToolCall(\n messageId: string,\n _index: number,\n toolCall: InternalToolCallState,\n ): void {\n toolCall.state = 'input-complete'\n\n // Try final parse\n toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments)\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: 'input-complete',\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n toolCall.id,\n 'input-complete',\n toolCall.arguments,\n )\n }\n\n /**\n * 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 /**\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.messages = errorStructuredOutputPart(\n this.messages,\n messageId,\n 'Stream ended without structured-output.complete',\n )\n }\n this.structuredMessageIds.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.pendingManualMessageId = null\n this.pendingThinkingStepId = null\n this.finishReason = null\n this.hasError = false\n this.isDone = false\n this.chunkStrategy.reset?.()\n }\n\n /**\n * Full reset (including messages)\n */\n reset(): void {\n this.resetStreamState()\n this.messages = []\n }\n\n /**\n * Check if a message contains only whitespace text and no other meaningful parts\n * (no tool calls, tool results, thinking, etc.)\n */\n private isWhitespaceOnlyMessage(message: UIMessage): boolean {\n if (message.parts.length === 0) return false\n return message.parts.every(\n (part) => part.type === 'text' && part.content.trim() === '',\n )\n }\n\n /**\n * Replay a recording through the processor\n */\n static async replay(\n recording: ChunkRecording,\n options?: StreamProcessorOptions,\n ): Promise<ProcessorResult> {\n const processor = new StreamProcessor(options)\n return processor.process(createReplayStream(recording))\n }\n}\n\n/**\n * Create an async iterable from a recording\n */\nexport function createReplayStream(\n recording: ChunkRecording,\n): AsyncIterable<StreamChunk> {\n return {\n // eslint-disable-next-line @typescript-eslint/require-await -- async generator required by AsyncIterable contract; body has no await\n async *[Symbol.asyncIterator]() {\n for (const { chunk } of recording.chunks) {\n yield chunk\n }\n },\n }\n}\n"],"names":["state","delta","stepId"],"mappings":"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"}
|
|
@@ -103,6 +103,16 @@ export declare function validateWithStandardSchema<T>(schema: unknown, data: unk
|
|
|
103
103
|
path?: Array<string> | undefined;
|
|
104
104
|
}>;
|
|
105
105
|
}>;
|
|
106
|
+
/**
|
|
107
|
+
* Error thrown when Standard Schema validation fails. Carries the original
|
|
108
|
+
* `issues` array so consumers (middleware `onError`, callers catching from
|
|
109
|
+
* `chat({ outputSchema })`) can programmatically inspect each failure.
|
|
110
|
+
*/
|
|
111
|
+
export declare class StandardSchemaValidationError extends Error {
|
|
112
|
+
readonly name = "StandardSchemaValidationError";
|
|
113
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
114
|
+
constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
115
|
+
}
|
|
106
116
|
/**
|
|
107
117
|
* Synchronously validates data against a Standard Schema compliant schema.
|
|
108
118
|
* Note: Some Standard Schema implementations may only support async validation.
|
|
@@ -111,6 +121,7 @@ export declare function validateWithStandardSchema<T>(schema: unknown, data: unk
|
|
|
111
121
|
* @param schema - Standard Schema compliant schema
|
|
112
122
|
* @param data - Data to validate
|
|
113
123
|
* @returns Parsed/validated data
|
|
114
|
-
* @throws
|
|
124
|
+
* @throws StandardSchemaValidationError if validation fails; Error if the
|
|
125
|
+
* schema only supports async validation.
|
|
115
126
|
*/
|
|
116
127
|
export declare function parseWithStandardSchema<T>(schema: unknown, data: unknown): T;
|
|
@@ -11,8 +11,7 @@ function isStandardJSONSchema(schema) {
|
|
|
11
11
|
schema["~standard"].version === 1 && typeof schema["~standard"].jsonSchema === "object" && typeof schema["~standard"].jsonSchema.input === "function";
|
|
12
12
|
}
|
|
13
13
|
function isStandardSchema(schema) {
|
|
14
|
-
return typeof schema === "object" && schema !== null && "~standard" in schema && typeof schema["~standard"] === "object" && schema["~standard"] !== null && "version" in schema["~standard"] &&
|
|
15
|
-
schema["~standard"].version === 1 && "validate" in schema["~standard"] && typeof schema["~standard"].validate === "function";
|
|
14
|
+
return typeof schema === "object" && schema !== null && "~standard" in schema && typeof schema["~standard"] === "object" && schema["~standard"] !== null && "version" in schema["~standard"] && schema["~standard"].version === 1 && "validate" in schema["~standard"] && typeof schema["~standard"].validate === "function";
|
|
16
15
|
}
|
|
17
16
|
function makeStructuredOutputCompatible(schema, originalRequired = []) {
|
|
18
17
|
const result = { ...schema };
|
|
@@ -98,6 +97,16 @@ function convertSchemaToJsonSchema(schema, options = {}) {
|
|
|
98
97
|
}
|
|
99
98
|
return schema;
|
|
100
99
|
}
|
|
100
|
+
class StandardSchemaValidationError extends Error {
|
|
101
|
+
name = "StandardSchemaValidationError";
|
|
102
|
+
issues;
|
|
103
|
+
constructor(issues) {
|
|
104
|
+
super(
|
|
105
|
+
`Validation failed: ${issues.map((i) => i.message || "Validation failed").join(", ")}`
|
|
106
|
+
);
|
|
107
|
+
this.issues = issues;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
101
110
|
function parseWithStandardSchema(schema, data) {
|
|
102
111
|
if (!isStandardSchema(schema)) {
|
|
103
112
|
return data;
|
|
@@ -111,10 +120,10 @@ function parseWithStandardSchema(schema, data) {
|
|
|
111
120
|
if (!result.issues) {
|
|
112
121
|
return result.value;
|
|
113
122
|
}
|
|
114
|
-
|
|
115
|
-
throw new Error(`Validation failed: ${errorMessages}`);
|
|
123
|
+
throw new StandardSchemaValidationError(result.issues);
|
|
116
124
|
}
|
|
117
125
|
export {
|
|
126
|
+
StandardSchemaValidationError,
|
|
118
127
|
convertSchemaToJsonSchema,
|
|
119
128
|
isStandardJSONSchema,
|
|
120
129
|
isStandardSchema,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-converter.js","sources":["../../../../../src/activities/chat/tools/schema-converter.ts"],"sourcesContent":["import type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from '@standard-schema/spec'\nimport type { JSONSchema, SchemaInput } from '../../../types'\n\n/**\n * Build a JSONSchema object from any plain key/value source. The `JSONSchema`\n * interface's `[key: string]: any` index signature makes every property\n * assignable through bracket access without a type cast — copying keys here\n * lets us narrow either `Record<string, unknown>` (returned by\n * `~standard.jsonSchema.input()`) or a `JSONSchema` (from the SchemaInput\n * pass-through arm) into the typed view used by the rest of this module.\n *\n * Accepts `object` so callers don't need a cast when narrowing from union\n * types like `SchemaInput`.\n */\nfunction toJsonSchema(obj: object): JSONSchema {\n const result: JSONSchema = {}\n for (const [key, value] of Object.entries(obj)) {\n if (key === '$schema') continue // not needed by LLM providers\n result[key] = value\n }\n return result\n}\n\n/**\n * Check if a value is a Standard JSON Schema compliant schema.\n * Standard JSON Schema compliant libraries (Zod v4+, ArkType, Valibot with toStandardJsonSchema, etc.)\n * implement the '~standard' property with jsonSchema converter methods.\n */\nexport function isStandardJSONSchema(\n schema: unknown,\n): schema is StandardJSONSchemaV1 {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n '~standard' in schema &&\n typeof (schema as StandardJSONSchemaV1)['~standard'] === 'object' &&\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for caller-provided unknown; type assertion narrows but doesn't validate the wire payload\n (schema as StandardJSONSchemaV1)['~standard'].version === 1 &&\n typeof (schema as StandardJSONSchemaV1)['~standard'].jsonSchema ===\n 'object' &&\n typeof (schema as StandardJSONSchemaV1)['~standard'].jsonSchema.input ===\n 'function'\n )\n}\n\n/**\n * Check if a value is a Standard Schema compliant schema (for validation).\n * Standard Schema compliant libraries implement the '~standard' property with a validate function.\n */\nexport function isStandardSchema(schema: unknown): schema is StandardSchemaV1 {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n '~standard' in schema &&\n typeof schema['~standard'] === 'object' &&\n schema['~standard'] !== null &&\n 'version' in schema['~standard'] &&\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for caller-provided unknown; in-operator narrows but doesn't validate the wire payload\n schema['~standard'].version === 1 &&\n 'validate' in schema['~standard'] &&\n typeof schema['~standard'].validate === 'function'\n )\n}\n\n/**\n * Transform a JSON schema to be compatible with OpenAI's structured output requirements.\n * OpenAI requires:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for objects\n *\n * @param schema - JSON schema to transform\n * @param originalRequired - Original required array (to know which fields were optional)\n * @returns Transformed schema compatible with OpenAI structured output\n */\nfunction makeStructuredOutputCompatible(\n schema: JSONSchema,\n originalRequired: Array<string> = [],\n): JSONSchema {\n const result: JSONSchema = { ...schema }\n\n // Handle object types\n if (result.type === 'object' && result.properties) {\n const properties: Record<string, JSONSchema> = { ...result.properties }\n const allPropertyNames = Object.keys(properties)\n\n // Transform each property\n for (const propName of allPropertyNames) {\n const prop = properties[propName]\n if (!prop) continue\n const wasOptional = !originalRequired.includes(propName)\n\n // Recursively transform nested objects/arrays\n if (prop.type === 'object' && prop.properties) {\n const transformed = makeStructuredOutputCompatible(\n prop,\n prop.required || [],\n )\n properties[propName] = wasOptional\n ? { ...transformed, type: ['object', 'null'] }\n : transformed\n } else if (prop.type === 'array' && prop.items) {\n const items = Array.isArray(prop.items) ? prop.items[0] : prop.items\n const transformed: JSONSchema = {\n ...prop,\n items: items\n ? makeStructuredOutputCompatible(items, items.required || [])\n : prop.items,\n }\n properties[propName] = wasOptional\n ? { ...transformed, type: ['array', 'null'] }\n : transformed\n } else if (wasOptional) {\n // Make optional fields nullable by adding null to the type\n if (prop.type && !Array.isArray(prop.type)) {\n properties[propName] = {\n ...prop,\n type: [prop.type, 'null'],\n }\n } else if (Array.isArray(prop.type) && !prop.type.includes('null')) {\n properties[propName] = {\n ...prop,\n type: [...prop.type, 'null'],\n }\n }\n }\n }\n\n result.properties = properties\n // ALL properties must be required for OpenAI structured output\n result.required = allPropertyNames\n // additionalProperties must be false\n result.additionalProperties = false\n }\n\n // Handle array types with object items\n if (result.type === 'array' && result.items) {\n const items = Array.isArray(result.items) ? result.items[0] : result.items\n if (items) {\n result.items = makeStructuredOutputCompatible(items, items.required || [])\n }\n }\n\n return result\n}\n\n/**\n * Options for schema conversion\n */\nexport interface ConvertSchemaOptions {\n /**\n * When true, transforms the schema to be compatible with OpenAI's structured output requirements:\n * - All properties are added to the `required` array\n * - Optional fields get null added to their type union\n * - additionalProperties is set to false for all objects\n *\n * @default false\n */\n forStructuredOutput?: boolean\n}\n\n/**\n * Converts a Standard JSON Schema compliant schema or plain JSONSchema to JSON Schema format\n * compatible with LLM providers.\n *\n * Supports any schema library that implements the Standard JSON Schema spec (v1):\n * - Zod v4+ (natively supports StandardJSONSchemaV1)\n * - ArkType (natively supports StandardJSONSchemaV1)\n * - Valibot (via `toStandardJsonSchema()` from `@valibot/to-json-schema`)\n *\n * If the input is already a plain JSONSchema object, it is returned as-is.\n *\n * @param schema - Standard JSON Schema compliant schema or plain JSONSchema object to convert\n * @param options - Conversion options\n * @returns JSON Schema object that can be sent to LLM providers\n *\n * @example\n * ```typescript\n * // Using Zod v4+ (natively supports Standard JSON Schema)\n * import * as z from 'zod';\n *\n * const zodSchema = z.object({\n * location: z.string().describe('City name'),\n * unit: z.enum(['celsius', 'fahrenheit']).optional()\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(zodSchema);\n *\n * @example\n * // Using ArkType (natively supports Standard JSON Schema)\n * import { type } from 'arktype';\n *\n * const arkSchema = type({\n * location: 'string',\n * unit: \"'celsius' | 'fahrenheit'\"\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(arkSchema);\n *\n * @example\n * // Using Valibot (via toStandardJsonSchema)\n * import * as v from 'valibot';\n * import { toStandardJsonSchema } from '@valibot/to-json-schema';\n *\n * const valibotSchema = toStandardJsonSchema(v.object({\n * location: v.string(),\n * unit: v.optional(v.picklist(['celsius', 'fahrenheit']))\n * }));\n *\n * const jsonSchema = convertSchemaToJsonSchema(valibotSchema);\n *\n * @example\n * // Using JSONSchema directly (passes through unchanged)\n * const rawSchema = {\n * type: 'object',\n * properties: { location: { type: 'string' } },\n * required: ['location']\n * };\n * const result = convertSchemaToJsonSchema(rawSchema);\n * ```\n */\nexport function convertSchemaToJsonSchema(\n schema: SchemaInput | undefined,\n options: ConvertSchemaOptions = {},\n): JSONSchema | undefined {\n if (!schema) return undefined\n\n const { forStructuredOutput = false } = options\n\n // If it's a Standard JSON Schema compliant schema, use the standard interface\n if (isStandardJSONSchema(schema)) {\n const jsonSchema = schema['~standard'].jsonSchema.input({\n target: 'draft-07',\n })\n\n // Rebuild structurally so the typed JSONSchema view is acquired without\n // a `Record<string, unknown> as JSONSchema` cast; `toJsonSchema()` also\n // drops the `$schema` key which LLM providers don't need.\n let result: JSONSchema = toJsonSchema(jsonSchema)\n\n // Ensure object schemas always have type: \"object\"\n // If it has properties (even empty), it should be an object type\n if ('properties' in result && !result.type) {\n result.type = 'object'\n }\n\n // Ensure properties exists for object types (even if empty)\n if (result.type === 'object' && !('properties' in result)) {\n result.properties = {}\n }\n\n // Ensure required exists for object types (even if empty array)\n if (result.type === 'object' && !('required' in result)) {\n result.required = []\n }\n\n // Apply structured output transformation if requested\n if (forStructuredOutput) {\n result = makeStructuredOutputCompatible(result, result.required || [])\n }\n\n return result\n }\n\n // Detect Standard Schema validators (Zod, ArkType, Valibot, …) that don't\n // expose a `~standard.jsonSchema` converter. These would otherwise fall\n // through to the JSONSchema pass-through below and ship `{ '~standard': … }`\n // straight to the LLM provider, producing an opaque downstream error. Fail\n // fast with actionable guidance instead.\n if (isStandardSchema(schema)) {\n throw new Error(\n 'Schema is a Standard Schema validator but does not expose a JSON Schema ' +\n 'converter on `~standard.jsonSchema`. Use Zod v4.2+, ArkType v2.1.28+, ' +\n 'or wrap a Valibot schema with `toStandardJsonSchema()` from ' +\n '`@valibot/to-json-schema` before passing it as `outputSchema`.',\n )\n }\n\n // If it's not a Standard JSON Schema, assume it's already a JSONSchema and pass through\n // Still apply structured output transformation if requested\n\n // At this branch, `schema` is the plain `JSONSchema` arm of `SchemaInput`\n // (the two `~standard` arms were handled above). When no transformation\n // is requested we pass the schema through by reference to preserve\n // identity for callers that compare via `===`.\n if (typeof schema !== 'object') {\n // The SchemaInput union is object-shaped on every arm; if we ever hit a\n // non-object here, propagate it untouched and let the downstream\n // provider error loudly rather than silently widen.\n return schema\n }\n\n if (forStructuredOutput) {\n // Build a typed view structurally so we don't need a SchemaInput→JSONSchema\n // cast on the transformation path.\n const typedView = toJsonSchema(schema)\n return makeStructuredOutputCompatible(typedView, typedView.required || [])\n }\n\n return schema\n}\n\n/**\n * Validates data against a Standard Schema compliant schema.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Validation result with success status, data or issues\n */\nexport async function validateWithStandardSchema<T>(\n schema: unknown,\n data: unknown,\n): Promise<\n | { success: true; data: T }\n | {\n success: false\n issues: Array<{ message: string; path?: Array<string> | undefined }>\n }\n> {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return { success: true, data: data as T }\n }\n\n const result = await schema['~standard'].validate(data)\n\n if (!result.issues) {\n return { success: true, data: result.value as T }\n }\n\n return {\n success: false,\n issues: result.issues.map((issue) => ({\n message: issue.message || 'Validation failed',\n path: issue.path?.map(String),\n })),\n }\n}\n\n/**\n * Synchronously validates data against a Standard Schema compliant schema.\n * Note: Some Standard Schema implementations may only support async validation.\n * In those cases, this function will throw.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Parsed/validated data\n * @throws Error if validation fails or if the schema only supports async validation\n */\nexport function parseWithStandardSchema<T>(schema: unknown, data: unknown): T {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return data as T\n }\n\n const result = schema['~standard'].validate(data)\n\n // Handle async result (Promise)\n if (result instanceof Promise) {\n throw new Error(\n 'Schema validation returned a Promise. Use validateWithStandardSchema for async validation.',\n )\n }\n // Standard Schema validation returns { value } for success or { issues } for failure\n if (!result.issues) {\n return result.value as T\n }\n\n // invalid validation, throw error with all issues\n const errorMessages = result.issues\n .map((issue) => issue.message || 'Validation failed')\n .join(', ')\n throw new Error(`Validation failed: ${errorMessages}`)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema-converter.js","sources":["../../../../../src/activities/chat/tools/schema-converter.ts"],"sourcesContent":["import type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from '@standard-schema/spec'\nimport type { JSONSchema, SchemaInput } from '../../../types'\n\n/**\n * Build a JSONSchema object from any plain key/value source. The `JSONSchema`\n * interface's `[key: string]: any` index signature makes every property\n * assignable through bracket access without a type cast — copying keys here\n * lets us narrow either `Record<string, unknown>` (returned by\n * `~standard.jsonSchema.input()`) or a `JSONSchema` (from the SchemaInput\n * pass-through arm) into the typed view used by the rest of this module.\n *\n * Accepts `object` so callers don't need a cast when narrowing from union\n * types like `SchemaInput`.\n */\nfunction toJsonSchema(obj: object): JSONSchema {\n const result: JSONSchema = {}\n for (const [key, value] of Object.entries(obj)) {\n if (key === '$schema') continue // not needed by LLM providers\n result[key] = value\n }\n return result\n}\n\n/**\n * Check if a value is a Standard JSON Schema compliant schema.\n * Standard JSON Schema compliant libraries (Zod v4+, ArkType, Valibot with toStandardJsonSchema, etc.)\n * implement the '~standard' property with jsonSchema converter methods.\n */\nexport function isStandardJSONSchema(\n schema: unknown,\n): schema is StandardJSONSchemaV1 {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n '~standard' in schema &&\n typeof (schema as StandardJSONSchemaV1)['~standard'] === 'object' &&\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for caller-provided unknown; type assertion narrows but doesn't validate the wire payload\n (schema as StandardJSONSchemaV1)['~standard'].version === 1 &&\n typeof (schema as StandardJSONSchemaV1)['~standard'].jsonSchema ===\n 'object' &&\n typeof (schema as StandardJSONSchemaV1)['~standard'].jsonSchema.input ===\n 'function'\n )\n}\n\n/**\n * Check if a value is a Standard Schema compliant schema (for validation).\n * Standard Schema compliant libraries implement the '~standard' property with a validate function.\n */\nexport function isStandardSchema(schema: unknown): schema is StandardSchemaV1 {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n '~standard' in schema &&\n typeof schema['~standard'] === 'object' &&\n schema['~standard'] !== null &&\n 'version' in schema['~standard'] &&\n schema['~standard'].version === 1 &&\n 'validate' in schema['~standard'] &&\n typeof schema['~standard'].validate === 'function'\n )\n}\n\n/**\n * Transform a JSON schema to be compatible with OpenAI's structured output requirements.\n * OpenAI requires:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for objects\n *\n * @param schema - JSON schema to transform\n * @param originalRequired - Original required array (to know which fields were optional)\n * @returns Transformed schema compatible with OpenAI structured output\n */\nfunction makeStructuredOutputCompatible(\n schema: JSONSchema,\n originalRequired: Array<string> = [],\n): JSONSchema {\n const result: JSONSchema = { ...schema }\n\n // Handle object types\n if (result.type === 'object' && result.properties) {\n const properties: Record<string, JSONSchema> = { ...result.properties }\n const allPropertyNames = Object.keys(properties)\n\n // Transform each property\n for (const propName of allPropertyNames) {\n const prop = properties[propName]\n if (!prop) continue\n const wasOptional = !originalRequired.includes(propName)\n\n // Recursively transform nested objects/arrays\n if (prop.type === 'object' && prop.properties) {\n const transformed = makeStructuredOutputCompatible(\n prop,\n prop.required || [],\n )\n properties[propName] = wasOptional\n ? { ...transformed, type: ['object', 'null'] }\n : transformed\n } else if (prop.type === 'array' && prop.items) {\n const items = Array.isArray(prop.items) ? prop.items[0] : prop.items\n const transformed: JSONSchema = {\n ...prop,\n items: items\n ? makeStructuredOutputCompatible(items, items.required || [])\n : prop.items,\n }\n properties[propName] = wasOptional\n ? { ...transformed, type: ['array', 'null'] }\n : transformed\n } else if (wasOptional) {\n // Make optional fields nullable by adding null to the type\n if (prop.type && !Array.isArray(prop.type)) {\n properties[propName] = {\n ...prop,\n type: [prop.type, 'null'],\n }\n } else if (Array.isArray(prop.type) && !prop.type.includes('null')) {\n properties[propName] = {\n ...prop,\n type: [...prop.type, 'null'],\n }\n }\n }\n }\n\n result.properties = properties\n // ALL properties must be required for OpenAI structured output\n result.required = allPropertyNames\n // additionalProperties must be false\n result.additionalProperties = false\n }\n\n // Handle array types with object items\n if (result.type === 'array' && result.items) {\n const items = Array.isArray(result.items) ? result.items[0] : result.items\n if (items) {\n result.items = makeStructuredOutputCompatible(items, items.required || [])\n }\n }\n\n return result\n}\n\n/**\n * Options for schema conversion\n */\nexport interface ConvertSchemaOptions {\n /**\n * When true, transforms the schema to be compatible with OpenAI's structured output requirements:\n * - All properties are added to the `required` array\n * - Optional fields get null added to their type union\n * - additionalProperties is set to false for all objects\n *\n * @default false\n */\n forStructuredOutput?: boolean\n}\n\n/**\n * Converts a Standard JSON Schema compliant schema or plain JSONSchema to JSON Schema format\n * compatible with LLM providers.\n *\n * Supports any schema library that implements the Standard JSON Schema spec (v1):\n * - Zod v4+ (natively supports StandardJSONSchemaV1)\n * - ArkType (natively supports StandardJSONSchemaV1)\n * - Valibot (via `toStandardJsonSchema()` from `@valibot/to-json-schema`)\n *\n * If the input is already a plain JSONSchema object, it is returned as-is.\n *\n * @param schema - Standard JSON Schema compliant schema or plain JSONSchema object to convert\n * @param options - Conversion options\n * @returns JSON Schema object that can be sent to LLM providers\n *\n * @example\n * ```typescript\n * // Using Zod v4+ (natively supports Standard JSON Schema)\n * import * as z from 'zod';\n *\n * const zodSchema = z.object({\n * location: z.string().describe('City name'),\n * unit: z.enum(['celsius', 'fahrenheit']).optional()\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(zodSchema);\n *\n * @example\n * // Using ArkType (natively supports Standard JSON Schema)\n * import { type } from 'arktype';\n *\n * const arkSchema = type({\n * location: 'string',\n * unit: \"'celsius' | 'fahrenheit'\"\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(arkSchema);\n *\n * @example\n * // Using Valibot (via toStandardJsonSchema)\n * import * as v from 'valibot';\n * import { toStandardJsonSchema } from '@valibot/to-json-schema';\n *\n * const valibotSchema = toStandardJsonSchema(v.object({\n * location: v.string(),\n * unit: v.optional(v.picklist(['celsius', 'fahrenheit']))\n * }));\n *\n * const jsonSchema = convertSchemaToJsonSchema(valibotSchema);\n *\n * @example\n * // Using JSONSchema directly (passes through unchanged)\n * const rawSchema = {\n * type: 'object',\n * properties: { location: { type: 'string' } },\n * required: ['location']\n * };\n * const result = convertSchemaToJsonSchema(rawSchema);\n * ```\n */\nexport function convertSchemaToJsonSchema(\n schema: SchemaInput | undefined,\n options: ConvertSchemaOptions = {},\n): JSONSchema | undefined {\n if (!schema) return undefined\n\n const { forStructuredOutput = false } = options\n\n // If it's a Standard JSON Schema compliant schema, use the standard interface\n if (isStandardJSONSchema(schema)) {\n const jsonSchema = schema['~standard'].jsonSchema.input({\n target: 'draft-07',\n })\n\n // Rebuild structurally so the typed JSONSchema view is acquired without\n // a `Record<string, unknown> as JSONSchema` cast; `toJsonSchema()` also\n // drops the `$schema` key which LLM providers don't need.\n let result: JSONSchema = toJsonSchema(jsonSchema)\n\n // Ensure object schemas always have type: \"object\"\n // If it has properties (even empty), it should be an object type\n if ('properties' in result && !result.type) {\n result.type = 'object'\n }\n\n // Ensure properties exists for object types (even if empty)\n if (result.type === 'object' && !('properties' in result)) {\n result.properties = {}\n }\n\n // Ensure required exists for object types (even if empty array)\n if (result.type === 'object' && !('required' in result)) {\n result.required = []\n }\n\n // Apply structured output transformation if requested\n if (forStructuredOutput) {\n result = makeStructuredOutputCompatible(result, result.required || [])\n }\n\n return result\n }\n\n // Detect Standard Schema validators (Zod, ArkType, Valibot, …) that don't\n // expose a `~standard.jsonSchema` converter. These would otherwise fall\n // through to the JSONSchema pass-through below and ship `{ '~standard': … }`\n // straight to the LLM provider, producing an opaque downstream error. Fail\n // fast with actionable guidance instead.\n if (isStandardSchema(schema)) {\n throw new Error(\n 'Schema is a Standard Schema validator but does not expose a JSON Schema ' +\n 'converter on `~standard.jsonSchema`. Use Zod v4.2+, ArkType v2.1.28+, ' +\n 'or wrap a Valibot schema with `toStandardJsonSchema()` from ' +\n '`@valibot/to-json-schema` before passing it as `outputSchema`.',\n )\n }\n\n // If it's not a Standard JSON Schema, assume it's already a JSONSchema and pass through\n // Still apply structured output transformation if requested\n\n // At this branch, `schema` is the plain `JSONSchema` arm of `SchemaInput`\n // (the two `~standard` arms were handled above). When no transformation\n // is requested we pass the schema through by reference to preserve\n // identity for callers that compare via `===`.\n if (typeof schema !== 'object') {\n // The SchemaInput union is object-shaped on every arm; if we ever hit a\n // non-object here, propagate it untouched and let the downstream\n // provider error loudly rather than silently widen.\n return schema\n }\n\n if (forStructuredOutput) {\n // Build a typed view structurally so we don't need a SchemaInput→JSONSchema\n // cast on the transformation path.\n const typedView = toJsonSchema(schema)\n return makeStructuredOutputCompatible(typedView, typedView.required || [])\n }\n\n return schema\n}\n\n/**\n * Validates data against a Standard Schema compliant schema.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Validation result with success status, data or issues\n */\nexport async function validateWithStandardSchema<T>(\n schema: unknown,\n data: unknown,\n): Promise<\n | { success: true; data: T }\n | {\n success: false\n issues: Array<{ message: string; path?: Array<string> | undefined }>\n }\n> {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return { success: true, data: data as T }\n }\n\n const result = await schema['~standard'].validate(data)\n\n if (!result.issues) {\n return { success: true, data: result.value as T }\n }\n\n return {\n success: false,\n issues: result.issues.map((issue) => ({\n message: issue.message || 'Validation failed',\n path: issue.path?.map(String),\n })),\n }\n}\n\n/**\n * Error thrown when Standard Schema validation fails. Carries the original\n * `issues` array so consumers (middleware `onError`, callers catching from\n * `chat({ outputSchema })`) can programmatically inspect each failure.\n */\nexport class StandardSchemaValidationError extends Error {\n override readonly name = 'StandardSchemaValidationError'\n readonly issues: ReadonlyArray<StandardSchemaV1.Issue>\n\n constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n super(\n `Validation failed: ${issues\n .map((i) => i.message || 'Validation failed')\n .join(', ')}`,\n )\n this.issues = issues\n }\n}\n\n/**\n * Synchronously validates data against a Standard Schema compliant schema.\n * Note: Some Standard Schema implementations may only support async validation.\n * In those cases, this function will throw.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Parsed/validated data\n * @throws StandardSchemaValidationError if validation fails; Error if the\n * schema only supports async validation.\n */\nexport function parseWithStandardSchema<T>(schema: unknown, data: unknown): T {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return data as T\n }\n\n const result = schema['~standard'].validate(data)\n\n // Handle async result (Promise)\n if (result instanceof Promise) {\n throw new Error(\n 'Schema validation returned a Promise. Use validateWithStandardSchema for async validation.',\n )\n }\n // Standard Schema validation returns { value } for success or { issues } for failure\n if (!result.issues) {\n return result.value as T\n }\n\n throw new StandardSchemaValidationError(result.issues)\n}\n"],"names":[],"mappings":"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"}
|