@tanstack/ai 0.19.1 → 0.20.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/adapter.d.ts +9 -3
- package/dist/esm/activities/chat/adapter.js +3 -1
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +13 -4
- package/dist/esm/activities/chat/index.js +46 -18
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +1 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.js +2 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +8 -7
- package/dist/esm/activities/chat/stream/processor.d.ts +8 -8
- package/dist/esm/activities/chat/stream/processor.js +29 -21
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/strategies.d.ts +3 -3
- package/dist/esm/activities/chat/stream/strategies.js +4 -4
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +5 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +36 -35
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +2 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +17 -9
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.js +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/generateAudio/adapter.js +3 -1
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +5 -1
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.js +3 -1
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js +5 -0
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +3 -1
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +3 -1
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.js +3 -1
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.js +6 -2
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.js +3 -1
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +5 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger/internal-logger.js +2 -0
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/middlewares/content-guard.js +5 -4
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/otel.js +38 -16
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +1 -1
- package/dist/esm/realtime/index.js.map +1 -1
- 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/tools/provider-tool.d.ts +9 -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 +22 -7
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/package.json +2 -2
- package/src/activities/chat/adapter.ts +11 -2
- package/src/activities/chat/index.ts +46 -27
- package/src/activities/chat/messages.ts +2 -1
- package/src/activities/chat/middleware/types.ts +14 -11
- package/src/activities/chat/stream/processor.ts +19 -19
- package/src/activities/chat/stream/strategies.ts +3 -3
- package/src/activities/chat/tools/schema-converter.ts +77 -52
- package/src/activities/chat/tools/tool-calls.ts +18 -11
- package/src/activities/chat/tools/tool-definition.ts +6 -1
- package/src/activities/generateAudio/index.ts +5 -4
- package/src/activities/generateImage/index.ts +5 -0
- package/src/activities/stream-generation-result.ts +11 -2
- package/src/activities/summarize/chat-stream-summarize.ts +5 -2
- package/src/activities/summarize/index.ts +2 -2
- package/src/index.ts +5 -0
- package/src/middlewares/content-guard.ts +11 -3
- package/src/middlewares/otel.ts +47 -16
- package/src/realtime/index.ts +1 -1
- package/src/system-prompts.ts +98 -0
- package/src/tools/provider-tool.ts +14 -0
- package/src/types.ts +28 -9
- package/src/utilities/ag-ui-wire.ts +2 -2
- package/src/utilities/chat-params.ts +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sources":["../../../../src/activities/chat/messages.ts"],"sourcesContent":["import type {\n ContentPart,\n MessagePart,\n ModelMessage,\n TextPart,\n ToolCallPart,\n UIMessage,\n} from '../../types'\n// ===========================\n// Message Converters\n// ===========================\n\n/**\n * Check if a MessagePart is a content part (text, image, audio, video, document)\n * that maps directly to a ModelMessage ContentPart.\n */\nfunction isContentPart(part: MessagePart): part is ContentPart {\n return (\n part.type === 'text' ||\n part.type === 'image' ||\n part.type === 'audio' ||\n part.type === 'video' ||\n part.type === 'document'\n )\n}\n\nfunction safeJsonStringify(value: unknown): string {\n try {\n return JSON.stringify(value)\n } catch {\n return ''\n }\n}\n\n/**\n * Collapse an array of ContentParts into the most compact ModelMessage content:\n * - Empty array → null\n * - All text parts → joined string (or null if empty)\n * - Mixed content → ContentPart array as-is\n */\nfunction collapseContentParts(\n parts: Array<ContentPart>,\n): string | null | Array<ContentPart> {\n if (parts.length === 0) return null\n\n const allText = parts.every((p) => p.type === 'text')\n if (allText) {\n const joined = parts.map((p) => p.content).join('')\n return joined || null\n }\n\n return parts\n}\n\n/**\n * Extract text content from ModelMessage content (string, null, or ContentPart array).\n * Used when only the text portion is needed (e.g., tool result content).\n */\nfunction getTextContent(content: string | null | Array<ContentPart>): string {\n if (content === null) return ''\n if (typeof content === 'string') return content\n return content\n .filter((part): part is TextPart => part.type === 'text')\n .map((part) => part.content)\n .join('')\n}\n\n/**\n * Convert UIMessages or ModelMessages to ModelMessages\n */\nexport function convertMessagesToModelMessages(\n messages: Array<UIMessage | ModelMessage>,\n): Array<ModelMessage> {\n // Pre-pass: collect toolCallIds already represented in anchor UIMessage parts.\n // Fan-out tool messages whose toolCallId matches an anchored ToolResultPart\n // are AG-UI duplicates and must be dropped to avoid double-feeding the LLM.\n const anchoredToolCallIds = new Set<string>()\n for (const msg of messages) {\n if ('parts' in msg) {\n for (const part of msg.parts) {\n if (part.type === 'tool-result') {\n anchoredToolCallIds.add(part.toolCallId)\n }\n }\n }\n }\n\n const modelMessages: Array<ModelMessage> = []\n for (const msg of messages) {\n if ('parts' in msg) {\n // UIMessage anchor — existing fan-out path\n modelMessages.push(...uiMessageToModelMessages(msg))\n continue\n }\n\n const role = (msg as { role: string }).role\n\n // AG-UI tool fan-out duplicate — drop if anchor already covers it\n if (\n role === 'tool' &&\n msg.toolCallId &&\n anchoredToolCallIds.has(msg.toolCallId)\n ) {\n continue\n }\n\n // AG-UI reasoning and activity — no ModelMessage equivalent today\n if (role === 'reasoning' || role === 'activity') {\n continue\n }\n\n // AG-UI developer — collapse to system\n if (role === 'developer') {\n modelMessages.push({\n role: 'system' as ModelMessage['role'],\n content: (msg as { content: string }).content,\n } as ModelMessage)\n continue\n }\n\n // Already a ModelMessage (user, assistant, system, tool with no anchor) — pass through\n modelMessages.push(msg)\n }\n return modelMessages\n}\n\n/**\n * Convert a UIMessage to ModelMessage(s)\n *\n * Walks the parts array IN ORDER to preserve the interleaving of text,\n * tool calls, and tool results. This is critical for multi-round tool\n * flows where the model generates text, calls a tool, gets the result,\n * then generates more text and calls another tool.\n *\n * The output preserves the sequential structure:\n * text1 → toolCall1 → toolResult1 → text2 → toolCall2 → toolResult2\n * becomes:\n * assistant: {content: \"text1\", toolCalls: [toolCall1]}\n * tool: toolResult1\n * assistant: {content: \"text2\", toolCalls: [toolCall2]}\n * tool: toolResult2\n *\n * @param uiMessage - The UIMessage to convert\n * @returns An array of ModelMessages preserving part ordering\n */\nexport function uiMessageToModelMessages(\n uiMessage: UIMessage,\n): Array<ModelMessage> {\n // Skip system messages - they're handled via systemPrompts, not ModelMessages\n if (uiMessage.role === 'system') {\n return []\n }\n\n // For non-assistant messages (user), use the simpler path since they\n // don't have tool calls or tool results to interleave\n if (uiMessage.role !== 'assistant') {\n return [buildUserOrToolMessage(uiMessage)]\n }\n\n // For assistant messages, walk parts in order to preserve interleaving\n return buildAssistantMessages(uiMessage)\n}\n\n/**\n * Build a single ModelMessage for user messages (simple path).\n * Preserves ordering of text and multimodal content parts.\n */\nfunction buildUserOrToolMessage(uiMessage: UIMessage): ModelMessage {\n const contentParts: Array<ContentPart> = []\n for (const part of uiMessage.parts) {\n if (isContentPart(part)) {\n contentParts.push(part)\n }\n }\n\n return {\n role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n }\n}\n\n// Accumulator for building an assistant segment (content + tool calls)\ninterface AssistantSegment {\n contentParts: Array<ContentPart>\n toolCalls: Array<{\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n /** Provider-specific metadata that round-trips with the tool call.\n * Untyped at this framework layer; adapters narrow it via their\n * `TToolCallMetadata` generic. */\n metadata?: unknown\n }>\n}\n\nfunction createSegment(): AssistantSegment {\n return { contentParts: [], toolCalls: [] }\n}\n\nfunction isToolCallIncluded(part: ToolCallPart): boolean {\n return (\n part.state === 'input-complete' ||\n part.state === 'approval-responded' ||\n part.output !== undefined\n )\n}\n\n/**\n * Build ModelMessages for an assistant UIMessage, preserving the\n * sequential interleaving of text, tool calls, and tool results.\n *\n * Walks parts in order. Text and tool-call parts accumulate into the\n * current \"segment\". When a tool-result part is encountered, the\n * current segment is flushed as an assistant message, then the tool\n * result is emitted as a tool message.\n */\nfunction buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {\n const messageList: Array<ModelMessage> = []\n let current = createSegment()\n let pendingThinking: Array<{ content: string; signature?: string }> = []\n\n // Track emitted tool result IDs to avoid duplicates.\n // A tool call can have BOTH an explicit tool-result part AND an output\n // field on the tool-call part. We only want one per tool call ID.\n const emittedToolResultIds = new Set<string>()\n\n function flushSegment(): void {\n const content = collapseContentParts(current.contentParts)\n const hasContent = content !== null\n const hasToolCalls = current.toolCalls.length > 0\n\n if (hasContent || hasToolCalls) {\n messageList.push({\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\n })\n pendingThinking = []\n }\n current = createSegment()\n }\n\n for (const part of uiMessage.parts) {\n switch (part.type) {\n case 'text':\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n current.contentParts.push(part)\n break\n\n case 'tool-call':\n if (isToolCallIncluded(part)) {\n current.toolCalls.push({\n id: part.id,\n type: 'function' as const,\n function: {\n name: part.name,\n arguments: part.arguments,\n },\n ...(part.metadata !== undefined && { metadata: part.metadata }),\n })\n }\n break\n\n case 'tool-result':\n // Flush the current assistant segment before emitting the tool result\n flushSegment()\n\n // Emit the tool result\n if (\n (part.state === 'complete' || part.state === 'error') &&\n !emittedToolResultIds.has(part.toolCallId)\n ) {\n messageList.push({\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n })\n emittedToolResultIds.add(part.toolCallId)\n }\n break\n\n case 'thinking':\n if (part.content) {\n pendingThinking.push({\n content: part.content,\n ...(part.signature && { signature: part.signature }),\n })\n }\n break\n\n case 'structured-output':\n // Only emit completed structured responses into history. Streaming or\n // errored buffers would push malformed JSON into the next LLM turn's\n // assistant content. `raw` is the source of truth; `data` is the\n // defensive fallback for terminal-only completes that didn't ship raw.\n if (part.status === 'complete') {\n const serialized =\n part.raw !== ''\n ? part.raw\n : part.data !== undefined\n ? safeJsonStringify(part.data)\n : ''\n if (serialized !== '') {\n current.contentParts.push({ type: 'text', content: serialized })\n }\n }\n break\n\n default:\n break\n }\n }\n\n // Flush any remaining accumulated content\n flushSegment()\n\n // Emit tool results from client tool-call parts with output or approval,\n // but only if not already covered by an explicit tool-result part above.\n // These are appended at the end since they don't have explicit tool-result\n // parts in the parts array to trigger inline emission.\n for (const part of uiMessage.parts) {\n if (part.type !== 'tool-call') continue\n\n // Output takes priority — if the tool has already produced a result,\n // emit the concrete output regardless of approval metadata.\n if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {\n messageList.push({\n role: 'tool',\n content: JSON.stringify(part.output),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response without output — emit approval status for iteration tracking\n if (\n part.output === undefined &&\n part.state === 'approval-responded' &&\n part.approval?.approved !== undefined &&\n !emittedToolResultIds.has(part.id)\n ) {\n const approved = part.approval.approved\n messageList.push({\n role: 'tool',\n content: JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n }),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n }\n\n // If no messages were produced (e.g., empty parts), emit a minimal assistant message\n if (messageList.length === 0) {\n messageList.push({\n role: 'assistant',\n content: null,\n })\n }\n\n return messageList\n}\n\n/**\n * Convert a ModelMessage to UIMessage\n *\n * This conversion creates a parts-based structure:\n * - content field → TextPart\n * - toolCalls array → ToolCallPart[]\n * - role=\"tool\" messages should be converted separately and merged\n *\n * @param modelMessage - The ModelMessage to convert\n * @param id - Optional ID for the UIMessage (generated if not provided)\n * @returns A UIMessage with parts\n */\nexport function modelMessageToUIMessage(\n modelMessage: ModelMessage,\n id?: string,\n): UIMessage {\n const parts: Array<MessagePart> = []\n\n if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {\n for (const thinking of modelMessage.thinking) {\n if (!thinking.content) continue\n parts.push({\n type: 'thinking',\n content: thinking.content,\n ...(thinking.signature && { signature: thinking.signature }),\n })\n }\n }\n\n // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n parts.push({\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n state: 'input-complete', // Model messages have complete arguments\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n }\n }\n\n return {\n id: id || generateMessageId(),\n role: modelMessage.role === 'tool' ? 'assistant' : modelMessage.role,\n parts,\n }\n}\n\n/**\n * Convert an array of ModelMessages to UIMessages\n *\n * This handles merging tool result messages with their corresponding assistant messages\n *\n * @param modelMessages - Array of ModelMessages to convert\n * @returns Array of UIMessages\n */\nexport function modelMessagesToUIMessages(\n modelMessages: Array<ModelMessage>,\n): Array<UIMessage> {\n const uiMessages: Array<UIMessage> = []\n let currentAssistantMessage: UIMessage | null = null\n\n for (const msg of modelMessages) {\n if (msg.role === 'tool') {\n // Tool result - merge into the last assistant message if possible\n if (\n currentAssistantMessage &&\n currentAssistantMessage.role === 'assistant'\n ) {\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId!,\n content: getTextContent(msg.content),\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message\n const uiMessage = modelMessageToUIMessage(msg)\n uiMessages.push(uiMessage)\n\n // Track assistant messages for potential tool result merging\n if (msg.role === 'assistant') {\n currentAssistantMessage = uiMessage\n } else {\n currentAssistantMessage = null\n }\n }\n }\n\n return uiMessages\n}\n\n/**\n * Normalize a message (UIMessage or ModelMessage) to a UIMessage\n * Ensures the message has an ID and createdAt timestamp\n *\n * @param message - Either a UIMessage or ModelMessage\n * @param generateId - Function to generate a message ID if needed\n * @returns A UIMessage with guaranteed id and createdAt\n */\nexport function normalizeToUIMessage(\n message: UIMessage | ModelMessage,\n generateId: () => string,\n): UIMessage {\n if ('parts' in message) {\n // Already a UIMessage\n return {\n ...message,\n id: message.id || generateId(),\n createdAt: message.createdAt || new Date(),\n }\n } else {\n // ModelMessage - convert to UIMessage\n return {\n ...modelMessageToUIMessage(message, generateId()),\n createdAt: new Date(),\n }\n }\n}\n\n/**\n * Generate a unique message ID\n */\nexport function generateMessageId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"messages.js","sources":["../../../../src/activities/chat/messages.ts"],"sourcesContent":["import type {\n ContentPart,\n MessagePart,\n ModelMessage,\n TextPart,\n ToolCallPart,\n UIMessage,\n} from '../../types'\n// ===========================\n// Message Converters\n// ===========================\n\n/**\n * Check if a MessagePart is a content part (text, image, audio, video, document)\n * that maps directly to a ModelMessage ContentPart.\n */\nfunction isContentPart(part: MessagePart): part is ContentPart {\n return (\n part.type === 'text' ||\n part.type === 'image' ||\n part.type === 'audio' ||\n part.type === 'video' ||\n part.type === 'document'\n )\n}\n\nfunction safeJsonStringify(value: unknown): string {\n try {\n return JSON.stringify(value)\n } catch {\n return ''\n }\n}\n\n/**\n * Collapse an array of ContentParts into the most compact ModelMessage content:\n * - Empty array → null\n * - All text parts → joined string (or null if empty)\n * - Mixed content → ContentPart array as-is\n */\nfunction collapseContentParts(\n parts: Array<ContentPart>,\n): string | null | Array<ContentPart> {\n if (parts.length === 0) return null\n\n const allText = parts.every((p) => p.type === 'text')\n if (allText) {\n const joined = parts.map((p) => p.content).join('')\n return joined || null\n }\n\n return parts\n}\n\n/**\n * Extract text content from ModelMessage content (string, null, or ContentPart array).\n * Used when only the text portion is needed (e.g., tool result content).\n */\nfunction getTextContent(content: string | null | Array<ContentPart>): string {\n if (content === null) return ''\n if (typeof content === 'string') return content\n return content\n .filter((part): part is TextPart => part.type === 'text')\n .map((part) => part.content)\n .join('')\n}\n\n/**\n * Convert UIMessages or ModelMessages to ModelMessages\n */\nexport function convertMessagesToModelMessages(\n messages: Array<UIMessage | ModelMessage>,\n): Array<ModelMessage> {\n // Pre-pass: collect toolCallIds already represented in anchor UIMessage parts.\n // Fan-out tool messages whose toolCallId matches an anchored ToolResultPart\n // are AG-UI duplicates and must be dropped to avoid double-feeding the LLM.\n const anchoredToolCallIds = new Set<string>()\n for (const msg of messages) {\n if ('parts' in msg) {\n for (const part of msg.parts) {\n if (part.type === 'tool-result') {\n anchoredToolCallIds.add(part.toolCallId)\n }\n }\n }\n }\n\n const modelMessages: Array<ModelMessage> = []\n for (const msg of messages) {\n if ('parts' in msg) {\n // UIMessage anchor — existing fan-out path\n modelMessages.push(...uiMessageToModelMessages(msg))\n continue\n }\n\n const role = (msg as { role: string }).role\n\n // AG-UI tool fan-out duplicate — drop if anchor already covers it\n if (\n role === 'tool' &&\n msg.toolCallId &&\n anchoredToolCallIds.has(msg.toolCallId)\n ) {\n continue\n }\n\n // AG-UI reasoning and activity — no ModelMessage equivalent today\n if (role === 'reasoning' || role === 'activity') {\n continue\n }\n\n // AG-UI developer — collapse to system\n if (role === 'developer') {\n modelMessages.push({\n role: 'system' as ModelMessage['role'],\n content: (msg as { content: string }).content,\n } as ModelMessage)\n continue\n }\n\n // Already a ModelMessage (user, assistant, system, tool with no anchor) — pass through\n modelMessages.push(msg)\n }\n return modelMessages\n}\n\n/**\n * Convert a UIMessage to ModelMessage(s)\n *\n * Walks the parts array IN ORDER to preserve the interleaving of text,\n * tool calls, and tool results. This is critical for multi-round tool\n * flows where the model generates text, calls a tool, gets the result,\n * then generates more text and calls another tool.\n *\n * The output preserves the sequential structure:\n * text1 → toolCall1 → toolResult1 → text2 → toolCall2 → toolResult2\n * becomes:\n * assistant: {content: \"text1\", toolCalls: [toolCall1]}\n * tool: toolResult1\n * assistant: {content: \"text2\", toolCalls: [toolCall2]}\n * tool: toolResult2\n *\n * @param uiMessage - The UIMessage to convert\n * @returns An array of ModelMessages preserving part ordering\n */\nexport function uiMessageToModelMessages(\n uiMessage: UIMessage,\n): Array<ModelMessage> {\n // Skip system messages - they're handled via systemPrompts, not ModelMessages\n if (uiMessage.role === 'system') {\n return []\n }\n\n // For non-assistant messages (user), use the simpler path since they\n // don't have tool calls or tool results to interleave\n if (uiMessage.role !== 'assistant') {\n return [buildUserOrToolMessage(uiMessage)]\n }\n\n // For assistant messages, walk parts in order to preserve interleaving\n return buildAssistantMessages(uiMessage)\n}\n\n/**\n * Build a single ModelMessage for user messages (simple path).\n * Preserves ordering of text and multimodal content parts.\n */\nfunction buildUserOrToolMessage(uiMessage: UIMessage): ModelMessage {\n const contentParts: Array<ContentPart> = []\n for (const part of uiMessage.parts) {\n if (isContentPart(part)) {\n contentParts.push(part)\n }\n }\n\n return {\n role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n }\n}\n\n// Accumulator for building an assistant segment (content + tool calls)\ninterface AssistantSegment {\n contentParts: Array<ContentPart>\n toolCalls: Array<{\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n /** Provider-specific metadata that round-trips with the tool call.\n * Untyped at this framework layer; adapters narrow it via their\n * `TToolCallMetadata` generic. */\n metadata?: unknown\n }>\n}\n\nfunction createSegment(): AssistantSegment {\n return { contentParts: [], toolCalls: [] }\n}\n\nfunction isToolCallIncluded(part: ToolCallPart): boolean {\n return (\n part.state === 'input-complete' ||\n part.state === 'approval-responded' ||\n part.output !== undefined\n )\n}\n\n/**\n * Build ModelMessages for an assistant UIMessage, preserving the\n * sequential interleaving of text, tool calls, and tool results.\n *\n * Walks parts in order. Text and tool-call parts accumulate into the\n * current \"segment\". When a tool-result part is encountered, the\n * current segment is flushed as an assistant message, then the tool\n * result is emitted as a tool message.\n */\nfunction buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {\n const messageList: Array<ModelMessage> = []\n let current = createSegment()\n let pendingThinking: Array<{ content: string; signature?: string }> = []\n\n // Track emitted tool result IDs to avoid duplicates.\n // A tool call can have BOTH an explicit tool-result part AND an output\n // field on the tool-call part. We only want one per tool call ID.\n const emittedToolResultIds = new Set<string>()\n\n function flushSegment(): void {\n const content = collapseContentParts(current.contentParts)\n const hasContent = content !== null\n const hasToolCalls = current.toolCalls.length > 0\n\n if (hasContent || hasToolCalls) {\n messageList.push({\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\n })\n pendingThinking = []\n }\n current = createSegment()\n }\n\n for (const part of uiMessage.parts) {\n switch (part.type) {\n case 'text':\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n current.contentParts.push(part)\n break\n\n case 'tool-call':\n if (isToolCallIncluded(part)) {\n current.toolCalls.push({\n id: part.id,\n type: 'function' as const,\n function: {\n name: part.name,\n arguments: part.arguments,\n },\n ...(part.metadata !== undefined && { metadata: part.metadata }),\n })\n }\n break\n\n case 'tool-result':\n // Flush the current assistant segment before emitting the tool result\n flushSegment()\n\n // Emit the tool result\n if (\n (part.state === 'complete' || part.state === 'error') &&\n !emittedToolResultIds.has(part.toolCallId)\n ) {\n messageList.push({\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n })\n emittedToolResultIds.add(part.toolCallId)\n }\n break\n\n case 'thinking':\n if (part.content) {\n pendingThinking.push({\n content: part.content,\n ...(part.signature && { signature: part.signature }),\n })\n }\n break\n\n case 'structured-output':\n // Only emit completed structured responses into history. Streaming or\n // errored buffers would push malformed JSON into the next LLM turn's\n // assistant content. `raw` is the source of truth; `data` is the\n // defensive fallback for terminal-only completes that didn't ship raw.\n if (part.status === 'complete') {\n const serialized =\n part.raw !== ''\n ? part.raw\n : part.data !== undefined\n ? safeJsonStringify(part.data)\n : ''\n if (serialized !== '') {\n current.contentParts.push({ type: 'text', content: serialized })\n }\n }\n break\n\n default:\n break\n }\n }\n\n // Flush any remaining accumulated content\n flushSegment()\n\n // Emit tool results from client tool-call parts with output or approval,\n // but only if not already covered by an explicit tool-result part above.\n // These are appended at the end since they don't have explicit tool-result\n // parts in the parts array to trigger inline emission.\n for (const part of uiMessage.parts) {\n if (part.type !== 'tool-call') continue\n\n // Output takes priority — if the tool has already produced a result,\n // emit the concrete output regardless of approval metadata.\n if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {\n messageList.push({\n role: 'tool',\n content: JSON.stringify(part.output),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response without output — emit approval status for iteration tracking\n if (\n part.output === undefined &&\n part.state === 'approval-responded' &&\n part.approval?.approved !== undefined &&\n !emittedToolResultIds.has(part.id)\n ) {\n const approved = part.approval.approved\n messageList.push({\n role: 'tool',\n content: JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n }),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n }\n\n // If no messages were produced (e.g., empty parts), emit a minimal assistant message\n if (messageList.length === 0) {\n messageList.push({\n role: 'assistant',\n content: null,\n })\n }\n\n return messageList\n}\n\n/**\n * Convert a ModelMessage to UIMessage\n *\n * This conversion creates a parts-based structure:\n * - content field → TextPart\n * - toolCalls array → ToolCallPart[]\n * - role=\"tool\" messages should be converted separately and merged\n *\n * @param modelMessage - The ModelMessage to convert\n * @param id - Optional ID for the UIMessage (generated if not provided)\n * @returns A UIMessage with parts\n */\nexport function modelMessageToUIMessage(\n modelMessage: ModelMessage,\n id?: string,\n): UIMessage {\n const parts: Array<MessagePart> = []\n\n if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {\n for (const thinking of modelMessage.thinking) {\n if (!thinking.content) continue\n parts.push({\n type: 'thinking',\n content: thinking.content,\n ...(thinking.signature && { signature: thinking.signature }),\n })\n }\n }\n\n // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n parts.push({\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n state: 'input-complete', // Model messages have complete arguments\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n }\n }\n\n return {\n id: id || generateMessageId(),\n role: modelMessage.role === 'tool' ? 'assistant' : modelMessage.role,\n parts,\n }\n}\n\n/**\n * Convert an array of ModelMessages to UIMessages\n *\n * This handles merging tool result messages with their corresponding assistant messages\n *\n * @param modelMessages - Array of ModelMessages to convert\n * @returns Array of UIMessages\n */\nexport function modelMessagesToUIMessages(\n modelMessages: Array<ModelMessage>,\n): Array<UIMessage> {\n const uiMessages: Array<UIMessage> = []\n let currentAssistantMessage: UIMessage | null = null\n\n for (const msg of modelMessages) {\n if (msg.role === 'tool') {\n // Tool result - merge into the last assistant message if possible\n if (\n msg.toolCallId !== undefined &&\n currentAssistantMessage &&\n currentAssistantMessage.role === 'assistant'\n ) {\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId,\n content: getTextContent(msg.content),\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message\n const uiMessage = modelMessageToUIMessage(msg)\n uiMessages.push(uiMessage)\n\n // Track assistant messages for potential tool result merging\n if (msg.role === 'assistant') {\n currentAssistantMessage = uiMessage\n } else {\n currentAssistantMessage = null\n }\n }\n }\n\n return uiMessages\n}\n\n/**\n * Normalize a message (UIMessage or ModelMessage) to a UIMessage\n * Ensures the message has an ID and createdAt timestamp\n *\n * @param message - Either a UIMessage or ModelMessage\n * @param generateId - Function to generate a message ID if needed\n * @returns A UIMessage with guaranteed id and createdAt\n */\nexport function normalizeToUIMessage(\n message: UIMessage | ModelMessage,\n generateId: () => string,\n): UIMessage {\n if ('parts' in message) {\n // Already a UIMessage\n return {\n ...message,\n id: message.id || generateId(),\n createdAt: message.createdAt || new Date(),\n }\n } else {\n // ModelMessage - convert to UIMessage\n return {\n ...modelMessageToUIMessage(message, generateId()),\n createdAt: new Date(),\n }\n }\n}\n\n/**\n * Generate a unique message ID\n */\nexport function generateMessageId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"names":[],"mappings":"AAgBA,SAAS,cAAc,MAAwC;AAC7D,SACE,KAAK,SAAS,UACd,KAAK,SAAS,WACd,KAAK,SAAS,WACd,KAAK,SAAS,WACd,KAAK,SAAS;AAElB;AAEA,SAAS,kBAAkB,OAAwB;AACjD,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,SAAS,qBACP,OACoC;AACpC,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,UAAU,MAAM,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM;AACpD,MAAI,SAAS;AACX,UAAM,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE;AAClD,WAAO,UAAU;AAAA,EACnB;AAEA,SAAO;AACT;AAMA,SAAS,eAAe,SAAqD;AAC3E,MAAI,YAAY,KAAM,QAAO;AAC7B,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,SAAO,QACJ,OAAO,CAAC,SAA2B,KAAK,SAAS,MAAM,EACvD,IAAI,CAAC,SAAS,KAAK,OAAO,EAC1B,KAAK,EAAE;AACZ;AAKO,SAAS,+BACd,UACqB;AAIrB,QAAM,0CAA0B,IAAA;AAChC,aAAW,OAAO,UAAU;AAC1B,QAAI,WAAW,KAAK;AAClB,iBAAW,QAAQ,IAAI,OAAO;AAC5B,YAAI,KAAK,SAAS,eAAe;AAC/B,8BAAoB,IAAI,KAAK,UAAU;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAqC,CAAA;AAC3C,aAAW,OAAO,UAAU;AAC1B,QAAI,WAAW,KAAK;AAElB,oBAAc,KAAK,GAAG,yBAAyB,GAAG,CAAC;AACnD;AAAA,IACF;AAEA,UAAM,OAAQ,IAAyB;AAGvC,QACE,SAAS,UACT,IAAI,cACJ,oBAAoB,IAAI,IAAI,UAAU,GACtC;AACA;AAAA,IACF;AAGA,QAAI,SAAS,eAAe,SAAS,YAAY;AAC/C;AAAA,IACF;AAGA,QAAI,SAAS,aAAa;AACxB,oBAAc,KAAK;AAAA,QACjB,MAAM;AAAA,QACN,SAAU,IAA4B;AAAA,MAAA,CACvB;AACjB;AAAA,IACF;AAGA,kBAAc,KAAK,GAAG;AAAA,EACxB;AACA,SAAO;AACT;AAqBO,SAAS,yBACd,WACqB;AAErB,MAAI,UAAU,SAAS,UAAU;AAC/B,WAAO,CAAA;AAAA,EACT;AAIA,MAAI,UAAU,SAAS,aAAa;AAClC,WAAO,CAAC,uBAAuB,SAAS,CAAC;AAAA,EAC3C;AAGA,SAAO,uBAAuB,SAAS;AACzC;AAMA,SAAS,uBAAuB,WAAoC;AAClE,QAAM,eAAmC,CAAA;AACzC,aAAW,QAAQ,UAAU,OAAO;AAClC,QAAI,cAAc,IAAI,GAAG;AACvB,mBAAa,KAAK,IAAI;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,UAAU;AAAA,IAChB,SAAS,qBAAqB,YAAY;AAAA,EAAA;AAE9C;AAgBA,SAAS,gBAAkC;AACzC,SAAO,EAAE,cAAc,IAAI,WAAW,CAAA,EAAC;AACzC;AAEA,SAAS,mBAAmB,MAA6B;AACvD,SACE,KAAK,UAAU,oBACf,KAAK,UAAU,wBACf,KAAK,WAAW;AAEpB;AAWA,SAAS,uBAAuB,WAA2C;AACzE,QAAM,cAAmC,CAAA;AACzC,MAAI,UAAU,cAAA;AACd,MAAI,kBAAkE,CAAA;AAKtE,QAAM,2CAA2B,IAAA;AAEjC,WAAS,eAAqB;AAC5B,UAAM,UAAU,qBAAqB,QAAQ,YAAY;AACzD,UAAM,aAAa,YAAY;AAC/B,UAAM,eAAe,QAAQ,UAAU,SAAS;AAEhD,QAAI,cAAc,cAAc;AAC9B,kBAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN;AAAA,QACA,GAAI,gBAAgB,EAAE,WAAW,QAAQ,UAAA;AAAA,QACzC,GAAI,gBAAgB,SAAS,KAAK,EAAE,UAAU,gBAAA;AAAA,MAAgB,CAC/D;AACD,wBAAkB,CAAA;AAAA,IACpB;AACA,cAAU,cAAA;AAAA,EACZ;AAEA,aAAW,QAAQ,UAAU,OAAO;AAClC,YAAQ,KAAK,MAAA;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,gBAAQ,aAAa,KAAK,IAAI;AAC9B;AAAA,MAEF,KAAK;AACH,YAAI,mBAAmB,IAAI,GAAG;AAC5B,kBAAQ,UAAU,KAAK;AAAA,YACrB,IAAI,KAAK;AAAA,YACT,MAAM;AAAA,YACN,UAAU;AAAA,cACR,MAAM,KAAK;AAAA,cACX,WAAW,KAAK;AAAA,YAAA;AAAA,YAElB,GAAI,KAAK,aAAa,UAAa,EAAE,UAAU,KAAK,SAAA;AAAA,UAAS,CAC9D;AAAA,QACH;AACA;AAAA,MAEF,KAAK;AAEH,qBAAA;AAGA,aACG,KAAK,UAAU,cAAc,KAAK,UAAU,YAC7C,CAAC,qBAAqB,IAAI,KAAK,UAAU,GACzC;AACA,sBAAY,KAAK;AAAA,YACf,MAAM;AAAA,YACN,SAAS,KAAK;AAAA,YACd,YAAY,KAAK;AAAA,UAAA,CAClB;AACD,+BAAqB,IAAI,KAAK,UAAU;AAAA,QAC1C;AACA;AAAA,MAEF,KAAK;AACH,YAAI,KAAK,SAAS;AAChB,0BAAgB,KAAK;AAAA,YACnB,SAAS,KAAK;AAAA,YACd,GAAI,KAAK,aAAa,EAAE,WAAW,KAAK,UAAA;AAAA,UAAU,CACnD;AAAA,QACH;AACA;AAAA,MAEF,KAAK;AAKH,YAAI,KAAK,WAAW,YAAY;AAC9B,gBAAM,aACJ,KAAK,QAAQ,KACT,KAAK,MACL,KAAK,SAAS,SACZ,kBAAkB,KAAK,IAAI,IAC3B;AACR,cAAI,eAAe,IAAI;AACrB,oBAAQ,aAAa,KAAK,EAAE,MAAM,QAAQ,SAAS,YAAY;AAAA,UACjE;AAAA,QACF;AACA;AAAA,IAGA;AAAA,EAEN;AAGA,eAAA;AAMA,aAAW,QAAQ,UAAU,OAAO;AAClC,QAAI,KAAK,SAAS,YAAa;AAI/B,QAAI,KAAK,WAAW,UAAa,CAAC,qBAAqB,IAAI,KAAK,EAAE,GAAG;AACnE,kBAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS,KAAK,UAAU,KAAK,MAAM;AAAA,QACnC,YAAY,KAAK;AAAA,MAAA,CAClB;AACD,2BAAqB,IAAI,KAAK,EAAE;AAAA,IAClC;AAGA,QACE,KAAK,WAAW,UAChB,KAAK,UAAU,wBACf,KAAK,UAAU,aAAa,UAC5B,CAAC,qBAAqB,IAAI,KAAK,EAAE,GACjC;AACA,YAAM,WAAW,KAAK,SAAS;AAC/B,kBAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS,KAAK,UAAU;AAAA,UACtB;AAAA,UACA,GAAI,YAAY,EAAE,kBAAkB,KAAA;AAAA,UACpC,SAAS,WACL,8BACA;AAAA,QAAA,CACL;AAAA,QACD,YAAY,KAAK;AAAA,MAAA,CAClB;AACD,2BAAqB,IAAI,KAAK,EAAE;AAAA,IAClC;AAAA,EACF;AAGA,MAAI,YAAY,WAAW,GAAG;AAC5B,gBAAY,KAAK;AAAA,MACf,MAAM;AAAA,MACN,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAEA,SAAO;AACT;AAcO,SAAS,wBACd,cACA,IACW;AACX,QAAM,QAA4B,CAAA;AAElC,MAAI,aAAa,SAAS,eAAe,aAAa,UAAU,QAAQ;AACtE,eAAW,YAAY,aAAa,UAAU;AAC5C,UAAI,CAAC,SAAS,QAAS;AACvB,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,SAAS,SAAS;AAAA,QAClB,GAAI,SAAS,aAAa,EAAE,WAAW,SAAS,UAAA;AAAA,MAAU,CAC3D;AAAA,IACH;AAAA,EACF;AAIA,MAAI,aAAa,SAAS,UAAU,aAAa,YAAY;AAC3D,UAAM,KAAK;AAAA,MACT,MAAM;AAAA,MACN,YAAY,aAAa;AAAA,MACzB,SAAS,eAAe,aAAa,OAAO;AAAA,MAC5C,OAAO;AAAA,IAAA,CACR;AAAA,EACH,WAAW,MAAM,QAAQ,aAAa,OAAO,GAAG;AAE9C,eAAW,QAAQ,aAAa,SAAS;AACvC,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF,OAAO;AAEL,UAAM,cAAc,eAAe,aAAa,OAAO;AACvD,QAAI,aAAa;AACf,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,SAAS;AAAA,MAAA,CACV;AAAA,IACH;AAAA,EACF;AAGA,MAAI,aAAa,aAAa,aAAa,UAAU,SAAS,GAAG;AAC/D,eAAW,YAAY,aAAa,WAAW;AAC7C,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,IAAI,SAAS;AAAA,QACb,MAAM,SAAS,SAAS;AAAA,QACxB,WAAW,SAAS,SAAS;AAAA,QAC7B,OAAO;AAAA;AAAA,QACP,GAAI,SAAS,aAAa,UAAa,EAAE,UAAU,SAAS,SAAA;AAAA,MAAS,CACtE;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,MAAM,kBAAA;AAAA,IACV,MAAM,aAAa,SAAS,SAAS,cAAc,aAAa;AAAA,IAChE;AAAA,EAAA;AAEJ;AAUO,SAAS,0BACd,eACkB;AAClB,QAAM,aAA+B,CAAA;AACrC,MAAI,0BAA4C;AAEhD,aAAW,OAAO,eAAe;AAC/B,QAAI,IAAI,SAAS,QAAQ;AAEvB,UACE,IAAI,eAAe,UACnB,2BACA,wBAAwB,SAAS,aACjC;AACA,gCAAwB,MAAM,KAAK;AAAA,UACjC,MAAM;AAAA,UACN,YAAY,IAAI;AAAA,UAChB,SAAS,eAAe,IAAI,OAAO;AAAA,UACnC,OAAO;AAAA,QAAA,CACR;AAAA,MACH,OAAO;AAEL,cAAM,sBAAsB,wBAAwB,GAAG;AACvD,mBAAW,KAAK,mBAAmB;AAAA,MACrC;AAAA,IACF,OAAO;AAEL,YAAM,YAAY,wBAAwB,GAAG;AAC7C,iBAAW,KAAK,SAAS;AAGzB,UAAI,IAAI,SAAS,aAAa;AAC5B,kCAA0B;AAAA,MAC5B,OAAO;AACL,kCAA0B;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAUO,SAAS,qBACd,SACA,YACW;AACX,MAAI,WAAW,SAAS;AAEtB,WAAO;AAAA,MACL,GAAG;AAAA,MACH,IAAI,QAAQ,MAAM,WAAA;AAAA,MAClB,WAAW,QAAQ,aAAa,oBAAI,KAAA;AAAA,IAAK;AAAA,EAE7C,OAAO;AAEL,WAAO;AAAA,MACL,GAAG,wBAAwB,SAAS,YAAY;AAAA,MAChD,+BAAe,KAAA;AAAA,IAAK;AAAA,EAExB;AACF;AAKO,SAAS,oBAA4B;AAC1C,SAAO,OAAO,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AACrE;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","sources":["../../../../../src/activities/chat/middleware/compose.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai-event-client'\nimport type { StreamChunk } from '../../../types'\nimport type { InternalLogger } from '../../../logger/internal-logger'\nimport type {\n AbortInfo,\n AfterToolCallInfo,\n BeforeToolCallDecision,\n ChatMiddleware,\n ChatMiddlewareConfig,\n ChatMiddlewareContext,\n ErrorInfo,\n FinishInfo,\n IterationInfo,\n ToolCallHookContext,\n ToolPhaseCompleteInfo,\n UsageInfo,\n} from './types'\n\n/** Check if a middleware should be skipped for instrumentation events. */\nfunction shouldSkipInstrumentation(mw: ChatMiddleware): boolean {\n return mw.name === 'devtools' || mw.name === 'strip-to-spec'\n}\n\n/** Build the base context for middleware instrumentation events. */\nfunction instrumentCtx(ctx: ChatMiddlewareContext) {\n return {\n requestId: ctx.requestId,\n streamId: ctx.streamId,\n clientId: ctx.threadId,\n timestamp: Date.now(),\n }\n}\n\n/**\n * Internal middleware runner that manages composed execution of middleware hooks.\n * Created once per chat() invocation.\n */\nexport class MiddlewareRunner {\n private readonly middlewares: ReadonlyArray<ChatMiddleware>\n private readonly logger: InternalLogger\n\n constructor(\n middlewares: ReadonlyArray<ChatMiddleware>,\n logger: InternalLogger,\n ) {\n this.middlewares = middlewares\n this.logger = logger\n }\n\n get hasMiddleware(): boolean {\n return this.middlewares.length > 0\n }\n\n /**\n * Pipe config through all middleware onConfig hooks in order.\n * Each middleware receives the merged config from previous middleware.\n * Partial returns are shallow-merged with the current config.\n */\n async runOnConfig(\n ctx: ChatMiddlewareContext,\n config: ChatMiddlewareConfig,\n ): Promise<ChatMiddlewareConfig> {\n let current = config\n for (const mw of this.middlewares) {\n if (mw.onConfig) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onConfig(ctx, current)\n const hasTransform = result !== undefined && result !== null\n if (hasTransform) {\n current = { ...current, ...result }\n if (!skip) {\n this.logger.config(\n `middleware=${mw.name ?? 'unnamed'} keys=${Object.keys(result as object).join(',')}`,\n {\n middleware: mw.name ?? 'unnamed',\n changes: result,\n },\n )\n }\n }\n if (!skip) {\n const base = instrumentCtx(ctx)\n aiEventClient.emit('middleware:hook:executed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n hookName: 'onConfig',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform,\n })\n if (hasTransform) {\n aiEventClient.emit('middleware:config:transformed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n iteration: ctx.iteration,\n changes: result as Record<string, unknown>,\n })\n }\n }\n }\n }\n return current\n }\n\n /**\n * Call onStart on all middleware in order.\n */\n async runOnStart(ctx: ChatMiddlewareContext): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onStart) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onStart(ctx)\n if (!skip) {\n this.logger.middleware(\n `hook=onStart middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onStart' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onStart',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Pipe a single chunk through all middleware onChunk hooks in order.\n * Returns the resulting chunks (0..N) to yield to the consumer.\n *\n * - void: pass through unchanged\n * - chunk: replace with this chunk\n * - chunk[]: expand to multiple chunks\n * - null: drop the chunk entirely\n */\n async runOnChunk(\n ctx: ChatMiddlewareContext,\n chunk: StreamChunk,\n ): Promise<Array<StreamChunk>> {\n let chunks: Array<StreamChunk> = [chunk]\n\n for (const mw of this.middlewares) {\n if (!mw.onChunk) continue\n const skip = shouldSkipInstrumentation(mw)\n\n const nextChunks: Array<StreamChunk> = []\n for (const c of chunks) {\n // Cast: @ag-ui/core Zod passthrough types prevent direct `.type` access\n const chunkType = (c as StreamChunk & { type: string }).type\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onChunk', in: c },\n )\n }\n const result = await mw.onChunk(ctx, c)\n if (result === null) {\n // Drop this chunk\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=<dropped>`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n dropped: true,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: 0,\n wasDropped: true,\n })\n }\n continue\n } else if (result === undefined) {\n // Pass through — no instrumentation for pass-throughs\n nextChunks.push(c)\n } else if (Array.isArray(result)) {\n // Expand\n nextChunks.push(...result)\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=[${result.map((r: StreamChunk) => (r as StreamChunk & { type: string }).type).join(',')}]`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n in: c,\n out: result,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: result.length,\n wasDropped: false,\n })\n }\n } else {\n // Replace\n nextChunks.push(result)\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=${(result as StreamChunk & { type: string }).type}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n in: c,\n out: result,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: 1,\n wasDropped: false,\n })\n }\n }\n }\n chunks = nextChunks\n }\n\n return chunks\n }\n\n /**\n * Run onBeforeToolCall through middleware in order.\n * Returns the first non-void decision, or undefined to continue normally.\n */\n async runOnBeforeToolCall(\n ctx: ChatMiddlewareContext,\n hookCtx: ToolCallHookContext,\n ): Promise<BeforeToolCallDecision> {\n for (const mw of this.middlewares) {\n if (mw.onBeforeToolCall) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const decision = await mw.onBeforeToolCall(ctx, hookCtx)\n const hasTransform = decision !== undefined && decision !== null\n if (!skip) {\n this.logger.middleware(\n `hook=onBeforeToolCall middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onBeforeToolCall' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onBeforeToolCall',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform,\n })\n }\n if (hasTransform) {\n return decision\n }\n }\n }\n return undefined\n }\n\n /**\n * Run onAfterToolCall on all middleware in order.\n */\n async runOnAfterToolCall(\n ctx: ChatMiddlewareContext,\n info: AfterToolCallInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onAfterToolCall) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onAfterToolCall(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onAfterToolCall middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onAfterToolCall' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onAfterToolCall',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onUsage on all middleware in order.\n */\n async runOnUsage(\n ctx: ChatMiddlewareContext,\n usage: UsageInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onUsage) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onUsage(ctx, usage)\n if (!skip) {\n this.logger.middleware(\n `hook=onUsage middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onUsage' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onUsage',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onFinish on all middleware in order.\n */\n async runOnFinish(\n ctx: ChatMiddlewareContext,\n info: FinishInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onFinish) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onFinish(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onFinish middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onFinish' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onFinish',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onAbort on all middleware in order.\n */\n async runOnAbort(ctx: ChatMiddlewareContext, info: AbortInfo): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onAbort) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onAbort(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onAbort middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onAbort' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onAbort',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onError on all middleware in order.\n */\n async runOnError(ctx: ChatMiddlewareContext, info: ErrorInfo): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onError) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onError(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onError middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onError' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onError',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onIteration on all middleware in order.\n * Called at the start of each agent loop iteration.\n */\n async runOnIteration(\n ctx: ChatMiddlewareContext,\n info: IterationInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onIteration) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onIteration(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onIteration middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onIteration' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onIteration',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onToolPhaseComplete on all middleware in order.\n * Called after all tool calls in an iteration have been processed.\n */\n async runOnToolPhaseComplete(\n ctx: ChatMiddlewareContext,\n info: ToolPhaseCompleteInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onToolPhaseComplete) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onToolPhaseComplete(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onToolPhaseComplete middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onToolPhaseComplete' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onToolPhaseComplete',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compose.js","sources":["../../../../../src/activities/chat/middleware/compose.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai-event-client'\nimport type { StreamChunk } from '../../../types'\nimport type { InternalLogger } from '../../../logger/internal-logger'\nimport type {\n AbortInfo,\n AfterToolCallInfo,\n BeforeToolCallDecision,\n ChatMiddleware,\n ChatMiddlewareConfig,\n ChatMiddlewareContext,\n ErrorInfo,\n FinishInfo,\n IterationInfo,\n ToolCallHookContext,\n ToolPhaseCompleteInfo,\n UsageInfo,\n} from './types'\n\n/** Check if a middleware should be skipped for instrumentation events. */\nfunction shouldSkipInstrumentation(mw: ChatMiddleware): boolean {\n return mw.name === 'devtools' || mw.name === 'strip-to-spec'\n}\n\n/** Build the base context for middleware instrumentation events. */\nfunction instrumentCtx(ctx: ChatMiddlewareContext) {\n return {\n requestId: ctx.requestId,\n streamId: ctx.streamId,\n clientId: ctx.threadId,\n timestamp: Date.now(),\n }\n}\n\n/**\n * Internal middleware runner that manages composed execution of middleware hooks.\n * Created once per chat() invocation.\n */\nexport class MiddlewareRunner {\n private readonly middlewares: ReadonlyArray<ChatMiddleware>\n private readonly logger: InternalLogger\n\n constructor(\n middlewares: ReadonlyArray<ChatMiddleware>,\n logger: InternalLogger,\n ) {\n this.middlewares = middlewares\n this.logger = logger\n }\n\n get hasMiddleware(): boolean {\n return this.middlewares.length > 0\n }\n\n /**\n * Pipe config through all middleware onConfig hooks in order.\n * Each middleware receives the merged config from previous middleware.\n * Partial returns are shallow-merged with the current config.\n */\n async runOnConfig(\n ctx: ChatMiddlewareContext,\n config: ChatMiddlewareConfig,\n ): Promise<ChatMiddlewareConfig> {\n let current = config\n for (const mw of this.middlewares) {\n if (mw.onConfig) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onConfig(ctx, current)\n const hasTransform = result !== undefined && result !== null\n if (hasTransform) {\n current = { ...current, ...result }\n if (!skip) {\n this.logger.config(\n `middleware=${mw.name ?? 'unnamed'} keys=${Object.keys(result as object).join(',')}`,\n {\n middleware: mw.name ?? 'unnamed',\n changes: result,\n },\n )\n }\n }\n if (!skip) {\n const base = instrumentCtx(ctx)\n aiEventClient.emit('middleware:hook:executed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n hookName: 'onConfig',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform,\n })\n if (hasTransform) {\n aiEventClient.emit('middleware:config:transformed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n iteration: ctx.iteration,\n changes: result as Record<string, unknown>,\n })\n }\n }\n }\n }\n return current\n }\n\n /**\n * Call onStart on all middleware in order.\n */\n async runOnStart(ctx: ChatMiddlewareContext): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onStart) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onStart(ctx)\n if (!skip) {\n this.logger.middleware(\n `hook=onStart middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onStart' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onStart',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Pipe a single chunk through all middleware onChunk hooks in order.\n * Returns the resulting chunks (0..N) to yield to the consumer.\n *\n * - void: pass through unchanged\n * - chunk: replace with this chunk\n * - chunk[]: expand to multiple chunks\n * - null: drop the chunk entirely\n */\n async runOnChunk(\n ctx: ChatMiddlewareContext,\n chunk: StreamChunk,\n ): Promise<Array<StreamChunk>> {\n let chunks: Array<StreamChunk> = [chunk]\n\n for (const mw of this.middlewares) {\n if (!mw.onChunk) continue\n const skip = shouldSkipInstrumentation(mw)\n\n const nextChunks: Array<StreamChunk> = []\n for (const c of chunks) {\n // Cast: @ag-ui/core Zod passthrough types prevent direct `.type` access\n const chunkType = (c as StreamChunk & { type: string }).type\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onChunk', in: c },\n )\n }\n const result = await mw.onChunk(ctx, c)\n if (result === null) {\n // Drop this chunk\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=<dropped>`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n dropped: true,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: 0,\n wasDropped: true,\n })\n }\n continue\n } else if (result === undefined) {\n // Pass through — no instrumentation for pass-throughs\n nextChunks.push(c)\n } else if (Array.isArray(result)) {\n // Expand\n nextChunks.push(...result)\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=[${result.map((r: StreamChunk) => (r as StreamChunk & { type: string }).type).join(',')}]`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n in: c,\n out: result,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: result.length,\n wasDropped: false,\n })\n }\n } else {\n // Replace\n nextChunks.push(result)\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=${(result as StreamChunk & { type: string }).type}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n in: c,\n out: result,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: 1,\n wasDropped: false,\n })\n }\n }\n }\n chunks = nextChunks\n }\n\n return chunks\n }\n\n /**\n * Run onBeforeToolCall through middleware in order.\n * Returns the first non-void decision, or undefined to continue normally.\n */\n async runOnBeforeToolCall(\n ctx: ChatMiddlewareContext,\n hookCtx: ToolCallHookContext,\n ): Promise<BeforeToolCallDecision> {\n for (const mw of this.middlewares) {\n if (mw.onBeforeToolCall) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const decision = await mw.onBeforeToolCall(ctx, hookCtx)\n const hasTransform = decision !== undefined && decision !== null\n if (!skip) {\n this.logger.middleware(\n `hook=onBeforeToolCall middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onBeforeToolCall' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onBeforeToolCall',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform,\n })\n }\n if (hasTransform) {\n return decision\n }\n }\n }\n return undefined\n }\n\n /**\n * Run onAfterToolCall on all middleware in order.\n */\n async runOnAfterToolCall(\n ctx: ChatMiddlewareContext,\n info: AfterToolCallInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onAfterToolCall) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onAfterToolCall(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onAfterToolCall middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onAfterToolCall' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onAfterToolCall',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onUsage on all middleware in order.\n */\n async runOnUsage(\n ctx: ChatMiddlewareContext,\n usage: UsageInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onUsage) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onUsage(ctx, usage)\n if (!skip) {\n this.logger.middleware(\n `hook=onUsage middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onUsage' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onUsage',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onFinish on all middleware in order.\n */\n async runOnFinish(\n ctx: ChatMiddlewareContext,\n info: FinishInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onFinish) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onFinish(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onFinish middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onFinish' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onFinish',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onAbort on all middleware in order.\n */\n async runOnAbort(ctx: ChatMiddlewareContext, info: AbortInfo): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onAbort) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onAbort(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onAbort middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onAbort' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onAbort',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onError on all middleware in order.\n */\n async runOnError(ctx: ChatMiddlewareContext, info: ErrorInfo): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onError) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onError(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onError middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onError' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onError',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onIteration on all middleware in order.\n * Called at the start of each agent loop iteration.\n */\n async runOnIteration(\n ctx: ChatMiddlewareContext,\n info: IterationInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onIteration) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onIteration(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onIteration middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onIteration' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onIteration',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onToolPhaseComplete on all middleware in order.\n * Called after all tool calls in an iteration have been processed.\n */\n async runOnToolPhaseComplete(\n ctx: ChatMiddlewareContext,\n info: ToolPhaseCompleteInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onToolPhaseComplete) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onToolPhaseComplete(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onToolPhaseComplete middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onToolPhaseComplete' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onToolPhaseComplete',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ModelMessage, StreamChunk, Tool, ToolCall } from '../../../types.js';
|
|
2
|
+
import { SystemPrompt } from '../../../system-prompts.js';
|
|
2
3
|
/**
|
|
3
4
|
* Phase of the chat middleware lifecycle.
|
|
4
5
|
* - 'init': Initial config transform before the chat engine starts
|
|
@@ -57,13 +58,13 @@ export interface ChatMiddlewareContext {
|
|
|
57
58
|
/** Whether the chat is streaming */
|
|
58
59
|
streaming: boolean;
|
|
59
60
|
/** System prompts configured for this chat */
|
|
60
|
-
systemPrompts: Array<
|
|
61
|
+
systemPrompts: Array<SystemPrompt>;
|
|
61
62
|
/** Names of configured tools, if any */
|
|
62
63
|
toolNames?: Array<string>;
|
|
63
64
|
/** Flattened generation options (temperature, topP, maxTokens, metadata) */
|
|
64
|
-
options?: Record<string, unknown
|
|
65
|
+
options?: Record<string, unknown> | undefined;
|
|
65
66
|
/** Provider-specific model options */
|
|
66
|
-
modelOptions?: Record<string, unknown
|
|
67
|
+
modelOptions?: Record<string, unknown> | undefined;
|
|
67
68
|
/** Number of messages at the start of the request */
|
|
68
69
|
messageCount: number;
|
|
69
70
|
/** Whether tools are configured */
|
|
@@ -84,13 +85,13 @@ export interface ChatMiddlewareContext {
|
|
|
84
85
|
*/
|
|
85
86
|
export interface ChatMiddlewareConfig {
|
|
86
87
|
messages: Array<ModelMessage>;
|
|
87
|
-
systemPrompts: Array<
|
|
88
|
+
systemPrompts: Array<SystemPrompt>;
|
|
88
89
|
tools: Array<Tool>;
|
|
89
90
|
temperature?: number;
|
|
90
91
|
topP?: number;
|
|
91
92
|
maxTokens?: number;
|
|
92
|
-
metadata?: Record<string, unknown
|
|
93
|
-
modelOptions?: Record<string, unknown
|
|
93
|
+
metadata?: Record<string, unknown> | undefined;
|
|
94
|
+
modelOptions?: Record<string, unknown> | undefined;
|
|
94
95
|
}
|
|
95
96
|
/**
|
|
96
97
|
* Context provided to tool call hooks (onBeforeToolCall / onAfterToolCall).
|
|
@@ -205,7 +206,7 @@ export interface FinishInfo {
|
|
|
205
206
|
promptTokens: number;
|
|
206
207
|
completionTokens: number;
|
|
207
208
|
totalTokens: number;
|
|
208
|
-
};
|
|
209
|
+
} | undefined;
|
|
209
210
|
}
|
|
210
211
|
/**
|
|
211
212
|
* Information passed to onAbort.
|
|
@@ -58,18 +58,18 @@ export interface StreamProcessorOptions {
|
|
|
58
58
|
* @see docs/chat-architecture.md#adapter-contract — What this class expects from adapters
|
|
59
59
|
*/
|
|
60
60
|
export declare class StreamProcessor {
|
|
61
|
-
private chunkStrategy;
|
|
62
|
-
private events;
|
|
63
|
-
private jsonParser;
|
|
61
|
+
private readonly chunkStrategy;
|
|
62
|
+
private readonly events;
|
|
63
|
+
private readonly jsonParser;
|
|
64
64
|
private recordingEnabled;
|
|
65
65
|
private messages;
|
|
66
|
-
private messageStates;
|
|
67
|
-
private activeMessageIds;
|
|
68
|
-
private toolCallToMessage;
|
|
66
|
+
private readonly messageStates;
|
|
67
|
+
private readonly activeMessageIds;
|
|
68
|
+
private readonly toolCallToMessage;
|
|
69
69
|
private pendingManualMessageId;
|
|
70
70
|
private pendingThinkingStepId;
|
|
71
|
-
private structuredMessageIds;
|
|
72
|
-
private activeRuns;
|
|
71
|
+
private readonly structuredMessageIds;
|
|
72
|
+
private readonly activeRuns;
|
|
73
73
|
private finishReason;
|
|
74
74
|
private hasError;
|
|
75
75
|
private isDone;
|
|
@@ -3,20 +3,29 @@ import { defaultJSONParser } from "./json-parser.js";
|
|
|
3
3
|
import { updateToolCallWithOutput, updateToolResultPart, updateToolCallApprovalResponse, appendStructuredOutputDelta, updateToolCallPart, errorStructuredOutputPart, updateThinkingPart, completeStructuredOutputPart, updateToolCallApproval, updateTextPart } from "./message-updaters.js";
|
|
4
4
|
import { ImmediateStrategy } from "./strategies.js";
|
|
5
5
|
class StreamProcessor {
|
|
6
|
+
chunkStrategy;
|
|
7
|
+
events;
|
|
8
|
+
jsonParser;
|
|
9
|
+
recordingEnabled;
|
|
10
|
+
// Message state
|
|
11
|
+
messages = [];
|
|
12
|
+
// Per-message stream state
|
|
13
|
+
messageStates = /* @__PURE__ */ new Map();
|
|
14
|
+
activeMessageIds = /* @__PURE__ */ new Set();
|
|
15
|
+
toolCallToMessage = /* @__PURE__ */ new Map();
|
|
16
|
+
pendingManualMessageId = null;
|
|
17
|
+
pendingThinkingStepId = null;
|
|
18
|
+
structuredMessageIds = /* @__PURE__ */ new Set();
|
|
19
|
+
// Run tracking (for concurrent run safety)
|
|
20
|
+
activeRuns = /* @__PURE__ */ new Set();
|
|
21
|
+
// Shared stream state
|
|
22
|
+
finishReason = null;
|
|
23
|
+
hasError = false;
|
|
24
|
+
isDone = false;
|
|
25
|
+
// Recording
|
|
26
|
+
recording = null;
|
|
27
|
+
recordingStartTime = 0;
|
|
6
28
|
constructor(options = {}) {
|
|
7
|
-
this.messages = [];
|
|
8
|
-
this.messageStates = /* @__PURE__ */ new Map();
|
|
9
|
-
this.activeMessageIds = /* @__PURE__ */ new Set();
|
|
10
|
-
this.toolCallToMessage = /* @__PURE__ */ new Map();
|
|
11
|
-
this.pendingManualMessageId = null;
|
|
12
|
-
this.pendingThinkingStepId = null;
|
|
13
|
-
this.structuredMessageIds = /* @__PURE__ */ new Set();
|
|
14
|
-
this.activeRuns = /* @__PURE__ */ new Set();
|
|
15
|
-
this.finishReason = null;
|
|
16
|
-
this.hasError = false;
|
|
17
|
-
this.isDone = false;
|
|
18
|
-
this.recording = null;
|
|
19
|
-
this.recordingStartTime = 0;
|
|
20
29
|
this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy();
|
|
21
30
|
this.events = options.events || {};
|
|
22
31
|
this.jsonParser = options.jsonParser || defaultJSONParser;
|
|
@@ -395,9 +404,8 @@ class StreamProcessor {
|
|
|
395
404
|
* Used as fallback for events that don't include a messageId.
|
|
396
405
|
*/
|
|
397
406
|
getActiveAssistantMessageId() {
|
|
398
|
-
const ids = Array.from(this.activeMessageIds);
|
|
399
|
-
for (
|
|
400
|
-
const id = ids[i];
|
|
407
|
+
const ids = Array.from(this.activeMessageIds).reverse();
|
|
408
|
+
for (const id of ids) {
|
|
401
409
|
const state = this.messageStates.get(id);
|
|
402
410
|
if (state && state.role === "assistant") {
|
|
403
411
|
return id;
|
|
@@ -422,7 +430,7 @@ class StreamProcessor {
|
|
|
422
430
|
const activeId = this.getActiveAssistantMessageId();
|
|
423
431
|
if (activeId) {
|
|
424
432
|
const state2 = this.getMessageState(activeId);
|
|
425
|
-
return { messageId: activeId, state: state2 };
|
|
433
|
+
if (state2) return { messageId: activeId, state: state2 };
|
|
426
434
|
}
|
|
427
435
|
if (preferredId) {
|
|
428
436
|
const existingMsg = this.messages.find((m) => m.id === preferredId);
|
|
@@ -488,10 +496,10 @@ class StreamProcessor {
|
|
|
488
496
|
const existingMsg = this.messages.find((m) => m.id === messageId);
|
|
489
497
|
if (existingMsg) {
|
|
490
498
|
this.activeMessageIds.add(messageId);
|
|
491
|
-
|
|
499
|
+
const existingState = this.messageStates.get(messageId);
|
|
500
|
+
if (!existingState) {
|
|
492
501
|
this.createMessageState(messageId, uiRole);
|
|
493
502
|
} else {
|
|
494
|
-
const existingState = this.messageStates.get(messageId);
|
|
495
503
|
if (existingState.hasToolCallsSinceTextStart) {
|
|
496
504
|
if (existingState.currentSegmentText !== existingState.lastEmittedText) {
|
|
497
505
|
this.emitTextUpdateForMessage(messageId);
|
|
@@ -910,7 +918,7 @@ class StreamProcessor {
|
|
|
910
918
|
state.thinkingStepOrder.push(stepId);
|
|
911
919
|
state.currentThinkingStepId = stepId;
|
|
912
920
|
}
|
|
913
|
-
const previous = state.thinkingSteps.get(stepId);
|
|
921
|
+
const previous = state.thinkingSteps.get(stepId) ?? "";
|
|
914
922
|
let nextThinking = previous;
|
|
915
923
|
if (chunk.delta && chunk.delta !== "") {
|
|
916
924
|
nextThinking = previous + chunk.delta;
|
|
@@ -1309,7 +1317,7 @@ class StreamProcessor {
|
|
|
1309
1317
|
}
|
|
1310
1318
|
function createReplayStream(recording) {
|
|
1311
1319
|
return {
|
|
1312
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1320
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- async generator required by AsyncIterable contract; body has no await
|
|
1313
1321
|
async *[Symbol.asyncIterator]() {
|
|
1314
1322
|
for (const { chunk } of recording.chunks) {
|
|
1315
1323
|
yield chunk;
|