@tanstack/ai 0.36.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/adapter.d.ts +163 -0
- package/dist/esm/activities/chat/adapter.js +17 -0
- package/dist/esm/activities/chat/adapter.js.map +1 -0
- package/dist/esm/activities/chat/agent-loop-strategies.d.ts +59 -0
- package/dist/esm/activities/chat/agent-loop-strategies.js +23 -0
- package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +270 -0
- package/dist/esm/activities/chat/index.js +1724 -0
- package/dist/esm/activities/chat/index.js.map +1 -0
- package/dist/esm/activities/chat/mcp/manager.d.ts +25 -0
- package/dist/esm/activities/chat/mcp/manager.js +71 -0
- package/dist/esm/activities/chat/mcp/manager.js.map +1 -0
- package/dist/esm/activities/chat/mcp/types.d.ts +56 -0
- package/dist/esm/activities/chat/messages.d.ts +78 -0
- package/dist/esm/activities/chat/messages.js +374 -0
- package/dist/esm/activities/chat/messages.js.map +1 -0
- package/dist/esm/activities/chat/middleware/builder.d.ts +46 -0
- package/dist/esm/activities/chat/middleware/builder.js +17 -0
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -0
- package/dist/esm/activities/chat/middleware/capabilities.d.ts +93 -0
- package/dist/esm/activities/chat/middleware/capabilities.js +45 -0
- package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -0
- package/dist/esm/activities/chat/middleware/compose.d.ts +87 -0
- package/dist/esm/activities/chat/middleware/compose.js +510 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -0
- package/dist/esm/activities/chat/middleware/define.d.ts +20 -0
- package/dist/esm/activities/chat/middleware/define.js +7 -0
- package/dist/esm/activities/chat/middleware/define.js.map +1 -0
- package/dist/esm/activities/chat/middleware/index.d.ts +10 -0
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.d.ts +89 -0
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +76 -0
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -0
- package/dist/esm/activities/chat/middleware/types.d.ts +405 -0
- package/dist/esm/activities/chat/middleware/validate.d.ts +19 -0
- package/dist/esm/activities/chat/middleware/validate.js +30 -0
- package/dist/esm/activities/chat/middleware/validate.js.map +1 -0
- package/dist/esm/activities/chat/runtime-context-types.d.ts +43 -0
- package/dist/esm/activities/chat/stream/index.d.ts +11 -0
- package/dist/esm/activities/chat/stream/json-parser.d.ts +38 -0
- package/dist/esm/activities/chat/stream/json-parser.js +28 -0
- package/dist/esm/activities/chat/stream/json-parser.js.map +1 -0
- package/dist/esm/activities/chat/stream/message-updaters.d.ts +81 -0
- package/dist/esm/activities/chat/stream/message-updaters.js +253 -0
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -0
- package/dist/esm/activities/chat/stream/processor.d.ts +439 -0
- package/dist/esm/activities/chat/stream/processor.js +1426 -0
- package/dist/esm/activities/chat/stream/processor.js.map +1 -0
- package/dist/esm/activities/chat/stream/strategies.d.ts +43 -0
- package/dist/esm/activities/chat/stream/strategies.js +54 -0
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -0
- package/dist/esm/activities/chat/stream/types.d.ts +90 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.d.ts +82 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +194 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -0
- package/dist/esm/activities/chat/tools/lazy-tools.d.ts +15 -0
- package/dist/esm/activities/chat/tools/lazy-tools.js +16 -0
- package/dist/esm/activities/chat/tools/lazy-tools.js.map +1 -0
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +140 -0
- package/dist/esm/activities/chat/tools/schema-converter.js +167 -0
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -0
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +140 -0
- package/dist/esm/activities/chat/tools/tool-calls.js +512 -0
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -0
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +135 -0
- package/dist/esm/activities/chat/tools/tool-definition.js +25 -0
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -0
- package/dist/esm/activities/error-payload.d.ts +30 -0
- package/dist/esm/activities/error-payload.js +54 -0
- package/dist/esm/activities/error-payload.js.map +1 -0
- package/dist/esm/activities/generateAudio/adapter.d.ts +62 -0
- package/dist/esm/activities/generateAudio/adapter.js +16 -0
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -0
- package/dist/esm/activities/generateAudio/index.d.ts +85 -0
- package/dist/esm/activities/generateAudio/index.js +114 -0
- package/dist/esm/activities/generateAudio/index.js.map +1 -0
- package/dist/esm/activities/generateImage/adapter.d.ts +78 -0
- package/dist/esm/activities/generateImage/adapter.js +16 -0
- package/dist/esm/activities/generateImage/adapter.js.map +1 -0
- package/dist/esm/activities/generateImage/index.d.ts +134 -0
- package/dist/esm/activities/generateImage/index.js +121 -0
- package/dist/esm/activities/generateImage/index.js.map +1 -0
- package/dist/esm/activities/generateSpeech/adapter.d.ts +62 -0
- package/dist/esm/activities/generateSpeech/adapter.js +16 -0
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -0
- package/dist/esm/activities/generateSpeech/index.d.ts +96 -0
- package/dist/esm/activities/generateSpeech/index.js +119 -0
- package/dist/esm/activities/generateSpeech/index.js.map +1 -0
- package/dist/esm/activities/generateTranscription/adapter.d.ts +62 -0
- package/dist/esm/activities/generateTranscription/adapter.js +16 -0
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -0
- package/dist/esm/activities/generateTranscription/index.d.ts +109 -0
- package/dist/esm/activities/generateTranscription/index.js +109 -0
- package/dist/esm/activities/generateTranscription/index.js.map +1 -0
- package/dist/esm/activities/generateVideo/adapter.d.ts +145 -0
- package/dist/esm/activities/generateVideo/adapter.js +30 -0
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -0
- package/dist/esm/activities/generateVideo/index.d.ts +223 -0
- package/dist/esm/activities/generateVideo/index.js +295 -0
- package/dist/esm/activities/generateVideo/index.js.map +1 -0
- package/dist/esm/activities/generateVideo/snap.d.ts +14 -0
- package/dist/esm/activities/generateVideo/snap.js +54 -0
- package/dist/esm/activities/generateVideo/snap.js.map +1 -0
- package/dist/esm/activities/index.d.ts +27 -0
- package/dist/esm/activities/index.js +43 -0
- package/dist/esm/activities/index.js.map +1 -0
- package/dist/esm/activities/middleware/index.d.ts +2 -0
- package/dist/esm/activities/middleware/run.d.ts +20 -0
- package/dist/esm/activities/middleware/run.js +42 -0
- package/dist/esm/activities/middleware/run.js.map +1 -0
- package/dist/esm/activities/middleware/types.d.ts +118 -0
- package/dist/esm/activities/stream-generation-result.d.ts +15 -0
- package/dist/esm/activities/stream-generation-result.js +49 -0
- package/dist/esm/activities/stream-generation-result.js.map +1 -0
- package/dist/esm/activities/summarize/adapter.d.ts +74 -0
- package/dist/esm/activities/summarize/adapter.js +16 -0
- package/dist/esm/activities/summarize/adapter.js.map +1 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +45 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js +212 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -0
- package/dist/esm/activities/summarize/index.d.ts +108 -0
- package/dist/esm/activities/summarize/index.js +113 -0
- package/dist/esm/activities/summarize/index.js.map +1 -0
- package/dist/esm/adapter-internals.d.ts +5 -0
- package/dist/esm/adapter-internals.js +10 -0
- package/dist/esm/adapter-internals.js.map +1 -0
- package/dist/esm/client.d.ts +44 -0
- package/dist/esm/client.js +67 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/extend-adapter.d.ts +152 -0
- package/dist/esm/extend-adapter.js +21 -0
- package/dist/esm/extend-adapter.js.map +1 -0
- package/dist/esm/index.d.ts +45 -0
- package/dist/esm/index.js +109 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logger/console-logger.d.ts +29 -0
- package/dist/esm/logger/console-logger.js +83 -0
- package/dist/esm/logger/console-logger.js.map +1 -0
- package/dist/esm/logger/internal-logger.d.ts +41 -0
- package/dist/esm/logger/internal-logger.js +86 -0
- package/dist/esm/logger/internal-logger.js.map +1 -0
- package/dist/esm/logger/resolve.d.ts +14 -0
- package/dist/esm/logger/resolve.js +54 -0
- package/dist/esm/logger/resolve.js.map +1 -0
- package/dist/esm/logger/types.d.ts +75 -0
- package/dist/esm/middlewares/content-guard.d.ts +77 -0
- package/dist/esm/middlewares/content-guard.js +156 -0
- package/dist/esm/middlewares/content-guard.js.map +1 -0
- package/dist/esm/middlewares/index.d.ts +2 -0
- package/dist/esm/middlewares/index.js +7 -0
- package/dist/esm/middlewares/index.js.map +1 -0
- package/dist/esm/middlewares/otel.d.ts +81 -0
- package/dist/esm/middlewares/otel.js +745 -0
- package/dist/esm/middlewares/otel.js.map +1 -0
- package/dist/esm/middlewares/tool-cache.d.ts +1 -0
- package/dist/esm/middlewares/usage-attributes.d.ts +24 -0
- package/dist/esm/middlewares/usage-attributes.js +43 -0
- package/dist/esm/middlewares/usage-attributes.js.map +1 -0
- package/dist/esm/realtime/index.d.ts +28 -0
- package/dist/esm/realtime/index.js +8 -0
- package/dist/esm/realtime/index.js.map +1 -0
- package/dist/esm/realtime/types.d.ts +282 -0
- package/dist/esm/stream-to-response.d.ts +102 -0
- package/dist/esm/stream-to-response.js +121 -0
- package/dist/esm/stream-to-response.js.map +1 -0
- package/dist/esm/strip-to-spec-middleware.d.ts +18 -0
- package/dist/esm/strip-to-spec-middleware.js +20 -0
- package/dist/esm/strip-to-spec-middleware.js.map +1 -0
- package/dist/esm/system-prompts.d.ts +66 -0
- package/dist/esm/system-prompts.js +23 -0
- package/dist/esm/system-prompts.js.map +1 -0
- package/dist/esm/tool-registry.d.ts +81 -0
- package/dist/esm/tool-registry.js +49 -0
- package/dist/esm/tool-registry.js.map +1 -0
- package/dist/esm/tools/provider-tool.d.ts +30 -0
- package/dist/esm/tools/provider-tool.js +7 -0
- package/dist/esm/tools/provider-tool.js.map +1 -0
- package/dist/esm/types.d.ts +1594 -0
- package/dist/esm/utilities/ag-ui-wire.d.ts +44 -0
- package/dist/esm/utilities/ag-ui-wire.js +107 -0
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -0
- package/dist/esm/utilities/chat-params.d.ts +85 -0
- package/dist/esm/utilities/chat-params.js +100 -0
- package/dist/esm/utilities/chat-params.js.map +1 -0
- package/dist/esm/utilities/errors.d.ts +13 -0
- package/dist/esm/utilities/errors.js +22 -0
- package/dist/esm/utilities/errors.js.map +1 -0
- package/dist/esm/utilities/media-prompt.d.ts +35 -0
- package/dist/esm/utilities/media-prompt.js +43 -0
- package/dist/esm/utilities/media-prompt.js.map +1 -0
- package/dist/esm/utilities/numbers.d.ts +8 -0
- package/dist/esm/utilities/numbers.js +12 -0
- package/dist/esm/utilities/numbers.js.map +1 -0
- package/dist/esm/utilities/sampling-keys.d.ts +20 -0
- package/dist/esm/utilities/sampling-keys.js +20 -0
- package/dist/esm/utilities/sampling-keys.js.map +1 -0
- package/dist/esm/utilities/tool-result.d.ts +21 -0
- package/dist/esm/utilities/tool-result.js +37 -0
- package/dist/esm/utilities/tool-result.js.map +1 -0
- package/dist/esm/utilities/usage.d.ts +31 -0
- package/dist/esm/utilities/usage.js +11 -0
- package/dist/esm/utilities/usage.js.map +1 -0
- package/dist/esm/utils.d.ts +17 -0
- package/dist/esm/utils.js +20 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +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 {\n aguiSnapshotMessageToUIMessage,\n generateMessageId,\n uiMessageToModelMessages,\n} from '../messages.js'\nimport { normalizeToolResult } from '../../../utilities/tool-result'\nimport { defaultJSONParser } from './json-parser'\nimport {\n appendStructuredOutputDelta,\n completeStructuredOutputPart,\n errorStructuredOutputPart,\n updateTextPart,\n updateThinkingPart,\n updateToolCallApproval,\n updateToolCallApprovalResponse,\n updateToolCallPart,\n updateToolCallWithOutput,\n updateToolResultPart,\n} from './message-updaters'\nimport { ImmediateStrategy } from './strategies'\nimport type {\n ChunkRecording,\n ChunkStrategy,\n InternalToolCallState,\n MessageStreamState,\n ProcessorResult,\n ProcessorState,\n ToolCallState,\n ToolResultState,\n} from './types'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StreamChunk,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n} from '../../../types'\n\n/**\n * Events emitted by the StreamProcessor\n */\nexport interface StreamProcessorEvents {\n // State events - full array on any change\n onMessagesChange?: (messages: Array<UIMessage>) => void\n\n // Lifecycle events\n onStreamStart?: () => void\n onStreamEnd?: (message: UIMessage) => void\n onError?: (error: Error) => void\n\n // Interaction events - client must handle these\n onToolCall?: (args: {\n toolCallId: string\n toolName: string\n input: any\n }) => void\n onApprovalRequest?: (args: {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n }) => void\n\n // Custom events from server-side tools\n onCustomEvent?: (\n eventType: string,\n data: unknown,\n context: { toolCallId?: string },\n ) => void\n\n // Granular events for UI optimization (character-by-character, state tracking)\n onTextUpdate?: (messageId: string, content: string) => void\n onToolCallStateChange?: (\n messageId: string,\n toolCallId: string,\n state: ToolCallState,\n args: string,\n ) => void\n onThinkingUpdate?: (\n messageId: string,\n stepId: string,\n content: string,\n ) => void\n onStructuredOutputChange?: (args: {\n phase: 'start' | 'update' | 'complete' | 'error'\n messageId: string\n status: 'streaming' | 'complete' | 'error'\n raw: string\n partial?: unknown\n data?: unknown\n reasoning?: string\n errorMessage?: string\n delta?: string\n }) => void\n}\n\n/**\n * Options for StreamProcessor\n */\nexport interface StreamProcessorOptions {\n chunkStrategy?: ChunkStrategy\n /** Event-driven handlers */\n events?: StreamProcessorEvents\n jsonParser?: {\n parse: (jsonString: string) => any\n }\n /** Enable recording for replay testing */\n recording?: boolean\n /** Initial messages to populate the processor */\n initialMessages?: Array<UIMessage>\n}\n\nconst STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE = 12\n\n/**\n * StreamProcessor - State machine for processing AI response streams\n *\n * Manages the full UIMessage[] conversation and emits events on changes.\n * Trusts the adapter contract: adapters emit clean AG-UI events in the\n * correct order.\n *\n * State tracking:\n * - Full message array\n * - Per-message stream state (text, tool calls, thinking)\n * - Multiple concurrent message streams\n * - Tool call completion via TOOL_CALL_END events\n *\n * @see docs/chat-architecture.md#streamprocessor-internal-state — State field reference\n * @see docs/chat-architecture.md#adapter-contract — What this class expects from adapters\n */\nexport class StreamProcessor {\n private readonly chunkStrategy: ChunkStrategy\n private readonly events: StreamProcessorEvents\n private readonly jsonParser: { parse: (jsonString: string) => any }\n private recordingEnabled: boolean\n\n // Message state\n private messages: Array<UIMessage> = []\n\n // Per-message stream state\n private readonly messageStates: Map<string, MessageStreamState> = new Map()\n private readonly activeMessageIds: Set<string> = new Set()\n private readonly toolCallToMessage: Map<string, string> = new Map()\n private pendingManualMessageId: string | null = null\n private pendingThinkingStepId: string | null = null\n\n private readonly structuredMessageIds: Set<string> = new Set()\n private readonly structuredOutputUpdateBatches = new Map<\n string,\n {\n delta: string\n chunkCount: number\n }\n >()\n\n // Run tracking (for concurrent run safety)\n private readonly activeRuns = new Set<string>()\n\n // Shared stream state\n private finishReason: string | null = null\n private hasError = false\n private isDone = false\n\n // Recording\n private recording: ChunkRecording | null = null\n private recordingStartTime = 0\n\n constructor(options: StreamProcessorOptions = {}) {\n this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy()\n this.events = options.events || {}\n this.jsonParser = options.jsonParser || defaultJSONParser\n this.recordingEnabled = options.recording ?? false\n\n // Initialize with provided messages\n if (options.initialMessages) {\n this.messages = [...options.initialMessages]\n }\n }\n\n // ============================================\n // Message Management Methods\n // ============================================\n\n /**\n * Set the messages array (e.g., from persisted state)\n */\n setMessages(messages: Array<UIMessage>): void {\n this.messages = [...messages]\n this.emitMessagesChange()\n }\n\n /**\n * Add a user message to the conversation.\n * Supports both simple string content and multimodal content arrays.\n *\n * @param content - The message content (string or array of content parts)\n * @param id - Optional custom message ID (generated if not provided)\n * @returns The created UIMessage\n *\n * @example\n * ```ts\n * // Simple text message\n * processor.addUserMessage('Hello!')\n *\n * // Multimodal message with image\n * processor.addUserMessage([\n * { type: 'text', content: 'What is in this image?' },\n * { type: 'image', source: { type: 'url', value: 'https://example.com/photo.jpg' } }\n * ])\n *\n * // With custom ID\n * processor.addUserMessage('Hello!', 'custom-id-123')\n * ```\n */\n addUserMessage(content: string | Array<ContentPart>, id?: string): UIMessage {\n // Convert content to message parts\n const parts: Array<MessagePart> =\n typeof content === 'string'\n ? [{ type: 'text', content }]\n : content.map((part) => {\n // ContentPart types (text, image, audio, video, document) are compatible with MessagePart\n return part\n })\n\n const userMessage: UIMessage = {\n id: id ?? generateMessageId(),\n role: 'user',\n parts,\n createdAt: new Date(),\n }\n\n this.messages = [...this.messages, userMessage]\n this.emitMessagesChange()\n\n return userMessage\n }\n\n /**\n * Prepare for a new assistant message stream.\n * Does NOT create the message immediately -- the message is created lazily\n * when the first content-bearing chunk arrives via ensureAssistantMessage().\n * This prevents empty assistant messages from flickering in the UI when\n * auto-continuation produces no content.\n */\n prepareAssistantMessage(): void {\n // Reset stream state for new message\n this.resetStreamState()\n }\n\n /**\n * @deprecated Use prepareAssistantMessage() instead. This eagerly creates\n * an assistant message which can cause empty message flicker.\n */\n startAssistantMessage(messageId?: string): string {\n this.prepareAssistantMessage()\n const { messageId: id } = this.ensureAssistantMessage(messageId)\n this.pendingManualMessageId = id\n return id\n }\n\n /**\n * Get the current assistant message ID (if one has been created).\n * Returns null if prepareAssistantMessage() was called but no content\n * has arrived yet.\n */\n getCurrentAssistantMessageId(): string | null {\n // Scan all message states (not just active) for the last assistant.\n // After finalizeStream() clears activeMessageIds, messageStates retains entries.\n // After reset() / resetStreamState(), messageStates is cleared → returns null.\n let lastId: string | null = null\n for (const [id, state] of this.messageStates) {\n if (state.role === 'assistant') {\n lastId = id\n }\n }\n return lastId\n }\n\n /**\n * Add a tool result (called by client after handling onToolCall)\n */\n addToolResult(toolCallId: string, output: any, error?: string): void {\n // Find the message containing this tool call\n const messageWithToolCall = this.messages.find((msg) =>\n msg.parts.some(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n ),\n )\n\n if (!messageWithToolCall) {\n console.warn(\n `[StreamProcessor] Could not find message with tool call ${toolCallId}`,\n )\n return\n }\n\n // Step 1: Update the tool-call part's output field (for UI rendering)\n let updatedMessages = updateToolCallWithOutput(\n this.messages,\n toolCallId,\n output,\n error ? 'error' : undefined,\n error,\n )\n\n // Step 2: Create a tool-result part (for LLM conversation history)\n const content = normalizeToolResult(output)\n const toolResultState: ToolResultState = error ? 'error' : 'complete'\n\n updatedMessages = updateToolResultPart(\n updatedMessages,\n messageWithToolCall.id,\n toolCallId,\n content,\n toolResultState,\n error,\n )\n\n this.messages = updatedMessages\n this.emitMessagesChange()\n }\n\n /**\n * Add an approval response (called by client after handling onApprovalRequest)\n */\n addToolApprovalResponse(approvalId: string, approved: boolean): void {\n this.messages = updateToolCallApprovalResponse(\n this.messages,\n approvalId,\n approved,\n )\n this.emitMessagesChange()\n }\n\n /**\n * Get the conversation as ModelMessages (for sending to LLM)\n */\n toModelMessages(): Array<ModelMessage> {\n const modelMessages: Array<ModelMessage> = []\n for (const msg of this.messages) {\n modelMessages.push(...uiMessageToModelMessages(msg))\n }\n return modelMessages\n }\n\n /**\n * Get current messages\n */\n getMessages(): Array<UIMessage> {\n return this.messages\n }\n\n /**\n * Check if all tool calls in the last assistant message are complete\n * Useful for auto-continue logic\n */\n areAllToolsComplete(): boolean {\n const lastAssistant = this.messages.findLast(\n (m: UIMessage) => m.role === 'assistant',\n )\n\n if (!lastAssistant) return true\n\n const toolParts = lastAssistant.parts.filter(\n (p): p is ToolCallPart => p.type === 'tool-call',\n )\n\n if (toolParts.length === 0) return true\n\n // Get tool result parts to check for server tool completion\n const toolResultIds = new Set(\n lastAssistant.parts\n .filter((p): p is ToolResultPart => p.type === 'tool-result')\n .map((p) => p.toolCallId),\n )\n\n // All tool calls must be in a terminal state\n // A tool call is complete if:\n // 1. It was approved/denied (approval-responded state)\n // 2. It has an output field set (client tool completed via addToolResult)\n // 3. It has a corresponding tool-result part (server tool completed)\n return toolParts.every(\n (part) =>\n part.state === 'complete' ||\n part.state === 'approval-responded' ||\n (part.output !== undefined && !part.approval) ||\n toolResultIds.has(part.id),\n )\n }\n\n /**\n * Remove messages after a certain index (for reload/retry)\n */\n removeMessagesAfter(index: number): void {\n const keptIds = new Set(this.messages.slice(0, index + 1).map((m) => m.id))\n // Drop routing state for messages that no longer exist; otherwise a\n // resumed stream (`reload()` or a server that reuses messageIds across\n // runs) could land deltas / tool args on stale map entries and corrupt\n // the new assistant message's parts. Mirror the four routing maps that\n // key on messageId: structuredMessageIds (custom-event routing),\n // messageStates (per-message stream state), toolCallToMessage (tool\n // args → message), and activeMessageIds (finalize / completeAllToolCalls\n // iteration targets — must not include phantoms).\n for (const id of this.structuredMessageIds) {\n if (!keptIds.has(id)) this.structuredMessageIds.delete(id)\n }\n for (const id of this.structuredOutputUpdateBatches.keys()) {\n if (!keptIds.has(id)) this.structuredOutputUpdateBatches.delete(id)\n }\n for (const id of this.messageStates.keys()) {\n if (!keptIds.has(id)) this.messageStates.delete(id)\n }\n for (const [toolCallId, msgId] of this.toolCallToMessage) {\n if (!keptIds.has(msgId)) this.toolCallToMessage.delete(toolCallId)\n }\n for (const id of this.activeMessageIds) {\n if (!keptIds.has(id)) this.activeMessageIds.delete(id)\n }\n this.messages = this.messages.slice(0, index + 1)\n this.emitMessagesChange()\n }\n\n /**\n * Clear all messages\n */\n clearMessages(): void {\n this.messages = []\n this.messageStates.clear()\n this.activeMessageIds.clear()\n this.toolCallToMessage.clear()\n this.structuredMessageIds.clear()\n this.structuredOutputUpdateBatches.clear()\n this.pendingManualMessageId = null\n this.emitMessagesChange()\n }\n\n // ============================================\n // Stream Processing Methods\n // ============================================\n\n /**\n * Process a stream and emit events through handlers\n */\n async process(stream: AsyncIterable<any>): Promise<ProcessorResult> {\n // Reset stream state (but keep messages)\n this.resetStreamState()\n\n // Start recording if enabled\n if (this.recordingEnabled) {\n this.startRecording()\n }\n\n // Process each chunk\n for await (const chunk of stream) {\n this.processChunk(chunk)\n }\n\n // Stream ended - finalize everything\n this.finalizeStream()\n\n // Finalize recording\n if (this.recording) {\n this.recording.result = this.getResult()\n }\n\n return this.getResult()\n }\n\n /**\n * Process a single chunk from the stream.\n *\n * Central dispatch for all AG-UI events. Each event type maps to a specific\n * handler. Events not listed in the switch are intentionally ignored\n * (STEP_STARTED, STATE_SNAPSHOT, STATE_DELTA).\n *\n * @see docs/chat-architecture.md#adapter-contract — Expected event types and ordering\n */\n processChunk(chunk: StreamChunk): void {\n // Record chunk if enabled\n if (this.recording) {\n this.recording.chunks.push({\n chunk,\n timestamp: Date.now(),\n index: this.recording.chunks.length,\n })\n }\n\n // Cast needed: @ag-ui/core Zod passthrough types add `& { [k: string]: unknown }`\n // which prevents TypeScript from narrowing the `type` discriminant in switch.\n const c = chunk\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType enum members vs string-literal case labels; default branch handles untraced events.\n switch (c.type) {\n // AG-UI Events\n case 'TEXT_MESSAGE_START':\n this.handleTextMessageStartEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,\n )\n break\n\n case 'TEXT_MESSAGE_CONTENT':\n this.handleTextMessageContentEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n )\n break\n\n case 'TEXT_MESSAGE_END':\n this.handleTextMessageEndEvent(\n chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_END' }>,\n )\n break\n\n case 'TOOL_CALL_START':\n this.handleToolCallStartEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n )\n break\n\n case 'TOOL_CALL_ARGS':\n this.handleToolCallArgsEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n )\n break\n\n case 'TOOL_CALL_END':\n this.handleToolCallEndEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n )\n break\n\n case 'RUN_FINISHED':\n this.handleRunFinishedEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n )\n break\n\n case 'RUN_ERROR':\n this.handleRunErrorEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n )\n break\n\n case 'STEP_FINISHED':\n this.handleStepFinishedEvent(\n chunk as Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n )\n break\n\n case 'MESSAGES_SNAPSHOT':\n this.handleMessagesSnapshotEvent(\n chunk as Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,\n )\n break\n\n case 'CUSTOM':\n this.handleCustomEvent(\n chunk as Extract<StreamChunk, { type: 'CUSTOM' }>,\n )\n break\n\n case 'RUN_STARTED':\n this.handleRunStartedEvent(\n chunk as Extract<StreamChunk, { type: 'RUN_STARTED' }>,\n )\n break\n\n case 'REASONING_START':\n case 'REASONING_MESSAGE_START':\n case 'REASONING_MESSAGE_END':\n case 'REASONING_END':\n // No special handling needed\n break\n\n case 'REASONING_MESSAGE_CONTENT':\n this.handleReasoningMessageContentEvent(\n chunk as Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,\n )\n break\n\n case 'TOOL_CALL_RESULT':\n this.handleToolCallResultEvent(\n chunk as Extract<StreamChunk, { type: 'TOOL_CALL_RESULT' }>,\n )\n break\n\n case 'STEP_STARTED':\n this.handleStepStartedEvent(\n chunk as Extract<StreamChunk, { type: 'STEP_STARTED' }>,\n )\n break\n\n default:\n // STATE_SNAPSHOT, STATE_DELTA - no special handling needed\n break\n }\n }\n\n // ============================================\n // Per-Message State Helpers\n // ============================================\n\n /**\n * Create a new MessageStreamState for a message\n */\n private createMessageState(\n messageId: string,\n role: 'user' | 'assistant' | 'system',\n ): MessageStreamState {\n const state: MessageStreamState = {\n id: messageId,\n role,\n totalTextContent: '',\n currentSegmentText: '',\n lastEmittedText: '',\n hasSeenReasoningEvents: false,\n thinkingSteps: new Map(),\n thinkingStepSignatures: new Map(),\n thinkingStepOrder: [],\n currentThinkingStepId: null,\n toolCalls: new Map(),\n toolCallOrder: [],\n hasToolCallsSinceTextStart: false,\n isComplete: false,\n }\n this.messageStates.set(messageId, state)\n return state\n }\n\n /**\n * Get the MessageStreamState for a message\n */\n private getMessageState(messageId: string): MessageStreamState | undefined {\n return this.messageStates.get(messageId)\n }\n\n /**\n * Promote a pending stepId from a STEP_STARTED that fired before the\n * assistant message existed onto the given message state, so the next\n * thinking event (STEP_FINISHED or REASONING_MESSAGE_CONTENT) attributes\n * to the correct step.\n */\n private consumePendingThinkingStep(state: MessageStreamState): void {\n if (!this.pendingThinkingStepId) return\n const stepId = this.pendingThinkingStepId\n state.currentThinkingStepId = stepId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n }\n this.pendingThinkingStepId = null\n }\n\n /**\n * Get the most recent active assistant message ID.\n * Used as fallback for events that don't include a messageId.\n */\n private getActiveAssistantMessageId(): string | null {\n // Set iteration is insertion-order; reverse-iterate to search from the end\n const ids = Array.from(this.activeMessageIds).reverse()\n for (const id of ids) {\n const state = this.messageStates.get(id)\n if (state && state.role === 'assistant') {\n return id\n }\n }\n return null\n }\n\n /**\n * Ensure an active assistant message exists, creating one if needed.\n * Used for backward compat when events arrive without prior TEXT_MESSAGE_START.\n *\n * On reconnect/resume, a TEXT_MESSAGE_CONTENT may arrive for a message that\n * already exists in this.messages (e.g. from initialMessages or a prior\n * MESSAGES_SNAPSHOT) but whose transient state was cleared. In that case we\n * hydrate state from the existing message rather than creating a duplicate.\n */\n private ensureAssistantMessage(preferredId?: string): {\n messageId: string\n state: MessageStreamState\n } {\n // Try to find state by preferred ID\n if (preferredId) {\n const state = this.getMessageState(preferredId)\n if (state) return { messageId: preferredId, state }\n }\n\n // Try active assistant message\n const activeId = this.getActiveAssistantMessageId()\n if (activeId) {\n const state = this.getMessageState(activeId)\n if (state) return { messageId: activeId, state }\n }\n\n // Check if a message with preferredId already exists (reconnect/resume case).\n // Hydrate transient state from the existing message instead of duplicating it.\n if (preferredId) {\n const existingMsg = this.messages.find((m) => m.id === preferredId)\n if (existingMsg) {\n const state = this.createMessageState(preferredId, existingMsg.role)\n this.activeMessageIds.add(preferredId)\n\n // Seed segment text from the existing last text part so that\n // incoming deltas append correctly and updateTextPart produces\n // the right content (existing text + new delta).\n const lastPart =\n existingMsg.parts.length > 0\n ? existingMsg.parts[existingMsg.parts.length - 1]\n : null\n if (lastPart && lastPart.type === 'text') {\n state.currentSegmentText = lastPart.content\n state.lastEmittedText = lastPart.content\n state.totalTextContent = lastPart.content\n }\n\n return { messageId: preferredId, state }\n }\n }\n\n // Auto-create an assistant message (backward compat for process() without TEXT_MESSAGE_START)\n const id = preferredId || generateMessageId()\n const assistantMessage: UIMessage = {\n id,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n this.messages = [...this.messages, assistantMessage]\n const state = this.createMessageState(id, 'assistant')\n this.activeMessageIds.add(id)\n this.pendingManualMessageId = id\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n return { messageId: id, state }\n }\n\n // ============================================\n // Event Handlers\n // ============================================\n\n /**\n * Handle TEXT_MESSAGE_START event\n */\n private handleTextMessageStartEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,\n ): void {\n const { messageId, role } = chunk\n\n // Map 'tool' and 'developer' roles to 'assistant' for both UIMessage and MessageStreamState\n // (UIMessage doesn't support 'tool'/'developer' role, and lookups like\n // getActiveAssistantMessageId() check state.role === 'assistant')\n const uiRole: 'system' | 'user' | 'assistant' =\n role === 'user' || role === 'system' ? role : 'assistant'\n\n // Case 1: A manual message was created via startAssistantMessage()\n if (this.pendingManualMessageId) {\n const pendingId = this.pendingManualMessageId\n this.pendingManualMessageId = null\n\n if (pendingId !== messageId) {\n // Update the message's ID in the messages array\n this.messages = this.messages.map((msg) =>\n msg.id === pendingId ? { ...msg, id: messageId } : msg,\n )\n\n // Move state to the new key\n const existingState = this.messageStates.get(pendingId)\n if (existingState) {\n existingState.id = messageId\n this.messageStates.delete(pendingId)\n this.messageStates.set(messageId, existingState)\n }\n\n // Update activeMessageIds\n this.activeMessageIds.delete(pendingId)\n this.activeMessageIds.add(messageId)\n }\n\n // Ensure state exists\n if (!this.messageStates.has(messageId)) {\n this.createMessageState(messageId, uiRole)\n this.activeMessageIds.add(messageId)\n }\n\n this.emitMessagesChange()\n return\n }\n\n // Case 2: Message already exists (dedup)\n const existingMsg = this.messages.find((m) => m.id === messageId)\n if (existingMsg) {\n this.activeMessageIds.add(messageId)\n const existingState = this.messageStates.get(messageId)\n if (!existingState) {\n this.createMessageState(messageId, uiRole)\n } else {\n // If tool calls happened since last text, this TEXT_MESSAGE_START\n // signals a new text segment — reset segment accumulation\n if (existingState.hasToolCallsSinceTextStart) {\n if (\n existingState.currentSegmentText !== existingState.lastEmittedText\n ) {\n this.emitTextUpdateForMessage(messageId)\n }\n existingState.currentSegmentText = ''\n existingState.lastEmittedText = ''\n existingState.hasToolCallsSinceTextStart = false\n }\n }\n return\n }\n\n // Case 3: New message from the stream\n const newMessage: UIMessage = {\n id: messageId,\n role: uiRole,\n parts: [],\n createdAt: new Date(),\n }\n\n this.messages = [...this.messages, newMessage]\n this.createMessageState(messageId, uiRole)\n this.activeMessageIds.add(messageId)\n\n this.events.onStreamStart?.()\n this.emitMessagesChange()\n }\n\n /**\n * Handle TEXT_MESSAGE_END event\n */\n private handleTextMessageEndEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_END' }>,\n ): void {\n const { messageId } = chunk\n const state = this.getMessageState(messageId)\n if (!state) return\n if (state.isComplete) return\n\n // Emit any pending text for this message\n if (state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n\n // Complete all tool calls for this message\n this.completeAllToolCallsForMessage(messageId)\n }\n\n /**\n * Handle MESSAGES_SNAPSHOT event\n */\n private handleMessagesSnapshotEvent(\n chunk: Extract<StreamChunk, { type: 'MESSAGES_SNAPSHOT' }>,\n ): void {\n this.resetStreamState()\n // Normalize AG-UI snapshot messages to UIMessage[] so every message has a\n // `parts` array. AG-UI messages carry `content` but no `parts`, so casting\n // them directly to UIMessage[] is unsafe and causes \"Cannot read properties\n // of undefined (reading 'find')\" when code later reads message.parts (e.g.\n // the onToolCallStateChange devtools handler).\n this.messages = chunk.messages.map(aguiSnapshotMessageToUIMessage)\n this.emitMessagesChange()\n }\n\n /**\n * Handle TEXT_MESSAGE_CONTENT event.\n *\n * Accumulates delta into both currentSegmentText (for UI emission) and\n * totalTextContent (for ProcessorResult). Lazily creates the assistant\n * UIMessage on first content. Uses updateTextPart() which replaces the\n * last TextPart or creates a new one depending on part ordering.\n *\n * @see docs/chat-architecture.md#single-shot-text-response — Text accumulation step-by-step\n * @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic\n */\n private handleTextMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(chunk.messageId)\n\n // Content arriving means all current tool calls for this message are complete\n this.completeAllToolCallsForMessage(messageId)\n\n if (this.structuredMessageIds.has(messageId)) {\n // `chunk.delta` is incremental; `chunk.content` is sometimes cumulative\n // (mirrors what the plain-text branch handles below). Reconcile against\n // the existing raw buffer so adapters that emit cumulative content\n // don't duplicate the JSON.\n let delta = chunk.delta || ''\n if (delta === '' && chunk.content !== undefined && chunk.content !== '') {\n const existingRaw = (\n this.messages\n .find((m) => m.id === messageId)\n ?.parts.find(\n (p): p is Extract<MessagePart, { type: 'structured-output' }> =>\n p.type === 'structured-output',\n ) ?? { raw: '' }\n ).raw\n if (chunk.content.startsWith(existingRaw)) {\n delta = chunk.content.slice(existingRaw.length)\n } else if (existingRaw.startsWith(chunk.content)) {\n delta = ''\n } else {\n delta = chunk.content\n }\n }\n if (delta !== '') {\n this.messages = appendStructuredOutputDelta(\n this.messages,\n messageId,\n delta,\n )\n state.totalTextContent += delta\n this.queueStructuredOutputUpdate(messageId, delta)\n this.emitMessagesChange()\n }\n return\n }\n\n const previousSegment = state.currentSegmentText\n\n // Detect if this is a NEW text segment (after tool calls) vs continuation\n const isNewSegment =\n state.hasToolCallsSinceTextStart &&\n previousSegment.length > 0 &&\n this.isNewTextSegment(chunk, previousSegment)\n\n if (isNewSegment) {\n // Emit any accumulated text before starting new segment\n if (previousSegment !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n // Reset SEGMENT text accumulation for the new text segment after tool calls\n state.currentSegmentText = ''\n state.lastEmittedText = ''\n state.hasToolCallsSinceTextStart = false\n }\n\n const currentText = state.currentSegmentText\n let nextText = currentText\n\n // Prefer delta over content - delta is the incremental change\n // Normalize to empty string to avoid \"undefined\" string concatenation\n const delta = chunk.delta || ''\n if (delta !== '') {\n nextText = currentText + delta\n } else if (chunk.content !== undefined && chunk.content !== '') {\n // Fallback: use content if delta is not provided\n if (chunk.content.startsWith(currentText)) {\n nextText = chunk.content\n } else if (currentText.startsWith(chunk.content)) {\n nextText = currentText\n } else {\n nextText = currentText + chunk.content\n }\n }\n\n // Calculate the delta for totalTextContent\n const textDelta = nextText.slice(currentText.length)\n state.currentSegmentText = nextText\n state.totalTextContent += textDelta\n\n // Use delta for chunk strategy if available\n const chunkPortion = chunk.delta || chunk.content || ''\n const shouldEmit = this.chunkStrategy.shouldEmit(\n chunkPortion,\n state.currentSegmentText,\n )\n if (shouldEmit && state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n }\n\n /**\n * Handle TOOL_CALL_START event.\n *\n * Creates a new InternalToolCallState entry in the toolCalls Map and appends\n * a ToolCallPart to the UIMessage. Duplicate toolCallId is a no-op.\n *\n * CRITICAL: This MUST be received before any TOOL_CALL_ARGS for the same\n * toolCallId. Args for unknown IDs are silently dropped.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Tool call state transitions\n * @see docs/chat-architecture.md#parallel-tool-calls-single-shot — Parallel tracking by ID\n * @see docs/chat-architecture.md#adapter-contract — Ordering requirements\n */\n private handleToolCallStartEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,\n ): void {\n // Determine the message this tool call belongs to\n const targetMessageId =\n chunk.parentMessageId ?? this.getActiveAssistantMessageId()\n const { messageId, state } = this.ensureAssistantMessage(\n targetMessageId ?? undefined,\n )\n\n // Mark that we've seen tool calls since the last text segment\n state.hasToolCallsSinceTextStart = true\n\n const toolCallId = chunk.toolCallId\n const existingToolCall = state.toolCalls.get(toolCallId)\n\n if (!existingToolCall) {\n // New tool call starting\n const initialState: ToolCallState = 'awaiting-input'\n\n // `toolName` is a deprecated alias for `toolCallName` (see ToolCallStartEvent\n // in types.ts). Accept either so chunks from older code paths or any\n // adapter that only sets the deprecated field still produce a named part.\n // The type marks both as required strings, but in practice some emitters\n // only set one — fall back via the runtime value rather than the type.\n const toolName =\n (chunk as { toolCallName?: string }).toolCallName ?? chunk.toolName\n\n // Capture provider metadata that arrived on TOOL_CALL_START so it\n // round-trips back through the assistant message on the next turn\n // (e.g. Gemini's thoughtSignature).\n const chunkMetadata = chunk.metadata\n\n const newToolCall: InternalToolCallState = {\n id: chunk.toolCallId,\n name: toolName,\n arguments: '',\n state: initialState,\n parsedArguments: undefined,\n index: chunk.index ?? state.toolCalls.size,\n ...(chunkMetadata !== undefined && { metadata: chunkMetadata }),\n }\n\n state.toolCalls.set(toolCallId, newToolCall)\n state.toolCallOrder.push(toolCallId)\n\n // Store mapping for TOOL_CALL_ARGS/END routing\n this.toolCallToMessage.set(toolCallId, messageId)\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: chunk.toolCallId,\n name: toolName,\n arguments: '',\n state: initialState,\n ...(chunkMetadata !== undefined && { metadata: chunkMetadata }),\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n chunk.toolCallId,\n initialState,\n '',\n )\n }\n }\n\n /**\n * Handle TOOL_CALL_ARGS event.\n *\n * Appends the delta to the tool call's accumulated arguments string.\n * Transitions state from awaiting-input → input-streaming on first non-empty delta.\n * Attempts partial JSON parse on each update for UI preview.\n *\n * If toolCallId is not found in the Map (no preceding TOOL_CALL_START),\n * this event is silently dropped.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Step-by-step tool call processing\n */\n private handleToolCallArgsEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,\n ): void {\n const toolCallId = chunk.toolCallId\n const messageId = this.toolCallToMessage.get(toolCallId)\n if (!messageId) return\n\n const state = this.getMessageState(messageId)\n if (!state) return\n\n const existingToolCall = state.toolCalls.get(toolCallId)\n if (!existingToolCall) return\n\n const wasAwaitingInput = existingToolCall.state === 'awaiting-input'\n\n // Accumulate arguments from delta\n existingToolCall.arguments += chunk.delta || ''\n\n // Update state\n if (wasAwaitingInput && chunk.delta) {\n existingToolCall.state = 'input-streaming'\n }\n\n // Try to parse the updated arguments\n existingToolCall.parsedArguments = this.jsonParser.parse(\n existingToolCall.arguments,\n )\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: existingToolCall.id,\n name: existingToolCall.name,\n arguments: existingToolCall.arguments,\n state: existingToolCall.state,\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n existingToolCall.id,\n existingToolCall.state,\n existingToolCall.arguments,\n )\n }\n\n /**\n * Handle TOOL_CALL_END event — authoritative signal that a tool call's input is finalized.\n *\n * This event has a DUAL ROLE:\n * - Without `result`: Signals arguments are done (from adapter). Transitions to input-complete.\n * - With `result`: Signals tool was executed and result is available (from TextEngine).\n * Creates both output on the tool-call part AND a tool-result part.\n *\n * If `input` is provided, it overrides the accumulated string parse as the\n * canonical parsed arguments.\n *\n * @see docs/chat-architecture.md#tool-results-and-the-tool_call_end-dual-role — Full explanation\n * @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow\n */\n private handleToolCallEndEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,\n ): void {\n const messageId = this.toolCallToMessage.get(chunk.toolCallId)\n if (!messageId) return\n\n const msgState = this.getMessageState(messageId)\n if (!msgState) return\n\n // Transition the tool call to input-complete (the authoritative completion signal)\n const existingToolCall = msgState.toolCalls.get(chunk.toolCallId)\n if (existingToolCall && existingToolCall.state !== 'input-complete') {\n // If TOOL_CALL_END provides parsed input and no TOOL_CALL_ARGS were\n // received, back-fill the arguments string so the UIMessage ToolCallPart\n // carries the correct value (defensive against adapters that skip ARGS).\n if (chunk.input !== undefined && !existingToolCall.arguments) {\n existingToolCall.arguments = JSON.stringify(chunk.input)\n }\n\n const index = msgState.toolCallOrder.indexOf(chunk.toolCallId)\n this.completeToolCall(messageId, index, existingToolCall)\n // If TOOL_CALL_END provides parsed input, use it as the canonical parsed\n // arguments (overrides the accumulated string parse from completeToolCall)\n if (chunk.input !== undefined) {\n existingToolCall.parsedArguments = chunk.input\n }\n }\n\n // Update UIMessage if there's a result\n if (chunk.result) {\n // Step 1: Update the tool-call part's output field (for UI consistency\n // with client tools — see GitHub issue #176)\n let output: unknown\n if (Array.isArray(chunk.result)) {\n output = chunk.result\n } else {\n try {\n output = JSON.parse(chunk.result)\n } catch {\n output = chunk.result\n }\n }\n this.messages = updateToolCallWithOutput(\n this.messages,\n chunk.toolCallId,\n output,\n chunk.state === 'output-error' ? 'error' : undefined,\n )\n\n // Step 2: Create/update the tool-result part (for LLM conversation history)\n const resultState: ToolResultState =\n chunk.state === 'output-error' ? 'error' : 'complete'\n this.messages = updateToolResultPart(\n this.messages,\n messageId,\n chunk.toolCallId,\n chunk.result,\n resultState,\n resultState === 'error'\n ? this.extractToolResultError(output)\n : undefined,\n )\n this.emitMessagesChange()\n }\n }\n\n private extractToolResultError(output: unknown): string {\n if (\n output &&\n typeof output === 'object' &&\n 'error' in output &&\n typeof output.error === 'string'\n ) {\n return output.error\n }\n return typeof output === 'string' ? output : 'Tool execution failed'\n }\n\n /**\n * Handle TOOL_CALL_RESULT event (AG-UI spec).\n *\n * Creates a tool-result part and updates the tool-call output field,\n * mirroring the logic from TOOL_CALL_END when it carries a result.\n * This is the spec-compliant path for delivering tool results to the client.\n */\n private handleToolCallResultEvent(\n chunk: Extract<StreamChunk, { type: 'TOOL_CALL_RESULT' }>,\n ): void {\n const messageId = this.toolCallToMessage.get(chunk.toolCallId)\n if (!messageId) return\n\n // Step 1: Update the tool-call part's output field\n let output: unknown\n try {\n output = JSON.parse(chunk.content)\n } catch {\n output = chunk.content\n }\n this.messages = updateToolCallWithOutput(\n this.messages,\n chunk.toolCallId,\n output,\n chunk.state === 'output-error' ? 'error' : undefined,\n )\n\n // Step 2: Create/update the tool-result part\n const resultState: ToolResultState =\n chunk.state === 'output-error' ? 'error' : 'complete'\n this.messages = updateToolResultPart(\n this.messages,\n messageId,\n chunk.toolCallId,\n chunk.content,\n resultState,\n resultState === 'error' ? this.extractToolResultError(output) : undefined,\n )\n this.emitMessagesChange()\n }\n\n /**\n * Handle RUN_STARTED event.\n *\n * Registers the run so that RUN_FINISHED can determine whether other\n * runs are still active before finalizing.\n */\n private handleRunStartedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_STARTED' }>,\n ): void {\n this.activeRuns.add(chunk.runId)\n }\n\n /**\n * Handle RUN_FINISHED event.\n *\n * Records the finishReason and removes the run from activeRuns.\n * Only finalizes when no more runs are active, so that concurrent\n * runs don't interfere with each other.\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — finishReason semantics\n * @see docs/chat-architecture.md#adapter-contract — Why RUN_FINISHED is mandatory\n */\n private handleRunFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_FINISHED' }>,\n ): void {\n this.finishReason = chunk.finishReason ?? null\n this.activeRuns.delete(chunk.runId)\n\n if (this.activeRuns.size === 0) {\n this.isDone = true\n this.completeAllToolCalls()\n this.finalizeStream()\n }\n }\n\n /**\n * Handle RUN_ERROR event\n */\n private handleRunErrorEvent(\n chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n ): void {\n this.hasError = true\n const runId =\n 'runId' in chunk && typeof chunk.runId === 'string'\n ? chunk.runId\n : undefined\n if (runId) {\n this.activeRuns.delete(runId)\n } else {\n this.activeRuns.clear()\n }\n const { messageId } = this.ensureAssistantMessage()\n // Prefer spec field `message`; fall back to deprecated `error.message`.\n // If neither is set, the chunk still carries debug context (provider\n // error codes, request ids, etc.) — log it so the failure isn't silent.\n const errorMessage =\n chunk.message || chunk.error?.message || 'An error occurred'\n if (!chunk.message && !chunk.error?.message) {\n console.error(\n '[StreamProcessor] RUN_ERROR with no message; original chunk:',\n chunk,\n )\n }\n\n if (this.structuredMessageIds.has(messageId)) {\n this.flushStructuredOutputUpdate(messageId)\n this.messages = errorStructuredOutputPart(\n this.messages,\n messageId,\n errorMessage,\n )\n this.structuredMessageIds.delete(messageId)\n this.emitStructuredOutputChange(messageId, 'error')\n this.emitMessagesChange()\n }\n\n // Attach the provider's structured error body (`rawEvent`) and `code` to\n // the surfaced Error so consumers can recover the upstream detail that the\n // RUN_ERROR's `message` alone discards. Both are optional and added only\n // when present, keeping the Error backward compatible.\n const error = new Error(errorMessage)\n const code = chunk.code ?? chunk.error?.code\n if (code !== undefined) {\n Object.assign(error, { code })\n }\n if (chunk.rawEvent !== undefined) {\n Object.assign(error, { rawEvent: chunk.rawEvent })\n }\n this.events.onError?.(error)\n }\n\n /**\n * Handle STEP_STARTED event (for thinking/reasoning content).\n *\n * Records the stepId so that subsequent STEP_FINISHED deltas accumulate\n * into their own ThinkingPart. Does not create a message — the message\n * is lazily created when the first STEP_FINISHED content arrives.\n */\n private handleStepStartedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_STARTED' }>,\n ): void {\n const stepId = chunk.stepId ?? generateMessageId()\n const activeId = this.getActiveAssistantMessageId()\n if (activeId) {\n const state = this.getMessageState(activeId)\n if (state) {\n state.currentThinkingStepId = stepId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n }\n // Clear any pending stepId from a prior STEP_STARTED that fired\n // before the assistant message existed. Now that we're tracking\n // the step directly on message state, the pending value is stale\n // and must not leak into the next STEP_FINISHED (which would\n // misattribute its delta to the stale step).\n this.pendingThinkingStepId = null\n return\n }\n }\n\n // No active message yet — defer until ensureAssistantMessage in STEP_FINISHED\n this.pendingThinkingStepId = stepId\n }\n\n /**\n * Handle STEP_FINISHED event (for thinking/reasoning content).\n *\n * Accumulates delta into the current thinking step's content and updates\n * the corresponding ThinkingPart in the UIMessage.\n *\n * @see docs/chat-architecture.md#thinkingreasoning-content — Thinking flow\n */\n private handleStepFinishedEvent(\n chunk: Extract<StreamChunk, { type: 'STEP_FINISHED' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(\n this.getActiveAssistantMessageId() ?? undefined,\n )\n\n // During the transition period, adapters emit BOTH STEP_FINISHED and\n // REASONING_MESSAGE_CONTENT with the same delta. If we've already processed\n // REASONING_MESSAGE_CONTENT events for this message, skip the duplicate\n // thinking content from STEP_FINISHED to avoid doubled content.\n if (state.hasSeenReasoningEvents) {\n if (chunk.signature) {\n const stepId = state.currentThinkingStepId ?? chunk.stepId\n if (!stepId) return\n const thinking = state.thinkingSteps.get(stepId)\n if (thinking !== undefined) {\n state.thinkingStepSignatures.set(stepId, chunk.signature)\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n thinking,\n chunk.signature,\n )\n this.emitMessagesChange()\n }\n }\n return\n }\n\n this.consumePendingThinkingStep(state)\n\n const stepId =\n state.currentThinkingStepId ?? chunk.stepId ?? generateMessageId()\n\n // Auto-initialize if no prior STEP_STARTED (backward compat)\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n state.currentThinkingStepId = stepId\n }\n\n const previous = state.thinkingSteps.get(stepId) ?? ''\n let nextThinking = previous\n\n // Prefer delta over content\n if (chunk.delta && chunk.delta !== '') {\n nextThinking = previous + chunk.delta\n } else if (chunk.content && chunk.content !== '') {\n if (chunk.content.startsWith(previous)) {\n nextThinking = chunk.content\n } else if (previous.startsWith(chunk.content)) {\n nextThinking = previous\n } else {\n nextThinking = previous + chunk.content\n }\n }\n\n state.thinkingSteps.set(stepId, nextThinking)\n\n if (chunk.signature) {\n state.thinkingStepSignatures.set(stepId, chunk.signature)\n }\n\n // Update UIMessage\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n nextThinking,\n state.thinkingStepSignatures.get(stepId),\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)\n }\n\n /**\n * Handle REASONING_MESSAGE_CONTENT event (AG-UI reasoning protocol).\n *\n * Accumulates reasoning delta into thinking content and updates the\n * corresponding ThinkingPart in the UIMessage.\n */\n private handleReasoningMessageContentEvent(\n chunk: Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,\n ): void {\n const { messageId, state } = this.ensureAssistantMessage(\n this.getActiveAssistantMessageId() ?? undefined,\n )\n\n state.hasSeenReasoningEvents = true\n const delta = chunk.delta || ''\n\n this.consumePendingThinkingStep(state)\n\n const stepId = state.currentThinkingStepId ?? chunk.messageId\n if (!state.thinkingSteps.has(stepId)) {\n state.thinkingSteps.set(stepId, '')\n state.thinkingStepOrder.push(stepId)\n state.currentThinkingStepId = stepId\n }\n\n const nextThinking = (state.thinkingSteps.get(stepId) ?? '') + delta\n state.thinkingSteps.set(stepId, nextThinking)\n\n this.messages = updateThinkingPart(\n this.messages,\n messageId,\n stepId,\n nextThinking,\n state.thinkingStepSignatures.get(stepId),\n )\n this.emitMessagesChange()\n\n this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)\n }\n\n /**\n * Handle CUSTOM event.\n *\n * Handles special custom events emitted by the TextEngine (not adapters):\n * - 'tool-input-available': Client tool needs execution. Fires onToolCall.\n * - 'approval-requested': Tool needs user approval. Updates tool-call part\n * state and fires onApprovalRequest.\n *\n * @see docs/chat-architecture.md#client-tools-and-approval-flows — Full flow details\n */\n private handleCustomEvent(\n chunk: Extract<StreamChunk, { type: 'CUSTOM' }>,\n ): void {\n const messageId = this.getActiveAssistantMessageId()\n\n if (chunk.name === 'structured-output.start' && chunk.value) {\n const v = chunk.value as { messageId?: string }\n const targetId = v.messageId ?? messageId\n if (targetId) {\n this.ensureAssistantMessage(targetId)\n this.structuredMessageIds.add(targetId)\n this.structuredOutputUpdateBatches.delete(targetId)\n this.events.onStructuredOutputChange?.({\n phase: 'start',\n messageId: targetId,\n status: 'streaming',\n raw: '',\n })\n }\n return\n }\n\n if (chunk.name === 'structured-output.complete' && chunk.value) {\n const v = chunk.value as {\n object: unknown\n raw?: string\n reasoning?: string\n messageId?: string\n }\n const targetId = v.messageId ?? messageId\n if (targetId) {\n this.flushStructuredOutputUpdate(targetId)\n this.messages = completeStructuredOutputPart(\n this.messages,\n targetId,\n v.object,\n v.raw ?? '',\n v.reasoning,\n )\n this.structuredMessageIds.delete(targetId)\n this.emitStructuredOutputChange(targetId, 'complete')\n this.emitMessagesChange()\n }\n // Fall through so user `onCustomEvent` callbacks still observe the event.\n }\n\n // Handle client tool input availability - trigger client-side execution\n if (chunk.name === 'tool-input-available' && chunk.value) {\n const { toolCallId, toolName, input } = chunk.value as {\n toolCallId: string\n toolName: string\n input: any\n }\n\n // Emit onToolCall event for the client to execute the tool\n this.events.onToolCall?.({\n toolCallId,\n toolName,\n input,\n })\n return\n }\n\n // Handle approval requests\n if (chunk.name === 'approval-requested' && chunk.value) {\n const { toolCallId, toolName, input, approval } = chunk.value as {\n toolCallId: string\n toolName: string\n input: any\n approval: { id: string; needsApproval: boolean }\n }\n\n // Resolve the message containing this tool call. After RUN_FINISHED,\n // activeMessageIds is cleared, so fall back to the toolCallToMessage map\n // which is populated during TOOL_CALL_START and preserved across finalize.\n const resolvedMessageId =\n messageId ?? this.toolCallToMessage.get(toolCallId)\n if (resolvedMessageId) {\n this.messages = updateToolCallApproval(\n this.messages,\n resolvedMessageId,\n toolCallId,\n approval.id,\n )\n this.emitMessagesChange()\n }\n\n // Emit approval request event\n this.events.onApprovalRequest?.({\n toolCallId,\n toolName,\n input,\n approvalId: approval.id,\n })\n return\n }\n\n // Forward non-system custom events to onCustomEvent callback\n if (this.events.onCustomEvent) {\n const toolCallId =\n chunk.value && typeof chunk.value === 'object'\n ? chunk.value.toolCallId\n : undefined\n this.events.onCustomEvent(chunk.name, chunk.value, { toolCallId })\n }\n }\n\n // ============================================\n // Internal Helpers\n // ============================================\n\n /**\n * Detect if an incoming content chunk represents a NEW text segment\n */\n private isNewTextSegment(\n chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,\n previous: string,\n ): boolean {\n // Check if content is present (delta is always defined but may be empty string)\n if (chunk.content !== undefined) {\n if (chunk.content.length < previous.length) {\n return true\n }\n if (\n !chunk.content.startsWith(previous) &&\n !previous.startsWith(chunk.content)\n ) {\n return true\n }\n }\n return false\n }\n\n /**\n * Complete all tool calls across all active messages — safety net for stream termination.\n *\n * Called by RUN_FINISHED and finalizeStream(). Force-transitions any tool call\n * not yet in input-complete state. Handles cases where TOOL_CALL_END was\n * missed (adapter bug, network error, aborted stream).\n *\n * @see docs/chat-architecture.md#single-shot-tool-call-response — Safety net behavior\n */\n private completeAllToolCalls(): void {\n for (const messageId of this.activeMessageIds) {\n this.completeAllToolCallsForMessage(messageId)\n }\n }\n\n /**\n * Complete all tool calls for a specific message\n */\n private completeAllToolCallsForMessage(messageId: string): void {\n const state = this.getMessageState(messageId)\n if (!state) return\n\n state.toolCalls.forEach((toolCall, id) => {\n if (toolCall.state !== 'input-complete') {\n const index = state.toolCallOrder.indexOf(id)\n this.completeToolCall(messageId, index, toolCall)\n }\n })\n }\n\n /**\n * Mark a tool call as complete and emit event\n */\n private completeToolCall(\n messageId: string,\n _index: number,\n toolCall: InternalToolCallState,\n ): void {\n // Finalize the internal bookkeeping: the call's input arguments ARE\n // complete regardless of whether execution later failed, so the call still\n // counts as a completed tool call in getCompletedToolCalls()/getState().\n toolCall.state = 'input-complete'\n\n // Try final parse\n toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments)\n\n // Don't downgrade the rendered part of a call that already reached the\n // terminal 'error' state (e.g. an output-error TOOL_CALL_RESULT arrived\n // without a preceding TOOL_CALL_END). The RUN_FINISHED / finalizeStream\n // safety net must not clobber a failed call back to 'input-complete'.\n if (this.isToolCallPartErrored(toolCall.id)) {\n return\n }\n\n // Update UIMessage\n this.messages = updateToolCallPart(this.messages, messageId, {\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: 'input-complete',\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onToolCallStateChange?.(\n messageId,\n toolCall.id,\n 'input-complete',\n toolCall.arguments,\n )\n }\n\n /**\n * Whether the rendered tool-call part for the given id has reached the\n * terminal 'error' state. Used to prevent the completion safety net from\n * downgrading a failed call back to 'input-complete'.\n */\n private isToolCallPartErrored(toolCallId: string): boolean {\n return this.messages.some((msg) =>\n msg.parts.some(\n (part) =>\n part.type === 'tool-call' &&\n part.id === toolCallId &&\n part.state === 'error',\n ),\n )\n }\n\n /**\n * Emit pending text update for a specific message.\n *\n * Calls updateTextPart() which has critical append-vs-replace logic:\n * - If last UIMessage part is TextPart → replaces its content (same segment).\n * - If last part is anything else → pushes new TextPart (new segment after tools).\n *\n * @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic\n */\n private emitTextUpdateForMessage(messageId: string): void {\n const state = this.getMessageState(messageId)\n if (!state) return\n\n state.lastEmittedText = state.currentSegmentText\n\n // Update UIMessage\n this.messages = updateTextPart(\n this.messages,\n messageId,\n state.currentSegmentText,\n )\n this.emitMessagesChange()\n\n // Emit granular event\n this.events.onTextUpdate?.(messageId, state.currentSegmentText)\n }\n\n private queueStructuredOutputUpdate(messageId: string, delta: string): void {\n const existing = this.structuredOutputUpdateBatches.get(messageId)\n const next = {\n delta: `${existing?.delta ?? ''}${delta}`,\n chunkCount: (existing?.chunkCount ?? 0) + 1,\n }\n\n this.structuredOutputUpdateBatches.set(messageId, next)\n\n if (next.chunkCount >= STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE) {\n this.flushStructuredOutputUpdate(messageId)\n }\n }\n\n private flushStructuredOutputUpdate(messageId: string): void {\n const batch = this.structuredOutputUpdateBatches.get(messageId)\n if (!batch || batch.chunkCount === 0) return\n\n this.structuredOutputUpdateBatches.delete(messageId)\n this.emitStructuredOutputChange(messageId, 'update', batch.delta)\n }\n\n private emitStructuredOutputChange(\n messageId: string,\n phase: 'update' | 'complete' | 'error',\n delta?: string,\n ): void {\n const part = this.messages\n .find((message) => message.id === messageId)\n ?.parts.find(\n (\n messagePart,\n ): messagePart is Extract<MessagePart, { type: 'structured-output' }> =>\n messagePart.type === 'structured-output',\n )\n if (!part) return\n\n this.events.onStructuredOutputChange?.({\n phase,\n messageId,\n status: part.status,\n raw: part.raw,\n ...(part.partial !== undefined ? { partial: part.partial } : {}),\n ...(part.data !== undefined ? { data: part.data } : {}),\n ...(part.reasoning !== undefined ? { reasoning: part.reasoning } : {}),\n ...(part.errorMessage !== undefined\n ? { errorMessage: part.errorMessage }\n : {}),\n ...(delta !== undefined ? { delta } : {}),\n })\n }\n\n /**\n * Emit messages change event\n */\n private emitMessagesChange(): void {\n this.events.onMessagesChange?.([...this.messages])\n }\n\n /**\n * Finalize the stream — complete all pending operations.\n *\n * Called when the async iterable ends (stream closed). Acts as the final\n * safety net: completes any remaining tool calls, flushes un-emitted text,\n * and fires onStreamEnd.\n *\n * @see docs/chat-architecture.md#single-shot-text-response — Finalization step\n */\n finalizeStream(): void {\n let lastAssistantMessage: UIMessage | undefined\n\n // Finalize ALL active messages\n for (const messageId of this.activeMessageIds) {\n const state = this.getMessageState(messageId)\n if (!state) continue\n\n // Complete any remaining tool calls\n this.completeAllToolCallsForMessage(messageId)\n\n // Emit any pending text if not already emitted\n if (state.currentSegmentText !== state.lastEmittedText) {\n this.emitTextUpdateForMessage(messageId)\n }\n\n state.isComplete = true\n\n const msg = this.messages.find((m) => m.id === messageId)\n if (msg && msg.role === 'assistant') {\n lastAssistantMessage = msg\n }\n }\n\n // The stream closed but one or more structured-output runs never sent\n // their terminal `structured-output.complete`. Snap each lingering\n // streaming part to error so the UI doesn't appear to stream forever,\n // and drop the routing entries so a subsequent run on the same\n // processor instance (long-lived `subscribe()` mode) doesn't reuse\n // the stale ids.\n //\n // The iteration is unconditional w.r.t. `this.hasError` — RUN_ERROR\n // already removed its target messageId from `structuredMessageIds`\n // before reaching finalize, so anything still in the set is by\n // definition a non-errored, never-completed run (the multi-run case:\n // run-A errors, run-B is still streaming when finalize fires).\n for (const messageId of this.structuredMessageIds) {\n this.flushStructuredOutputUpdate(messageId)\n this.messages = errorStructuredOutputPart(\n this.messages,\n messageId,\n 'Stream ended without structured-output.complete',\n )\n this.emitStructuredOutputChange(messageId, 'error')\n }\n this.structuredMessageIds.clear()\n this.structuredOutputUpdateBatches.clear()\n\n this.activeMessageIds.clear()\n\n // Remove whitespace-only assistant messages (handles models like Gemini\n // that sometimes return just \"\\n\" during auto-continuation).\n // Preserve the message on errors so the UI can show error state.\n if (lastAssistantMessage && !this.hasError) {\n if (this.isWhitespaceOnlyMessage(lastAssistantMessage)) {\n this.messages = this.messages.filter(\n (m) => m.id !== lastAssistantMessage.id,\n )\n this.emitMessagesChange()\n return\n }\n }\n\n // Emit stream end for the last assistant message\n if (lastAssistantMessage) {\n this.events.onStreamEnd?.(lastAssistantMessage)\n }\n }\n\n /**\n * Get completed tool calls in API format (aggregated across all messages)\n */\n private getCompletedToolCalls(): Array<ToolCall> {\n const result: Array<ToolCall> = []\n for (const state of this.messageStates.values()) {\n for (const tc of state.toolCalls.values()) {\n if (tc.state === 'input-complete') {\n result.push({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.name,\n arguments: tc.arguments,\n },\n // Preserve provider metadata (e.g. Gemini thoughtSignature) on\n // ProcessorResult.toolCalls so callers using process()/getResult()\n // get the same round-trip support as the streaming UI path.\n ...(tc.metadata !== undefined && { metadata: tc.metadata }),\n })\n }\n }\n }\n return result\n }\n\n /**\n * Get current result (aggregated across all messages)\n */\n private getResult(): ProcessorResult {\n const toolCalls = this.getCompletedToolCalls()\n let content = ''\n let thinking = ''\n\n for (const state of this.messageStates.values()) {\n content += state.totalTextContent\n for (const stepId of state.thinkingStepOrder) {\n thinking += state.thinkingSteps.get(stepId) ?? ''\n }\n }\n\n return {\n content,\n thinking: thinking || undefined,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n finishReason: this.finishReason,\n }\n }\n\n /**\n * Get current processor state (aggregated across all messages)\n */\n getState(): ProcessorState {\n let content = ''\n let thinking = ''\n const toolCalls = new Map<string, InternalToolCallState>()\n const toolCallOrder: Array<string> = []\n\n for (const state of this.messageStates.values()) {\n content += state.totalTextContent\n for (const stepId of state.thinkingStepOrder) {\n thinking += state.thinkingSteps.get(stepId) ?? ''\n }\n for (const [id, tc] of state.toolCalls) {\n toolCalls.set(id, tc)\n }\n toolCallOrder.push(...state.toolCallOrder)\n }\n\n return {\n content,\n thinking,\n toolCalls,\n toolCallOrder,\n finishReason: this.finishReason,\n done: this.isDone,\n }\n }\n\n /**\n * Start recording chunks\n */\n startRecording(): void {\n this.recordingEnabled = true\n this.recordingStartTime = Date.now()\n this.recording = {\n version: '1.0',\n timestamp: this.recordingStartTime,\n chunks: [],\n }\n }\n\n /**\n * Get the current recording\n */\n getRecording(): ChunkRecording | null {\n return this.recording\n }\n\n /**\n * Reset stream state (but keep messages)\n */\n private resetStreamState(): void {\n this.messageStates.clear()\n this.activeMessageIds.clear()\n this.activeRuns.clear()\n this.toolCallToMessage.clear()\n this.structuredMessageIds.clear()\n this.structuredOutputUpdateBatches.clear()\n this.pendingManualMessageId = null\n this.pendingThinkingStepId = null\n this.finishReason = null\n this.hasError = false\n this.isDone = false\n this.chunkStrategy.reset?.()\n }\n\n /**\n * Full reset (including messages)\n */\n reset(): void {\n this.resetStreamState()\n this.messages = []\n }\n\n /**\n * Check if a message contains only whitespace text and no other meaningful parts\n * (no tool calls, tool results, thinking, etc.)\n */\n private isWhitespaceOnlyMessage(message: UIMessage): boolean {\n if (message.parts.length === 0) return false\n return message.parts.every(\n (part) => part.type === 'text' && part.content.trim() === '',\n )\n }\n\n /**\n * Replay a recording through the processor\n */\n static async replay(\n recording: ChunkRecording,\n options?: StreamProcessorOptions,\n ): Promise<ProcessorResult> {\n const processor = new StreamProcessor(options)\n return processor.process(createReplayStream(recording))\n }\n}\n\n/**\n * Create an async iterable from a recording\n */\nexport function createReplayStream(\n recording: ChunkRecording,\n): AsyncIterable<StreamChunk> {\n return {\n // eslint-disable-next-line @typescript-eslint/require-await -- async generator required by AsyncIterable contract; body has no await\n async *[Symbol.asyncIterator]() {\n for (const { chunk } of recording.chunks) {\n yield chunk\n }\n },\n }\n}\n"],"names":["state","delta","stepId"],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ChunkStrategy } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Immediate Strategy - emit on every chunk (default behavior)
|
|
4
|
+
*/
|
|
5
|
+
export declare class ImmediateStrategy implements ChunkStrategy {
|
|
6
|
+
shouldEmit(_chunk: string, _accumulated: string): boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Punctuation Strategy - emit when chunk contains punctuation
|
|
10
|
+
* Useful for natural text flow in UI
|
|
11
|
+
*/
|
|
12
|
+
export declare class PunctuationStrategy implements ChunkStrategy {
|
|
13
|
+
private readonly punctuation;
|
|
14
|
+
shouldEmit(chunk: string, _accumulated: string): boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Batch Strategy - emit every N chunks
|
|
18
|
+
* Useful for reducing UI update frequency
|
|
19
|
+
*/
|
|
20
|
+
export declare class BatchStrategy implements ChunkStrategy {
|
|
21
|
+
private readonly batchSize;
|
|
22
|
+
private chunkCount;
|
|
23
|
+
constructor(batchSize?: number);
|
|
24
|
+
shouldEmit(_chunk: string, _accumulated: string): boolean;
|
|
25
|
+
reset(): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Word Boundary Strategy - emit at word boundaries
|
|
29
|
+
* Prevents cutting words in half
|
|
30
|
+
*/
|
|
31
|
+
export declare class WordBoundaryStrategy implements ChunkStrategy {
|
|
32
|
+
shouldEmit(chunk: string, _accumulated: string): boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Composite Strategy - combine multiple strategies (OR logic)
|
|
36
|
+
* Emits if ANY strategy says to emit
|
|
37
|
+
*/
|
|
38
|
+
export declare class CompositeStrategy implements ChunkStrategy {
|
|
39
|
+
private readonly strategies;
|
|
40
|
+
constructor(strategies: Array<ChunkStrategy>);
|
|
41
|
+
shouldEmit(chunk: string, accumulated: string): boolean;
|
|
42
|
+
reset(): void;
|
|
43
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class ImmediateStrategy {
|
|
2
|
+
shouldEmit(_chunk, _accumulated) {
|
|
3
|
+
return true;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
class PunctuationStrategy {
|
|
7
|
+
punctuation = /[.,!?;:\n]/;
|
|
8
|
+
shouldEmit(chunk, _accumulated) {
|
|
9
|
+
return this.punctuation.test(chunk);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
class BatchStrategy {
|
|
13
|
+
constructor(batchSize = 5) {
|
|
14
|
+
this.batchSize = batchSize;
|
|
15
|
+
}
|
|
16
|
+
batchSize;
|
|
17
|
+
chunkCount = 0;
|
|
18
|
+
shouldEmit(_chunk, _accumulated) {
|
|
19
|
+
this.chunkCount++;
|
|
20
|
+
if (this.chunkCount >= this.batchSize) {
|
|
21
|
+
this.chunkCount = 0;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
reset() {
|
|
27
|
+
this.chunkCount = 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
class WordBoundaryStrategy {
|
|
31
|
+
shouldEmit(chunk, _accumulated) {
|
|
32
|
+
return /\s$/.test(chunk);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
class CompositeStrategy {
|
|
36
|
+
constructor(strategies) {
|
|
37
|
+
this.strategies = strategies;
|
|
38
|
+
}
|
|
39
|
+
strategies;
|
|
40
|
+
shouldEmit(chunk, accumulated) {
|
|
41
|
+
return this.strategies.some((s) => s.shouldEmit(chunk, accumulated));
|
|
42
|
+
}
|
|
43
|
+
reset() {
|
|
44
|
+
this.strategies.forEach((s) => s.reset?.());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
BatchStrategy,
|
|
49
|
+
CompositeStrategy,
|
|
50
|
+
ImmediateStrategy,
|
|
51
|
+
PunctuationStrategy,
|
|
52
|
+
WordBoundaryStrategy
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=strategies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategies.js","sources":["../../../../../src/activities/chat/stream/strategies.ts"],"sourcesContent":["/**\n * Built-in Chunk Strategies\n *\n * Strategies for controlling when text updates are emitted to the UI\n */\n\nimport type { ChunkStrategy } from './types'\n\n/**\n * Immediate Strategy - emit on every chunk (default behavior)\n */\nexport class ImmediateStrategy implements ChunkStrategy {\n shouldEmit(_chunk: string, _accumulated: string): boolean {\n return true\n }\n}\n\n/**\n * Punctuation Strategy - emit when chunk contains punctuation\n * Useful for natural text flow in UI\n */\nexport class PunctuationStrategy implements ChunkStrategy {\n private readonly punctuation = /[.,!?;:\\n]/\n\n shouldEmit(chunk: string, _accumulated: string): boolean {\n return this.punctuation.test(chunk)\n }\n}\n\n/**\n * Batch Strategy - emit every N chunks\n * Useful for reducing UI update frequency\n */\nexport class BatchStrategy implements ChunkStrategy {\n private chunkCount = 0\n\n constructor(private readonly batchSize: number = 5) {}\n\n shouldEmit(_chunk: string, _accumulated: string): boolean {\n this.chunkCount++\n if (this.chunkCount >= this.batchSize) {\n this.chunkCount = 0\n return true\n }\n return false\n }\n\n reset(): void {\n this.chunkCount = 0\n }\n}\n\n/**\n * Word Boundary Strategy - emit at word boundaries\n * Prevents cutting words in half\n */\nexport class WordBoundaryStrategy implements ChunkStrategy {\n shouldEmit(chunk: string, _accumulated: string): boolean {\n // Emit if chunk ends with whitespace\n return /\\s$/.test(chunk)\n }\n}\n\n/**\n * Composite Strategy - combine multiple strategies (OR logic)\n * Emits if ANY strategy says to emit\n */\nexport class CompositeStrategy implements ChunkStrategy {\n constructor(private readonly strategies: Array<ChunkStrategy>) {}\n\n shouldEmit(chunk: string, accumulated: string): boolean {\n return this.strategies.some((s) => s.shouldEmit(chunk, accumulated))\n }\n\n reset(): void {\n this.strategies.forEach((s) => s.reset?.())\n }\n}\n"],"names":[],"mappings":"AAWO,MAAM,kBAA2C;AAAA,EACtD,WAAW,QAAgB,cAA+B;AACxD,WAAO;AAAA,EACT;AACF;AAMO,MAAM,oBAA6C;AAAA,EACvC,cAAc;AAAA,EAE/B,WAAW,OAAe,cAA+B;AACvD,WAAO,KAAK,YAAY,KAAK,KAAK;AAAA,EACpC;AACF;AAMO,MAAM,cAAuC;AAAA,EAGlD,YAA6B,YAAoB,GAAG;AAAvB,SAAA,YAAA;AAAA,EAAwB;AAAA,EAAxB;AAAA,EAFrB,aAAa;AAAA,EAIrB,WAAW,QAAgB,cAA+B;AACxD,SAAK;AACL,QAAI,KAAK,cAAc,KAAK,WAAW;AACrC,WAAK,aAAa;AAClB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEA,QAAc;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAMO,MAAM,qBAA8C;AAAA,EACzD,WAAW,OAAe,cAA+B;AAEvD,WAAO,MAAM,KAAK,KAAK;AAAA,EACzB;AACF;AAMO,MAAM,kBAA2C;AAAA,EACtD,YAA6B,YAAkC;AAAlC,SAAA,aAAA;AAAA,EAAmC;AAAA,EAAnC;AAAA,EAE7B,WAAW,OAAe,aAA8B;AACtD,WAAO,KAAK,WAAW,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,WAAW,CAAC;AAAA,EACrE;AAAA,EAEA,QAAc;AACZ,SAAK,WAAW,QAAQ,CAAC,MAAM,EAAE,SAAS;AAAA,EAC5C;AACF;"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { StreamChunk, ToolCall, ToolCallState, ToolResultState } from '../../../types.js';
|
|
2
|
+
export type { ToolCallState, ToolResultState };
|
|
3
|
+
/**
|
|
4
|
+
* Internal state for a tool call being tracked
|
|
5
|
+
*/
|
|
6
|
+
export interface InternalToolCallState {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
arguments: string;
|
|
10
|
+
state: ToolCallState;
|
|
11
|
+
parsedArguments?: any;
|
|
12
|
+
index: number;
|
|
13
|
+
/** Provider-specific metadata that round-trips with the tool call
|
|
14
|
+
* (e.g. Gemini's `thoughtSignature`). Untyped at this layer because
|
|
15
|
+
* the stream processor is provider-agnostic; adapters narrow it
|
|
16
|
+
* via their `TToolCallMetadata` generic. */
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Strategy for determining when to emit text updates
|
|
21
|
+
*/
|
|
22
|
+
export interface ChunkStrategy {
|
|
23
|
+
/**
|
|
24
|
+
* Called for each text chunk received
|
|
25
|
+
* @param chunk - The new chunk of text (delta)
|
|
26
|
+
* @param accumulated - All text accumulated so far
|
|
27
|
+
* @returns true if an update should be emitted now
|
|
28
|
+
*/
|
|
29
|
+
shouldEmit: (chunk: string, accumulated: string) => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Optional: Reset strategy state (called when streaming starts)
|
|
32
|
+
*/
|
|
33
|
+
reset?: () => void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Per-message streaming state.
|
|
37
|
+
* Tracks the accumulation of text, tool calls, and thinking content
|
|
38
|
+
* for a single message in the stream.
|
|
39
|
+
*/
|
|
40
|
+
export interface MessageStreamState {
|
|
41
|
+
id: string;
|
|
42
|
+
role: 'user' | 'assistant' | 'system';
|
|
43
|
+
totalTextContent: string;
|
|
44
|
+
currentSegmentText: string;
|
|
45
|
+
lastEmittedText: string;
|
|
46
|
+
hasSeenReasoningEvents: boolean;
|
|
47
|
+
thinkingSteps: Map<string, string>;
|
|
48
|
+
thinkingStepSignatures: Map<string, string>;
|
|
49
|
+
thinkingStepOrder: Array<string>;
|
|
50
|
+
currentThinkingStepId: string | null;
|
|
51
|
+
toolCalls: Map<string, InternalToolCallState>;
|
|
52
|
+
toolCallOrder: Array<string>;
|
|
53
|
+
hasToolCallsSinceTextStart: boolean;
|
|
54
|
+
isComplete: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Result from processing a stream
|
|
58
|
+
*/
|
|
59
|
+
export interface ProcessorResult {
|
|
60
|
+
content: string;
|
|
61
|
+
thinking?: string;
|
|
62
|
+
toolCalls?: Array<ToolCall>;
|
|
63
|
+
finishReason?: string | null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Current state of the processor
|
|
67
|
+
*/
|
|
68
|
+
export interface ProcessorState {
|
|
69
|
+
content: string;
|
|
70
|
+
thinking: string;
|
|
71
|
+
toolCalls: Map<string, InternalToolCallState>;
|
|
72
|
+
toolCallOrder: Array<string>;
|
|
73
|
+
finishReason: string | null;
|
|
74
|
+
done: boolean;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Recording format for replay testing
|
|
78
|
+
*/
|
|
79
|
+
export interface ChunkRecording {
|
|
80
|
+
version: '1.0';
|
|
81
|
+
timestamp: number;
|
|
82
|
+
model?: string;
|
|
83
|
+
provider?: string;
|
|
84
|
+
chunks: Array<{
|
|
85
|
+
chunk: StreamChunk;
|
|
86
|
+
timestamp: number;
|
|
87
|
+
index: number;
|
|
88
|
+
}>;
|
|
89
|
+
result?: ProcessorResult;
|
|
90
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { AnyTool, LazyToolsConfig, Tool } from '../../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Name of the synthetic tool the LLM calls to discover lazy tools.
|
|
4
|
+
*
|
|
5
|
+
* Exported so callers building custom message-compaction / history-trimming
|
|
6
|
+
* logic can reference the discovery tool by constant instead of hard-coding
|
|
7
|
+
* the string (which is an internal contract that could change).
|
|
8
|
+
*/
|
|
9
|
+
export declare const DISCOVERY_TOOL_NAME = "__lazy__tool__discovery__";
|
|
10
|
+
/**
|
|
11
|
+
* Manages lazy tool discovery for the chat agent loop.
|
|
12
|
+
*
|
|
13
|
+
* Lazy tools are not sent to the LLM initially. Instead, a synthetic
|
|
14
|
+
* "discovery tool" is provided that lets the LLM discover lazy tools
|
|
15
|
+
* by name, receiving their full descriptions and schemas on demand.
|
|
16
|
+
*/
|
|
17
|
+
export declare class LazyToolManager {
|
|
18
|
+
private readonly eagerTools;
|
|
19
|
+
private readonly lazyToolMap;
|
|
20
|
+
private readonly discoveredTools;
|
|
21
|
+
private hasNewDiscoveries;
|
|
22
|
+
private readonly discoveryTool;
|
|
23
|
+
private readonly lazyToolsConfig;
|
|
24
|
+
constructor(tools: ReadonlyArray<Tool>, messages: ReadonlyArray<{
|
|
25
|
+
role: string;
|
|
26
|
+
content?: any;
|
|
27
|
+
toolCalls?: Array<{
|
|
28
|
+
id: string;
|
|
29
|
+
type: string;
|
|
30
|
+
function: {
|
|
31
|
+
name: string;
|
|
32
|
+
arguments: string;
|
|
33
|
+
};
|
|
34
|
+
}>;
|
|
35
|
+
toolCallId?: string;
|
|
36
|
+
}>, lazyToolsConfig?: LazyToolsConfig);
|
|
37
|
+
/**
|
|
38
|
+
* Returns the set of tools that should be sent to the LLM:
|
|
39
|
+
* eager tools + discovered lazy tools + discovery tool (if undiscovered tools remain).
|
|
40
|
+
* Resets the hasNewDiscoveries flag.
|
|
41
|
+
*/
|
|
42
|
+
getActiveTools(): Array<Tool>;
|
|
43
|
+
/**
|
|
44
|
+
* Returns the tools that should be available for *execution* this turn.
|
|
45
|
+
*
|
|
46
|
+
* This is the advertised set (`getActiveTools()`, passed in as `activeTools`)
|
|
47
|
+
* plus the discovery tool when a pending call references it but it is no
|
|
48
|
+
* longer advertised. Once every lazy tool has been discovered the discovery
|
|
49
|
+
* tool is dropped from the advertised set, but a model may still re-request
|
|
50
|
+
* discovery (long context / hallucination); keeping it executable lets that
|
|
51
|
+
* call return the schemas again instead of failing with "Unknown tool".
|
|
52
|
+
*
|
|
53
|
+
* The advertised set is intentionally left unchanged — only execution lookup
|
|
54
|
+
* is widened. Operates on the already-built `activeTools`: it must NOT call
|
|
55
|
+
* `getActiveTools()`, which would reset `hasNewDiscoveries` before the
|
|
56
|
+
* post-execution refresh check in the agent loop.
|
|
57
|
+
*/
|
|
58
|
+
getExecutableTools(activeTools: ReadonlyArray<AnyTool>, pendingToolCallNames: ReadonlyArray<string>): ReadonlyArray<AnyTool>;
|
|
59
|
+
/**
|
|
60
|
+
* Returns whether new tools have been discovered since the last getActiveTools() call.
|
|
61
|
+
*/
|
|
62
|
+
hasNewlyDiscoveredTools(): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Returns true if the given name is a lazy tool that has not yet been discovered.
|
|
65
|
+
*/
|
|
66
|
+
isUndiscoveredLazyTool(name: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Returns a helpful error message for when an undiscovered lazy tool is called.
|
|
69
|
+
*/
|
|
70
|
+
getUndiscoveredToolError(name: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Scans message history to find previously discovered lazy tools.
|
|
73
|
+
* Looks for assistant messages with discovery tool calls and their
|
|
74
|
+
* corresponding tool result messages.
|
|
75
|
+
*/
|
|
76
|
+
private scanMessageHistory;
|
|
77
|
+
/**
|
|
78
|
+
* Creates the synthetic discovery tool that the LLM can call
|
|
79
|
+
* to discover lazy tools' descriptions and schemas.
|
|
80
|
+
*/
|
|
81
|
+
private createDiscoveryTool;
|
|
82
|
+
}
|