@tanstack/ai 0.45.1 → 0.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/index.d.ts +36 -11
- package/dist/esm/activities/chat/index.js +462 -66
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.d.ts +1 -0
- package/dist/esm/activities/chat/messages.js +12 -7
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.d.ts +7 -2
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +10 -3
- package/dist/esm/activities/chat/middleware/compose.js +55 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.d.ts +6 -3
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/generic-interrupts.d.ts +13 -0
- package/dist/esm/activities/chat/middleware/generic-interrupts.js +8 -0
- package/dist/esm/activities/chat/middleware/generic-interrupts.js.map +1 -0
- package/dist/esm/activities/chat/middleware/index.d.ts +4 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +54 -3
- package/dist/esm/activities/chat/middleware/types.js +16 -0
- package/dist/esm/activities/chat/middleware/types.js.map +1 -0
- package/dist/esm/activities/chat/stream/processor.js +18 -5
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/unique-tool-names.d.ts +20 -0
- package/dist/esm/activities/chat/tools/unique-tool-names.js +57 -0
- package/dist/esm/activities/chat/tools/unique-tool-names.js.map +1 -0
- package/dist/esm/adapter-internals.d.ts +7 -0
- package/dist/esm/adapter-internals.js +5 -1
- package/dist/esm/client.d.ts +4 -0
- package/dist/esm/client.js +3 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/generic-interrupt-continuation.d.ts +45 -0
- package/dist/esm/generic-interrupt-continuation.js +80 -0
- package/dist/esm/generic-interrupt-continuation.js.map +1 -0
- package/dist/esm/index.d.ts +9 -1
- package/dist/esm/index.js +7 -2
- package/dist/esm/interrupt-definition.d.ts +113 -0
- package/dist/esm/interrupt-definition.js +169 -0
- package/dist/esm/interrupt-definition.js.map +1 -0
- package/dist/esm/interrupt-resume.d.ts +3 -0
- package/dist/esm/interrupt-resume.js +77 -16
- package/dist/esm/interrupt-resume.js.map +1 -1
- package/dist/esm/interrupts.d.ts +12 -3
- package/dist/esm/interrupts.js.map +1 -1
- package/dist/esm/middlewares/usage-attributes.d.ts +2 -2
- package/dist/esm/middlewares/usage-attributes.js +9 -2
- package/dist/esm/middlewares/usage-attributes.js.map +1 -1
- package/dist/esm/stream-to-response.d.ts +26 -0
- package/dist/esm/stream-to-response.js +1 -1
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/stream-to-websocket.d.ts +123 -0
- package/dist/esm/stream-to-websocket.js +249 -0
- package/dist/esm/stream-to-websocket.js.map +1 -0
- package/dist/esm/types.d.ts +19 -10
- package/dist/esm/utilities/chat-params.js +10 -1
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/package.json +2 -2
- package/skills/ai-core/media-generation/SKILL.md +13 -9
- package/skills/ai-core/middleware/SKILL.md +53 -44
- package/skills/ai-core/structured-outputs/SKILL.md +59 -55
- package/skills/ai-core/tool-calling/SKILL.md +54 -1
- package/src/activities/chat/index.ts +1076 -194
- package/src/activities/chat/messages.ts +11 -3
- package/src/activities/chat/middleware/builder.ts +29 -4
- package/src/activities/chat/middleware/compose.ts +95 -5
- package/src/activities/chat/middleware/define.ts +13 -3
- package/src/activities/chat/middleware/generic-interrupts.ts +26 -0
- package/src/activities/chat/middleware/index.ts +15 -0
- package/src/activities/chat/middleware/types.ts +127 -2
- package/src/activities/chat/stream/processor.ts +21 -0
- package/src/activities/chat/tools/unique-tool-names.ts +73 -0
- package/src/adapter-internals.ts +24 -0
- package/src/client.ts +20 -0
- package/src/generic-interrupt-continuation.ts +162 -0
- package/src/index.ts +51 -0
- package/src/interrupt-definition.ts +581 -0
- package/src/interrupt-resume.ts +156 -25
- package/src/interrupts.ts +13 -3
- package/src/middlewares/usage-attributes.ts +12 -2
- package/src/stream-to-response.ts +2 -2
- package/src/stream-to-websocket.ts +418 -0
- package/src/types.ts +21 -8
- package/src/utilities/chat-params.ts +16 -3
|
@@ -9,7 +9,7 @@ function isContentPart(part) {
|
|
|
9
9
|
}
|
|
10
10
|
function safeJsonStringify(value) {
|
|
11
11
|
try {
|
|
12
|
-
return JSON.stringify(value);
|
|
12
|
+
return JSON.stringify(value) ?? "";
|
|
13
13
|
} catch {
|
|
14
14
|
return "";
|
|
15
15
|
}
|
|
@@ -141,6 +141,7 @@ function buildAssistantMessages(uiMessage) {
|
|
|
141
141
|
content,
|
|
142
142
|
...hasToolCalls && { toolCalls: current.toolCalls },
|
|
143
143
|
...pendingThinking.length > 0 && { thinking: pendingThinking },
|
|
144
|
+
...current.structuredOutput && { structuredOutput: current.structuredOutput },
|
|
144
145
|
...uiMessage.createdAt !== void 0 && { createdAt: uiMessage.createdAt }
|
|
145
146
|
});
|
|
146
147
|
pendingThinking = [];
|
|
@@ -187,10 +188,13 @@ function buildAssistantMessages(uiMessage) {
|
|
|
187
188
|
break;
|
|
188
189
|
case "structured-output": if (part.status === "complete") {
|
|
189
190
|
const serialized = part.raw !== "" ? part.raw : part.data !== void 0 ? safeJsonStringify(part.data) : "";
|
|
190
|
-
if (serialized !== "")
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
191
|
+
if (serialized !== "") {
|
|
192
|
+
current.contentParts.push({
|
|
193
|
+
type: "text",
|
|
194
|
+
content: serialized
|
|
195
|
+
});
|
|
196
|
+
current.structuredOutput = part;
|
|
197
|
+
}
|
|
194
198
|
}
|
|
195
199
|
}
|
|
196
200
|
flushSegment();
|
|
@@ -252,7 +256,8 @@ function modelMessageToUIMessage(modelMessage, id) {
|
|
|
252
256
|
...thinking.signature && { signature: thinking.signature }
|
|
253
257
|
});
|
|
254
258
|
}
|
|
255
|
-
if (modelMessage.role === "
|
|
259
|
+
if (modelMessage.role === "assistant" && modelMessage.structuredOutput) parts.push(modelMessage.structuredOutput);
|
|
260
|
+
else if (modelMessage.role === "tool" && modelMessage.toolCallId) parts.push({
|
|
256
261
|
type: "tool-result",
|
|
257
262
|
toolCallId: modelMessage.toolCallId,
|
|
258
263
|
content: getTextContent(modelMessage.content),
|
|
@@ -437,6 +442,6 @@ function generateMessageId() {
|
|
|
437
442
|
return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
438
443
|
}
|
|
439
444
|
//#endregion
|
|
440
|
-
export { aguiSnapshotMessageToUIMessage, convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, uiMessageToModelMessages };
|
|
445
|
+
export { aguiSnapshotMessageToUIMessage, convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, safeJsonStringify, uiMessageToModelMessages };
|
|
441
446
|
|
|
442
447
|
//# sourceMappingURL=messages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","names":[],"sources":["../../../../src/activities/chat/messages.ts"],"sourcesContent":["import { normalizeToolResult } from '../../utilities/tool-result'\nimport type { Message as AGUIMessage } from '@ag-ui/core'\nimport 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\nfunction parseToolResultContent(content: string): unknown {\n try {\n return JSON.parse(content)\n } catch {\n return content\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 })\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 id: uiMessage.id,\n role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 === 'complete' ||\n part.state === 'approval-requested' ||\n part.state === 'approval-responded' ||\n part.state === 'error' ||\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 // A single UI message can fan out into several model messages. Keep the\n // shared UI id on each one so persistence can retain the original identity.\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 id: uiMessage.id,\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 id: uiMessage.id,\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 case 'ui-resource':\n // MCP Apps widget — rendered client-side only. It must never enter\n // model input, so it is intentionally dropped from the model message.\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 id: uiMessage.id,\n role: 'tool',\n content: normalizeToolResult(part.output),\n toolCallId: part.id,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 id: uiMessage.id,\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 ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 id: uiMessage.id,\n role: 'assistant',\n content: null,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 // Model-message arguments are complete, so surface the parsed input.\n // A malformed arguments string just leaves `input` undefined.\n let input: unknown\n try {\n input = JSON.parse(toolCall.function.arguments)\n } catch {\n input = undefined\n }\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 ...(input !== undefined && { input }),\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 ...(modelMessage.createdAt !== undefined && {\n createdAt: modelMessage.createdAt,\n }),\n }\n}\n\n/**\n * Normalize a single AG-UI `MESSAGES_SNAPSHOT` message into a `UIMessage`.\n *\n * AG-UI snapshot messages use the wire shape `{ id, role, content }` and have\n * no `parts` array. Casting them directly to `UIMessage` is unsafe: any code\n * that later reads `message.parts` (e.g. the devtools `onToolCallStateChange`\n * handler) crashes with \"Cannot read properties of undefined (reading 'find')\".\n *\n * Each role is mapped to the canonical `UIMessage` shape, reusing\n * `modelMessageToUIMessage` for the roles that share `ModelMessage`'s structure.\n * The original AG-UI `id` is preserved so later `TEXT_MESSAGE_CONTENT` /\n * `TOOL_CALL_*` events still route by `messageId` (falling back to a generated\n * id only when the snapshot omits one). Messages that already carry `parts`\n * (e.g. a TanStack server echoing `UIMessage`s back over the wire) pass through\n * unchanged apart from ensuring an id.\n */\nexport function aguiSnapshotMessageToUIMessage(\n message: AGUIMessage | UIMessage,\n): UIMessage {\n if ('parts' in message) {\n return { ...message, id: message.id || generateMessageId() }\n }\n\n const id = message.id || generateMessageId()\n\n switch (message.role) {\n case 'user':\n return {\n id,\n role: 'user',\n parts: aguiUserContentToParts(message.content),\n }\n case 'assistant':\n return modelMessageToUIMessage(\n {\n role: 'assistant',\n content: message.content ?? null,\n ...(message.toolCalls && { toolCalls: message.toolCalls }),\n },\n id,\n )\n case 'tool':\n return modelMessageToUIMessage(\n {\n role: 'tool',\n content: message.content,\n toolCallId: message.toolCallId,\n },\n id,\n )\n case 'system':\n case 'developer':\n // `ModelMessage` has no system/developer role; build the part directly.\n return {\n id,\n role: 'system',\n parts: message.content\n ? [{ type: 'text', content: message.content }]\n : [],\n }\n case 'reasoning':\n return {\n id,\n role: 'assistant',\n parts: message.content\n ? [{ type: 'thinking', content: message.content }]\n : [],\n }\n case 'activity':\n default:\n // `activity` (and any future role) has no text/parts equivalent today.\n return { id, role: 'assistant', parts: [] }\n }\n}\n\n/**\n * Convert AG-UI user message content into `UIMessage` parts.\n *\n * AG-UI user content is either a plain string or a multimodal array whose text\n * entries use `{ type: 'text', text }` (vs. TanStack's `{ type: 'text', content }`).\n * Text entries are rewritten to the TanStack shape; image/audio/video/document\n * entries already match `ContentPart` and pass through. `binary` entries have no\n * TanStack equivalent and are dropped.\n */\nfunction aguiUserContentToParts(\n content: Extract<AGUIMessage, { role: 'user' }>['content'],\n): Array<MessagePart> {\n if (typeof content === 'string') {\n return content ? [{ type: 'text', content }] : []\n }\n\n const parts: Array<MessagePart> = []\n for (const part of content) {\n if (part.type === 'text') {\n parts.push({ type: 'text', content: part.text })\n } else if (part.type !== 'binary') {\n parts.push(part)\n }\n }\n return parts\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 const content = getTextContent(msg.content)\n const toolCallPart = currentAssistantMessage.parts.find(\n (part): part is ToolCallPart =>\n part.type === 'tool-call' && part.id === msg.toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.output = parseToolResultContent(content)\n toolCallPart.state = 'complete'\n }\n\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId,\n content,\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg, msg.id)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message. Preserve a persisted stable id so a hydrated message\n // keeps the same identity as its live stream (enables in-place resume).\n const uiMessage = modelMessageToUIMessage(msg, msg.id)\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: message.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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"messages.js","names":[],"sources":["../../../../src/activities/chat/messages.ts"],"sourcesContent":["import { normalizeToolResult } from '../../utilities/tool-result'\nimport type { Message as AGUIMessage } from '@ag-ui/core'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StructuredOutputPart,\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\nexport function safeJsonStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? ''\n } catch {\n return ''\n }\n}\n\nfunction parseToolResultContent(content: string): unknown {\n try {\n return JSON.parse(content)\n } catch {\n return content\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 })\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 id: uiMessage.id,\n role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n }\n}\n\n// Accumulator for building an assistant segment (content + tool calls)\ninterface AssistantSegment {\n contentParts: Array<ContentPart>\n structuredOutput?: StructuredOutputPart\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 === 'complete' ||\n part.state === 'approval-requested' ||\n part.state === 'approval-responded' ||\n part.state === 'error' ||\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 // A single UI message can fan out into several model messages. Keep the\n // shared UI id on each one so persistence can retain the original identity.\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 id: uiMessage.id,\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\n ...(current.structuredOutput && {\n structuredOutput: current.structuredOutput,\n }),\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 id: uiMessage.id,\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 current.structuredOutput = part\n }\n }\n break\n\n case 'ui-resource':\n // MCP Apps widget — rendered client-side only. It must never enter\n // model input, so it is intentionally dropped from the model message.\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 id: uiMessage.id,\n role: 'tool',\n content: normalizeToolResult(part.output),\n toolCallId: part.id,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 id: uiMessage.id,\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 ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 id: uiMessage.id,\n role: 'assistant',\n content: null,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\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 === 'assistant' && modelMessage.structuredOutput) {\n parts.push(modelMessage.structuredOutput)\n } else 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 // Model-message arguments are complete, so surface the parsed input.\n // A malformed arguments string just leaves `input` undefined.\n let input: unknown\n try {\n input = JSON.parse(toolCall.function.arguments)\n } catch {\n input = undefined\n }\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 ...(input !== undefined && { input }),\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 ...(modelMessage.createdAt !== undefined && {\n createdAt: modelMessage.createdAt,\n }),\n }\n}\n\n/**\n * Normalize a single AG-UI `MESSAGES_SNAPSHOT` message into a `UIMessage`.\n *\n * AG-UI snapshot messages use the wire shape `{ id, role, content }` and have\n * no `parts` array. Casting them directly to `UIMessage` is unsafe: any code\n * that later reads `message.parts` (e.g. the devtools `onToolCallStateChange`\n * handler) crashes with \"Cannot read properties of undefined (reading 'find')\".\n *\n * Each role is mapped to the canonical `UIMessage` shape, reusing\n * `modelMessageToUIMessage` for the roles that share `ModelMessage`'s structure.\n * The original AG-UI `id` is preserved so later `TEXT_MESSAGE_CONTENT` /\n * `TOOL_CALL_*` events still route by `messageId` (falling back to a generated\n * id only when the snapshot omits one). Messages that already carry `parts`\n * (e.g. a TanStack server echoing `UIMessage`s back over the wire) pass through\n * unchanged apart from ensuring an id.\n */\nexport function aguiSnapshotMessageToUIMessage(\n message: AGUIMessage | UIMessage,\n): UIMessage {\n if ('parts' in message) {\n return { ...message, id: message.id || generateMessageId() }\n }\n\n const id = message.id || generateMessageId()\n\n switch (message.role) {\n case 'user':\n return {\n id,\n role: 'user',\n parts: aguiUserContentToParts(message.content),\n }\n case 'assistant':\n return modelMessageToUIMessage(\n {\n role: 'assistant',\n content: message.content ?? null,\n ...(message.toolCalls && { toolCalls: message.toolCalls }),\n },\n id,\n )\n case 'tool':\n return modelMessageToUIMessage(\n {\n role: 'tool',\n content: message.content,\n toolCallId: message.toolCallId,\n },\n id,\n )\n case 'system':\n case 'developer':\n // `ModelMessage` has no system/developer role; build the part directly.\n return {\n id,\n role: 'system',\n parts: message.content\n ? [{ type: 'text', content: message.content }]\n : [],\n }\n case 'reasoning':\n return {\n id,\n role: 'assistant',\n parts: message.content\n ? [{ type: 'thinking', content: message.content }]\n : [],\n }\n case 'activity':\n default:\n // `activity` (and any future role) has no text/parts equivalent today.\n return { id, role: 'assistant', parts: [] }\n }\n}\n\n/**\n * Convert AG-UI user message content into `UIMessage` parts.\n *\n * AG-UI user content is either a plain string or a multimodal array whose text\n * entries use `{ type: 'text', text }` (vs. TanStack's `{ type: 'text', content }`).\n * Text entries are rewritten to the TanStack shape; image/audio/video/document\n * entries already match `ContentPart` and pass through. `binary` entries have no\n * TanStack equivalent and are dropped.\n */\nfunction aguiUserContentToParts(\n content: Extract<AGUIMessage, { role: 'user' }>['content'],\n): Array<MessagePart> {\n if (typeof content === 'string') {\n return content ? [{ type: 'text', content }] : []\n }\n\n const parts: Array<MessagePart> = []\n for (const part of content) {\n if (part.type === 'text') {\n parts.push({ type: 'text', content: part.text })\n } else if (part.type !== 'binary') {\n parts.push(part)\n }\n }\n return parts\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 const content = getTextContent(msg.content)\n const toolCallPart = currentAssistantMessage.parts.find(\n (part): part is ToolCallPart =>\n part.type === 'tool-call' && part.id === msg.toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.output = parseToolResultContent(content)\n toolCallPart.state = 'complete'\n }\n\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId,\n content,\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg, msg.id)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message. Preserve a persisted stable id so a hydrated message\n // keeps the same identity as its live stream (enables in-place resume).\n const uiMessage = modelMessageToUIMessage(msg, msg.id)\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: message.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"],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CapabilityHandle } from './capabilities.js';
|
|
2
2
|
import { AnyChatMiddleware } from './types.js';
|
|
3
3
|
import { DefinedChatMiddleware } from './define.js';
|
|
4
|
+
import { InterruptDefinition } from '../../../interrupt-definition.js';
|
|
5
|
+
type AnyInterruptDefinition = InterruptDefinition<any, any, any, any>;
|
|
4
6
|
/** Union of capability NAME literals from a tuple of handles. */
|
|
5
7
|
export type NamesOf<T extends ReadonlyArray<CapabilityHandle>> = T[number]['capabilityName'];
|
|
6
8
|
/** Names provided across a middleware array (imprecise middleware → `string`). */
|
|
@@ -37,8 +39,11 @@ export type CheckCoverage<TList extends ReadonlyArray<AnyChatMiddleware>> = [
|
|
|
37
39
|
*
|
|
38
40
|
* `TProvided` is the running union of provided capability name literals.
|
|
39
41
|
*/
|
|
40
|
-
export interface ChatMiddlewareBuilder<TList extends ReadonlyArray<AnyChatMiddleware>, TProvided extends string> {
|
|
41
|
-
use: <TRequires extends ReadonlyArray<CapabilityHandle>, TProvides extends ReadonlyArray<CapabilityHandle>, TContext = unknown>(middleware: [NamesOf<TRequires>] extends [TProvided] ? DefinedChatMiddleware<TContext, TRequires, TProvides> : DefinedChatMiddleware<TContext, TRequires, TProvides> & MissingCapabilities<Exclude<NamesOf<TRequires>, TProvided>>) => ChatMiddlewareBuilder<readonly [
|
|
42
|
+
export interface ChatMiddlewareBuilder<TList extends ReadonlyArray<AnyChatMiddleware>, TProvided extends string, TInterruptDefinitions extends AnyInterruptDefinition = never> {
|
|
43
|
+
use: <TRequires extends ReadonlyArray<CapabilityHandle>, TProvides extends ReadonlyArray<CapabilityHandle>, TContext = unknown, TMiddlewareInterruptDefinitions extends AnyInterruptDefinition = TInterruptDefinitions>(middleware: [NamesOf<TRequires>] extends [TProvided] ? DefinedChatMiddleware<TContext, TRequires, TProvides, TMiddlewareInterruptDefinitions> : DefinedChatMiddleware<TContext, TRequires, TProvides, TMiddlewareInterruptDefinitions> & MissingCapabilities<Exclude<NamesOf<TRequires>, TProvided>>) => ChatMiddlewareBuilder<readonly [
|
|
44
|
+
...TList,
|
|
45
|
+
DefinedChatMiddleware<TContext, TRequires, TProvides, TMiddlewareInterruptDefinitions>
|
|
46
|
+
], TProvided | NamesOf<TProvides>, TInterruptDefinitions | TMiddlewareInterruptDefinitions>;
|
|
42
47
|
build: () => [...TList];
|
|
43
48
|
}
|
|
44
49
|
/** Create an order-aware middleware builder. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.js","names":[],"sources":["../../../../../src/activities/chat/middleware/builder.ts"],"sourcesContent":["import type { CapabilityHandle } from './capabilities'\nimport type { AnyChatMiddleware, ChatMiddleware } from './types'\nimport type { DefinedChatMiddleware } from './define'\n\n/** Union of capability NAME literals from a tuple of handles. */\nexport type NamesOf<T extends ReadonlyArray<CapabilityHandle>> =\n T[number]['capabilityName']\n\n/** Names provided across a middleware array (imprecise middleware → `string`). */\nexport type ProvidedNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n NonNullable<TList[number]['provides']> extends infer P\n ? P extends ReadonlyArray<CapabilityHandle>\n ? NamesOf<P>\n : never\n : never\n\n/** Names required across a middleware array. */\nexport type RequiredNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n NonNullable<TList[number]['requires']> extends infer P\n ? P extends ReadonlyArray<CapabilityHandle>\n ? NamesOf<P>\n : never\n : never\n\n/**\n * Branded marker surfaced when required capability names are missing from the\n * provided set, so the compiler error names the gap instead of emitting an\n * opaque \"not assignable\".\n */\nexport type MissingCapabilities<TMissing extends string> = {\n // The human-readable message lives in the property KEY, so TypeScript's\n // \"Property '<key>' is missing in type ... but required in type ...\" error\n // prints the explanation instead of an opaque `__missingCapabilities`. The\n // key distributes over a union of missing names (one required key each).\n [K in `✖ Missing capability \"${TMissing}\": no configured middleware provides it. Add a middleware whose \\`provides\\` includes it (and, with createChatMiddleware().use(), order the provider before this consumer).`]: never\n}\n\n/**\n * Missing capability names. When required names are imprecise (`string`, i.e.\n * plain `ChatMiddleware` not authored via `defineChatMiddleware`), we cannot\n * prove a gap, so we allow it (→ `never`). Otherwise the precise literals not\n * present in the provided set.\n */\ntype MissingNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n string extends RequiredNames<TList>\n ? never\n : Exclude<RequiredNames<TList>, ProvidedNames<TList>>\n\n/**\n * Resolves to `TList` when coverage holds, otherwise to a `MissingCapabilities`\n * marker (not assignable to a middleware array) — producing a compile error at\n * the `middleware` option that names the missing capability.\n */\nexport type CheckCoverage<TList extends ReadonlyArray<AnyChatMiddleware>> = [\n MissingNames<TList>,\n] extends [never]\n ? TList\n : MissingCapabilities<MissingNames<TList>>\n\n/**\n * Order-aware middleware builder. Each `.use()` requires that the middleware's\n * required capability names are already in the accumulated provided set, then\n * adds its provided names. `.build()` returns the ordered array.\n *\n * `TProvided` is the running union of provided capability name literals.\n */\nexport interface ChatMiddlewareBuilder<\n TList extends ReadonlyArray<AnyChatMiddleware>,\n TProvided extends string,\n> {\n use: <\n TRequires extends ReadonlyArray<CapabilityHandle>,\n TProvides extends ReadonlyArray<CapabilityHandle>,\n TContext = unknown,\n >(\n middleware: [NamesOf<TRequires>] extends [TProvided]\n ? DefinedChatMiddleware
|
|
1
|
+
{"version":3,"file":"builder.js","names":[],"sources":["../../../../../src/activities/chat/middleware/builder.ts"],"sourcesContent":["import type { CapabilityHandle } from './capabilities'\nimport type { AnyChatMiddleware, ChatMiddleware } from './types'\nimport type { DefinedChatMiddleware } from './define'\nimport type { InterruptDefinition } from '../../../interrupt-definition'\n\ntype AnyInterruptDefinition = InterruptDefinition<any, any, any, any>\n\n/** Union of capability NAME literals from a tuple of handles. */\nexport type NamesOf<T extends ReadonlyArray<CapabilityHandle>> =\n T[number]['capabilityName']\n\n/** Names provided across a middleware array (imprecise middleware → `string`). */\nexport type ProvidedNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n NonNullable<TList[number]['provides']> extends infer P\n ? P extends ReadonlyArray<CapabilityHandle>\n ? NamesOf<P>\n : never\n : never\n\n/** Names required across a middleware array. */\nexport type RequiredNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n NonNullable<TList[number]['requires']> extends infer P\n ? P extends ReadonlyArray<CapabilityHandle>\n ? NamesOf<P>\n : never\n : never\n\n/**\n * Branded marker surfaced when required capability names are missing from the\n * provided set, so the compiler error names the gap instead of emitting an\n * opaque \"not assignable\".\n */\nexport type MissingCapabilities<TMissing extends string> = {\n // The human-readable message lives in the property KEY, so TypeScript's\n // \"Property '<key>' is missing in type ... but required in type ...\" error\n // prints the explanation instead of an opaque `__missingCapabilities`. The\n // key distributes over a union of missing names (one required key each).\n [K in `✖ Missing capability \"${TMissing}\": no configured middleware provides it. Add a middleware whose \\`provides\\` includes it (and, with createChatMiddleware().use(), order the provider before this consumer).`]: never\n}\n\n/**\n * Missing capability names. When required names are imprecise (`string`, i.e.\n * plain `ChatMiddleware` not authored via `defineChatMiddleware`), we cannot\n * prove a gap, so we allow it (→ `never`). Otherwise the precise literals not\n * present in the provided set.\n */\ntype MissingNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n string extends RequiredNames<TList>\n ? never\n : Exclude<RequiredNames<TList>, ProvidedNames<TList>>\n\n/**\n * Resolves to `TList` when coverage holds, otherwise to a `MissingCapabilities`\n * marker (not assignable to a middleware array) — producing a compile error at\n * the `middleware` option that names the missing capability.\n */\nexport type CheckCoverage<TList extends ReadonlyArray<AnyChatMiddleware>> = [\n MissingNames<TList>,\n] extends [never]\n ? TList\n : MissingCapabilities<MissingNames<TList>>\n\n/**\n * Order-aware middleware builder. Each `.use()` requires that the middleware's\n * required capability names are already in the accumulated provided set, then\n * adds its provided names. `.build()` returns the ordered array.\n *\n * `TProvided` is the running union of provided capability name literals.\n */\nexport interface ChatMiddlewareBuilder<\n TList extends ReadonlyArray<AnyChatMiddleware>,\n TProvided extends string,\n TInterruptDefinitions extends AnyInterruptDefinition = never,\n> {\n use: <\n TRequires extends ReadonlyArray<CapabilityHandle>,\n TProvides extends ReadonlyArray<CapabilityHandle>,\n TContext = unknown,\n TMiddlewareInterruptDefinitions extends AnyInterruptDefinition =\n TInterruptDefinitions,\n >(\n middleware: [NamesOf<TRequires>] extends [TProvided]\n ? DefinedChatMiddleware<\n TContext,\n TRequires,\n TProvides,\n TMiddlewareInterruptDefinitions\n >\n : DefinedChatMiddleware<\n TContext,\n TRequires,\n TProvides,\n TMiddlewareInterruptDefinitions\n > &\n MissingCapabilities<Exclude<NamesOf<TRequires>, TProvided>>,\n ) => ChatMiddlewareBuilder<\n readonly [\n ...TList,\n DefinedChatMiddleware<\n TContext,\n TRequires,\n TProvides,\n TMiddlewareInterruptDefinitions\n >,\n ],\n TProvided | NamesOf<TProvides>,\n TInterruptDefinitions | TMiddlewareInterruptDefinitions\n >\n\n build: () => [...TList]\n}\n\n/** Create an order-aware middleware builder. */\nexport function createChatMiddleware(): ChatMiddlewareBuilder<\n readonly [],\n never\n> {\n const list: Array<ChatMiddleware<unknown>> = []\n const builder = {\n use(middleware: ChatMiddleware<unknown>) {\n list.push(middleware)\n return builder\n },\n build() {\n return list\n },\n }\n // The only sanctioned assertion in this PR: the runtime `builder` is a single\n // object reused across `.use()` calls, but the type accumulates `TProvided`\n // and `TList` per call — TypeScript cannot derive that from runtime values, so\n // a structural `as` is impossible and the double assertion is irreducible.\n // oxlint-disable-next-line eslint-js/no-restricted-syntax -- irreducible: type-level accumulation cannot be expressed from a single runtime object\n return builder as unknown as ChatMiddlewareBuilder<readonly [], never>\n}\n"],"mappings":";;AAiHA,SAAgB,uBAGd;CACA,MAAM,OAAuC,CAAC;CAC9C,MAAM,UAAU;EACd,IAAI,YAAqC;GACvC,KAAK,KAAK,UAAU;GACpB,OAAO;EACT;EACA,QAAQ;GACN,OAAO;EACT;CACF;CAMA,OAAO;AACT"}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { AgentLoopState, StreamChunk } from '../../../types.js';
|
|
2
2
|
import { InternalLogger } from '../../../logger/internal-logger.js';
|
|
3
|
-
import { AbortInfo, AfterToolCallInfo, BeforeToolCallDecision, ChatMiddleware, ChatMiddlewareConfig, ChatMiddlewareContext, ErrorInfo, FinishInfo, IterationInfo, SandboxFileHookEvent, StructuredOutputMiddlewareConfig, ToolCallHookContext, ToolPhaseCompleteInfo, UsageInfo } from './types.js';
|
|
3
|
+
import { AbortInfo, AfterToolCallInfo, BeforeToolCallDecision, ChatMiddleware, ChatMiddlewareConfig, ChatMiddlewareContext, ErrorInfo, FinishInfo, InterruptBoundaryPhase, InterruptResolutionCollection, InterruptToolResume, IterationInfo, SandboxFileHookEvent, StructuredOutputMiddlewareConfig, ToolCallHookContext, ToolPhaseCompleteInfo, UsageInfo } from './types.js';
|
|
4
|
+
import { GenericInterruptRequest, InterruptDefinition } from '../../../interrupt-definition.js';
|
|
4
5
|
/**
|
|
5
6
|
* Internal middleware runner that manages composed execution of middleware hooks.
|
|
6
7
|
* Created once per chat() invocation.
|
|
7
8
|
*/
|
|
8
|
-
export declare class MiddlewareRunner<TContext = unknown> {
|
|
9
|
+
export declare class MiddlewareRunner<TContext = unknown, TInterruptDefinitions extends InterruptDefinition<any, any, any, any> = InterruptDefinition<any, any, any, any>> {
|
|
9
10
|
private readonly middlewares;
|
|
10
11
|
private readonly logger;
|
|
11
|
-
constructor(middlewares: ReadonlyArray<ChatMiddleware<TContext>>, logger: InternalLogger);
|
|
12
|
+
constructor(middlewares: ReadonlyArray<ChatMiddleware<TContext, TInterruptDefinitions>>, logger: InternalLogger);
|
|
12
13
|
get hasMiddleware(): boolean;
|
|
14
|
+
runOnInterruptBoundary(ctx: ChatMiddlewareContext<TContext> & {
|
|
15
|
+
phase: InterruptBoundaryPhase;
|
|
16
|
+
}): Promise<ReadonlyArray<GenericInterruptRequest<TInterruptDefinitions>>>;
|
|
17
|
+
runOnInterruptResolution(ctx: ChatMiddlewareContext<TContext>, resolutions: InterruptResolutionCollection<TInterruptDefinitions>): Promise<{
|
|
18
|
+
toolResume?: InterruptToolResume;
|
|
19
|
+
}>;
|
|
13
20
|
/**
|
|
14
21
|
* Pipe config through all middleware onConfig hooks in order.
|
|
15
22
|
* Each middleware receives the merged config from previous middleware.
|
|
@@ -27,6 +27,61 @@ var MiddlewareRunner = class {
|
|
|
27
27
|
get hasMiddleware() {
|
|
28
28
|
return this.middlewares.length > 0;
|
|
29
29
|
}
|
|
30
|
+
async runOnInterruptBoundary(ctx) {
|
|
31
|
+
const requests = [];
|
|
32
|
+
for (const mw of this.middlewares) if (mw.onInterruptBoundary) {
|
|
33
|
+
const skip = shouldSkipInstrumentation(mw);
|
|
34
|
+
const start = Date.now();
|
|
35
|
+
const result = await mw.onInterruptBoundary(ctx);
|
|
36
|
+
if (result?.interrupts) requests.push(...result.interrupts);
|
|
37
|
+
if (!skip) {
|
|
38
|
+
this.logger.middleware(`hook=onInterruptBoundary middleware=${mw.name ?? "unnamed"}`, {
|
|
39
|
+
middleware: mw.name ?? "unnamed",
|
|
40
|
+
hook: "onInterruptBoundary"
|
|
41
|
+
});
|
|
42
|
+
aiEventClient.emit("middleware:hook:executed", {
|
|
43
|
+
...instrumentCtx(ctx),
|
|
44
|
+
middlewareName: mw.name || "unnamed",
|
|
45
|
+
hookName: "onInterruptBoundary",
|
|
46
|
+
iteration: ctx.iteration,
|
|
47
|
+
duration: Date.now() - start,
|
|
48
|
+
hasTransform: result?.interrupts !== void 0
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return requests;
|
|
53
|
+
}
|
|
54
|
+
async runOnInterruptResolution(ctx, resolutions) {
|
|
55
|
+
let toolResume;
|
|
56
|
+
for (const mw of this.middlewares) if (mw.onInterruptResolution) {
|
|
57
|
+
const skip = shouldSkipInstrumentation(mw);
|
|
58
|
+
const start = Date.now();
|
|
59
|
+
const next = await mw.onInterruptResolution(ctx, resolutions);
|
|
60
|
+
if (next?.toolResume !== void 0) {
|
|
61
|
+
const priority = {
|
|
62
|
+
continue: 0,
|
|
63
|
+
cancel: 1,
|
|
64
|
+
stop: 2
|
|
65
|
+
};
|
|
66
|
+
if (toolResume === void 0 || priority[next.toolResume] > priority[toolResume]) toolResume = next.toolResume;
|
|
67
|
+
}
|
|
68
|
+
if (!skip) {
|
|
69
|
+
this.logger.middleware(`hook=onInterruptResolution middleware=${mw.name ?? "unnamed"}`, {
|
|
70
|
+
middleware: mw.name ?? "unnamed",
|
|
71
|
+
hook: "onInterruptResolution"
|
|
72
|
+
});
|
|
73
|
+
aiEventClient.emit("middleware:hook:executed", {
|
|
74
|
+
...instrumentCtx(ctx),
|
|
75
|
+
middlewareName: mw.name || "unnamed",
|
|
76
|
+
hookName: "onInterruptResolution",
|
|
77
|
+
iteration: ctx.iteration,
|
|
78
|
+
duration: Date.now() - start,
|
|
79
|
+
hasTransform: next !== void 0
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return toolResume === void 0 ? {} : { toolResume };
|
|
84
|
+
}
|
|
30
85
|
/**
|
|
31
86
|
* Pipe config through all middleware onConfig hooks in order.
|
|
32
87
|
* Each middleware receives the merged config from previous middleware.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","names":[],"sources":["../../../../../src/activities/chat/middleware/compose.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai-event-client'\nimport type { AgentLoopState, 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 SandboxFileHookEvent,\n StructuredOutputMiddlewareConfig,\n ToolCallHookContext,\n ToolPhaseCompleteInfo,\n UsageInfo,\n} from './types'\n\n/** One middleware's terminal-hook throw, captured instead of propagated. */\ninterface HookFailure {\n middleware: string\n error: unknown\n}\n\n/** Check if a middleware should be skipped for instrumentation events. */\nfunction shouldSkipInstrumentation(mw: ChatMiddleware<any>): 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<any>) {\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<TContext = unknown> {\n private readonly middlewares: ReadonlyArray<ChatMiddleware<TContext>>\n private readonly logger: InternalLogger\n\n constructor(\n middlewares: ReadonlyArray<ChatMiddleware<TContext>>,\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<TContext>,\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).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,\n })\n }\n }\n }\n }\n return current\n }\n\n /**\n * Pipe config through all middleware onStructuredOutputConfig 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 * Called once at the structured-output boundary, before runOnConfig at the\n * same boundary (which receives a ChatMiddlewareConfig view, no outputSchema).\n */\n async runOnStructuredOutputConfig(\n ctx: ChatMiddlewareContext<TContext>,\n config: StructuredOutputMiddlewareConfig,\n ): Promise<StructuredOutputMiddlewareConfig> {\n let current = config\n for (const mw of this.middlewares) {\n if (mw.onStructuredOutputConfig) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onStructuredOutputConfig(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).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: 'onStructuredOutputConfig',\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 // `result` is `Partial<StructuredOutputMiddlewareConfig>` —\n // Object.fromEntries(Object.entries(result)) yields the\n // structural `Record<string, unknown>` the event emitter wants\n // without an `as` cast.\n changes: Object.fromEntries(Object.entries(result)),\n })\n }\n }\n }\n }\n return current\n }\n\n /**\n * Run all `setup` hooks in array order, then assert every declared `provides`\n * capability was actually provided. Wires the last-wins duplicate-provide\n * warning into the registry. Runs before init `onConfig`.\n *\n * Takes the full `ChatMiddlewareContext` — the same stable context the engine\n * threads through every other hook — because it both forwards `ctx` to each\n * `setup` hook and emits instrumentation events from it.\n */\n async runSetup(ctx: ChatMiddlewareContext<TContext>): Promise<void> {\n ctx.capabilities.setOnDuplicate((name) => {\n this.logger.warn(\n `capability \"${name}\" was provided more than once; last provider wins`,\n { capability: name },\n )\n })\n\n for (const mw of this.middlewares) {\n if (mw.setup) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.setup(ctx)\n if (!skip) {\n this.logger.middleware(\n `hook=setup middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'setup' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'setup',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n\n for (const mw of this.middlewares) {\n for (const handle of mw.provides ?? []) {\n if (!ctx.capabilities.has(handle)) {\n throw new Error(\n `Middleware \"${mw.name ?? 'unnamed'}\" declares it provides ` +\n `\"${handle.capabilityName}\" but never called provide() in setup().`,\n )\n }\n }\n }\n }\n\n /**\n * Call onStart on all middleware in order.\n */\n async runOnStart(ctx: ChatMiddlewareContext<TContext>): 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<TContext>,\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.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.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.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 * Dispatch a sandbox file event to every middleware's `sandbox` hooks, in\n * array order: the catch-all `onFile` then the type-specific hook. Errors are\n * logged and swallowed so one bad hook can't break the run.\n */\n async runSandboxFile(\n ctx: ChatMiddlewareContext<TContext>,\n event: SandboxFileHookEvent,\n ): Promise<void> {\n const typed = (\n {\n create: 'onFileCreate',\n change: 'onFileChange',\n delete: 'onFileDelete',\n } as const\n )[event.type]\n for (const mw of this.middlewares) {\n const hooks = mw.sandbox\n if (!hooks) continue\n for (const fn of [hooks.onFile, hooks[typed]]) {\n if (!fn) continue\n try {\n await fn(ctx, event)\n } catch (error) {\n this.logger.sandbox(\n `hook=${typed} middleware=${mw.name ?? 'unnamed'} threw`,\n { middleware: mw.name ?? 'unnamed', error },\n )\n }\n }\n }\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<TContext>,\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<TContext>,\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<TContext>,\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 * Await ONE terminal hook and RETURN its throw instead of letting it escape\n * the caller's loop, logging it on the `errors` channel first so the failure\n * is never invisible. `undefined` means the hook completed.\n *\n * Capturing (rather than swallowing at this level) is what lets isolation and\n * reporting coexist: every caller gives every middleware its turn, and then\n * each decides on its own whether the collected failures are worth telling the\n * caller about. See {@link runOnFinish} vs {@link runOnAbort} /\n * {@link runOnError}.\n */\n private async captureTerminalHook(\n mw: ChatMiddleware<TContext>,\n hookName: 'onFinish' | 'onAbort' | 'onError',\n invoke: () => void | Promise<void>,\n ): Promise<HookFailure | undefined> {\n try {\n await invoke()\n return undefined\n } catch (error) {\n this.logger.errors(`middleware ${hookName} hook failed`, {\n middleware: mw.name ?? 'unnamed',\n hook: hookName,\n error,\n })\n return { middleware: mw.name ?? 'unnamed', error }\n }\n }\n\n /**\n * Run onFinish on all middleware in order.\n *\n * ISOLATED **and** REPORTED. `onFinish` is the only terminal fan-out on the\n * SUCCESS path, and it is where `withPersistence.onFinish` writes the\n * assistant turn through the store. So the two properties are needed together\n * and neither may be traded for the other:\n *\n * - ISOLATION: every middleware's hook runs even if an earlier one threw, so a\n * transient store error cannot skip a later middleware's own bookkeeping.\n * Each failure is captured by {@link captureTerminalHook}, not propagated\n * mid-loop.\n * - REPORTING: after the loop, the failures are rethrown. `chat()`'s catch\n * treats what we throw as a genuine error (it is not a\n * `MiddlewareAbortError`, and `structuralInterruptFailure` does not match\n * it) and rethrows it out of the generator.\n *\n * What that rethrow can and cannot achieve depends on the transport, because\n * this fan-out is awaited AFTER the adapter's `RUN_FINISHED` has already been\n * yielded (`chat()` yields terminal chunks while streaming, then awaits this\n * hook on its way out). The success terminal is therefore already gone; the\n * rethrow can only append to what the consumer saw, never retract it:\n *\n * - NON-DURABLE transport: the throw escapes the generator mid-response, and\n * the SSE / HTTP-stream encoder turns it into a TRAILING `RUN_ERROR` on the\n * wire carrying the store's own message and `code`. `ai-client` surfaces\n * that as an error status, so the user is not told the turn was saved when\n * it was not.\n * - DURABLE transport: the throw reaches the durability sink instead. The\n * terminal was already persisted AND forwarded, so the sink deliberately\n * does NOT append a second, contradictory terminal, and `terminalForwarded`\n * (see `stream-to-response.ts`) suppresses the rethrow to the live consumer.\n * The `RUN_FINISHED` stands and the failure is RECORDED SERVER-SIDE on the\n * sink's `errors` channel. That is the intended outcome, not a gap: the save\n * failed, not the run — the consumer did receive the complete stream, so\n * telling it the run errored would be the lie. What the rethrow buys here is\n * that the sink sees the failure at all; while this loop swallowed, the only\n * trace anywhere was {@link captureTerminalHook}'s log line.\n *\n * Either way, swallowing is the one option ruled out: a failed\n * `messages.append` would otherwise leave a `completed` run record with the\n * assistant turn missing from storage and nothing beyond a middleware log\n * line, and the client would go on to send a history the server has no record\n * of.\n *\n * A single failure is rethrown AS-IS so the store's own error — its message,\n * `cause`, `code` and `instanceof` identity — is what reaches the caller and\n * the wire; wrapping the common case would bury it. Two or more become an\n * `AggregateError` (never a `MiddlewareAbortError`, so it cannot be mistaken\n * for an abort) rather than picking a winner and dropping the rest.\n */\n async runOnFinish(\n ctx: ChatMiddlewareContext<TContext>,\n info: FinishInfo,\n ): Promise<void> {\n const failures: Array<HookFailure> = []\n let firstFailure: HookFailure | undefined\n\n for (const mw of this.middlewares) {\n const hook = mw.onFinish\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onFinish', () =>\n hook.call(mw, ctx, info),\n )\n if (failure !== undefined) {\n firstFailure ??= failure\n failures.push(failure)\n continue\n }\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 if (firstFailure !== undefined) {\n throw failures.length === 1\n ? firstFailure.error\n : new AggregateError(\n failures.map((f) => f.error),\n `${failures.length} middleware onFinish hooks failed: ` +\n failures.map((f) => f.middleware).join(', '),\n )\n }\n }\n\n /**\n * Run onAbort on all middleware in order.\n *\n * ISOLATED and DELIBERATELY SWALLOWED. `onAbort` is a pure teardown fan-out\n * released from `chat()`'s `finally`, on a path where the outcome is already\n * decided: the run stopped, and the caller is being told why. A throw here has\n * nothing better to report than the abort reason it would DISPLACE — the\n * `finally` would surface a flaky store's error in place of \"client\n * disconnected\" — so failures are logged on the `errors` channel and go no\n * further. That is not a silent failure; it is refusing to let teardown\n * rewrite an outcome it did not produce.\n *\n * Isolation matters independently: these hooks release PER-MIDDLEWARE\n * resources (`withSandbox.onAbort` detaches or destroys the sandbox and stamps\n * `detachedSince`; `withPersistence.onAbort` records the run status through the\n * store), so an unguarded loop turns one transient store error into a\n * permanently leaked sandbox for every middleware ordered after it.\n */\n async runOnAbort(\n ctx: ChatMiddlewareContext<TContext>,\n info: AbortInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n const hook = mw.onAbort\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onAbort', () =>\n hook.call(mw, ctx, info),\n )\n if (failure === undefined && !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 * ISOLATED and DELIBERATELY SWALLOWED, for the same reason as\n * {@link runOnAbort} and NOT merely because it is teardown: the run has\n * already failed, `info.error` IS that failure, and `chat()` rethrows it to the\n * caller the moment this fan-out returns. A propagated hook throw could only\n * REPLACE the run's real error with a teardown artifact — strictly less\n * information for the caller, who is already learning the run failed. Reporting\n * would buy nothing and cost the diagnosis, so failures are logged on the\n * `errors` channel and stop there.\n *\n * Contrast {@link runOnFinish}, where nothing else is telling the caller\n * anything is wrong — which is why that one reports.\n */\n async runOnError(\n ctx: ChatMiddlewareContext<TContext>,\n info: ErrorInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n const hook = mw.onError\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onError', () =>\n hook.call(mw, ctx, info),\n )\n if (failure === undefined && !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<TContext>,\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 onShouldContinue through middleware in order (AND semantics).\n * Any explicit `false` stops further iterations; `true` / void / undefined pass.\n * Called after `agentLoopStrategy` has already approved continuation.\n */\n async runOnShouldContinue(\n ctx: ChatMiddlewareContext<TContext>,\n state: AgentLoopState,\n ): Promise<boolean> {\n for (const mw of this.middlewares) {\n if (mw.onShouldContinue) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onShouldContinue(ctx, state)\n if (!skip) {\n this.logger.middleware(\n `hook=onShouldContinue middleware=${mw.name ?? 'unnamed'}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onShouldContinue',\n result,\n },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onShouldContinue',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: result === false,\n })\n }\n if (result === false) {\n return false\n }\n }\n }\n return true\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<TContext>,\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compose.js","names":[],"sources":["../../../../../src/activities/chat/middleware/compose.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai-event-client'\nimport type { AgentLoopState, 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 InterruptBoundaryPhase,\n InterruptResolutionCollection,\n InterruptToolResume,\n IterationInfo,\n SandboxFileHookEvent,\n StructuredOutputMiddlewareConfig,\n ToolCallHookContext,\n ToolPhaseCompleteInfo,\n UsageInfo,\n} from './types'\nimport type {\n GenericInterruptRequest,\n InterruptDefinition,\n} from '../../../interrupt-definition'\n\n/** One middleware's terminal-hook throw, captured instead of propagated. */\ninterface HookFailure {\n middleware: string\n error: unknown\n}\n\n/** Check if a middleware should be skipped for instrumentation events. */\nfunction shouldSkipInstrumentation(mw: ChatMiddleware<any, any>): 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<any>) {\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 TContext = unknown,\n TInterruptDefinitions extends InterruptDefinition<any, any, any, any> =\n InterruptDefinition<any, any, any, any>,\n> {\n private readonly middlewares: ReadonlyArray<\n ChatMiddleware<TContext, TInterruptDefinitions>\n >\n private readonly logger: InternalLogger\n\n constructor(\n middlewares: ReadonlyArray<ChatMiddleware<TContext, TInterruptDefinitions>>,\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 async runOnInterruptBoundary(\n ctx: ChatMiddlewareContext<TContext> & { phase: InterruptBoundaryPhase },\n ): Promise<ReadonlyArray<GenericInterruptRequest<TInterruptDefinitions>>> {\n const requests: Array<GenericInterruptRequest<TInterruptDefinitions>> = []\n for (const mw of this.middlewares) {\n if (mw.onInterruptBoundary) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onInterruptBoundary(ctx)\n if (result?.interrupts) requests.push(...result.interrupts)\n if (!skip) {\n this.logger.middleware(\n `hook=onInterruptBoundary middleware=${mw.name ?? 'unnamed'}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onInterruptBoundary',\n },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onInterruptBoundary',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: result?.interrupts !== undefined,\n })\n }\n }\n }\n return requests\n }\n\n async runOnInterruptResolution(\n ctx: ChatMiddlewareContext<TContext>,\n resolutions: InterruptResolutionCollection<TInterruptDefinitions>,\n ): Promise<{ toolResume?: InterruptToolResume }> {\n let toolResume: InterruptToolResume | undefined\n for (const mw of this.middlewares) {\n if (mw.onInterruptResolution) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const next = await mw.onInterruptResolution(ctx, resolutions)\n if (next?.toolResume !== undefined) {\n const priority: Record<InterruptToolResume, number> = {\n continue: 0,\n cancel: 1,\n stop: 2,\n }\n if (\n toolResume === undefined ||\n priority[next.toolResume] > priority[toolResume]\n ) {\n toolResume = next.toolResume\n }\n }\n if (!skip) {\n this.logger.middleware(\n `hook=onInterruptResolution middleware=${mw.name ?? 'unnamed'}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onInterruptResolution',\n },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onInterruptResolution',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: next !== undefined,\n })\n }\n }\n }\n return toolResume === undefined ? {} : { toolResume }\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<TContext>,\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).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,\n })\n }\n }\n }\n }\n return current\n }\n\n /**\n * Pipe config through all middleware onStructuredOutputConfig 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 * Called once at the structured-output boundary, before runOnConfig at the\n * same boundary (which receives a ChatMiddlewareConfig view, no outputSchema).\n */\n async runOnStructuredOutputConfig(\n ctx: ChatMiddlewareContext<TContext>,\n config: StructuredOutputMiddlewareConfig,\n ): Promise<StructuredOutputMiddlewareConfig> {\n let current = config\n for (const mw of this.middlewares) {\n if (mw.onStructuredOutputConfig) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onStructuredOutputConfig(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).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: 'onStructuredOutputConfig',\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 // `result` is `Partial<StructuredOutputMiddlewareConfig>` —\n // Object.fromEntries(Object.entries(result)) yields the\n // structural `Record<string, unknown>` the event emitter wants\n // without an `as` cast.\n changes: Object.fromEntries(Object.entries(result)),\n })\n }\n }\n }\n }\n return current\n }\n\n /**\n * Run all `setup` hooks in array order, then assert every declared `provides`\n * capability was actually provided. Wires the last-wins duplicate-provide\n * warning into the registry. Runs before init `onConfig`.\n *\n * Takes the full `ChatMiddlewareContext` — the same stable context the engine\n * threads through every other hook — because it both forwards `ctx` to each\n * `setup` hook and emits instrumentation events from it.\n */\n async runSetup(ctx: ChatMiddlewareContext<TContext>): Promise<void> {\n ctx.capabilities.setOnDuplicate((name) => {\n this.logger.warn(\n `capability \"${name}\" was provided more than once; last provider wins`,\n { capability: name },\n )\n })\n\n for (const mw of this.middlewares) {\n if (mw.setup) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.setup(ctx)\n if (!skip) {\n this.logger.middleware(\n `hook=setup middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'setup' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'setup',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n\n for (const mw of this.middlewares) {\n for (const handle of mw.provides ?? []) {\n if (!ctx.capabilities.has(handle)) {\n throw new Error(\n `Middleware \"${mw.name ?? 'unnamed'}\" declares it provides ` +\n `\"${handle.capabilityName}\" but never called provide() in setup().`,\n )\n }\n }\n }\n }\n\n /**\n * Call onStart on all middleware in order.\n */\n async runOnStart(ctx: ChatMiddlewareContext<TContext>): 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<TContext>,\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.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.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.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 * Dispatch a sandbox file event to every middleware's `sandbox` hooks, in\n * array order: the catch-all `onFile` then the type-specific hook. Errors are\n * logged and swallowed so one bad hook can't break the run.\n */\n async runSandboxFile(\n ctx: ChatMiddlewareContext<TContext>,\n event: SandboxFileHookEvent,\n ): Promise<void> {\n const typed = (\n {\n create: 'onFileCreate',\n change: 'onFileChange',\n delete: 'onFileDelete',\n } as const\n )[event.type]\n for (const mw of this.middlewares) {\n const hooks = mw.sandbox\n if (!hooks) continue\n for (const fn of [hooks.onFile, hooks[typed]]) {\n if (!fn) continue\n try {\n await fn(ctx, event)\n } catch (error) {\n this.logger.sandbox(\n `hook=${typed} middleware=${mw.name ?? 'unnamed'} threw`,\n { middleware: mw.name ?? 'unnamed', error },\n )\n }\n }\n }\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<TContext>,\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<TContext>,\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<TContext>,\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 * Await ONE terminal hook and RETURN its throw instead of letting it escape\n * the caller's loop, logging it on the `errors` channel first so the failure\n * is never invisible. `undefined` means the hook completed.\n *\n * Capturing (rather than swallowing at this level) is what lets isolation and\n * reporting coexist: every caller gives every middleware its turn, and then\n * each decides on its own whether the collected failures are worth telling the\n * caller about. See {@link runOnFinish} vs {@link runOnAbort} /\n * {@link runOnError}.\n */\n private async captureTerminalHook(\n mw: ChatMiddleware<TContext, TInterruptDefinitions>,\n hookName: 'onFinish' | 'onAbort' | 'onError',\n invoke: () => void | Promise<void>,\n ): Promise<HookFailure | undefined> {\n try {\n await invoke()\n return undefined\n } catch (error) {\n this.logger.errors(`middleware ${hookName} hook failed`, {\n middleware: mw.name ?? 'unnamed',\n hook: hookName,\n error,\n })\n return { middleware: mw.name ?? 'unnamed', error }\n }\n }\n\n /**\n * Run onFinish on all middleware in order.\n *\n * ISOLATED **and** REPORTED. `onFinish` is the only terminal fan-out on the\n * SUCCESS path, and it is where `withPersistence.onFinish` writes the\n * assistant turn through the store. So the two properties are needed together\n * and neither may be traded for the other:\n *\n * - ISOLATION: every middleware's hook runs even if an earlier one threw, so a\n * transient store error cannot skip a later middleware's own bookkeeping.\n * Each failure is captured by {@link captureTerminalHook}, not propagated\n * mid-loop.\n * - REPORTING: after the loop, the failures are rethrown. `chat()`'s catch\n * treats what we throw as a genuine error (it is not a\n * `MiddlewareAbortError`, and `structuralInterruptFailure` does not match\n * it) and rethrows it out of the generator.\n *\n * What that rethrow can and cannot achieve depends on the transport, because\n * this fan-out is awaited AFTER the adapter's `RUN_FINISHED` has already been\n * yielded (`chat()` yields terminal chunks while streaming, then awaits this\n * hook on its way out). The success terminal is therefore already gone; the\n * rethrow can only append to what the consumer saw, never retract it:\n *\n * - NON-DURABLE transport: the throw escapes the generator mid-response, and\n * the SSE / HTTP-stream encoder turns it into a TRAILING `RUN_ERROR` on the\n * wire carrying the store's own message and `code`. `ai-client` surfaces\n * that as an error status, so the user is not told the turn was saved when\n * it was not.\n * - DURABLE transport: the throw reaches the durability sink instead. The\n * terminal was already persisted AND forwarded, so the sink deliberately\n * does NOT append a second, contradictory terminal, and `terminalForwarded`\n * (see `stream-to-response.ts`) suppresses the rethrow to the live consumer.\n * The `RUN_FINISHED` stands and the failure is RECORDED SERVER-SIDE on the\n * sink's `errors` channel. That is the intended outcome, not a gap: the save\n * failed, not the run — the consumer did receive the complete stream, so\n * telling it the run errored would be the lie. What the rethrow buys here is\n * that the sink sees the failure at all; while this loop swallowed, the only\n * trace anywhere was {@link captureTerminalHook}'s log line.\n *\n * Either way, swallowing is the one option ruled out: a failed\n * `messages.append` would otherwise leave a `completed` run record with the\n * assistant turn missing from storage and nothing beyond a middleware log\n * line, and the client would go on to send a history the server has no record\n * of.\n *\n * A single failure is rethrown AS-IS so the store's own error — its message,\n * `cause`, `code` and `instanceof` identity — is what reaches the caller and\n * the wire; wrapping the common case would bury it. Two or more become an\n * `AggregateError` (never a `MiddlewareAbortError`, so it cannot be mistaken\n * for an abort) rather than picking a winner and dropping the rest.\n */\n async runOnFinish(\n ctx: ChatMiddlewareContext<TContext>,\n info: FinishInfo,\n ): Promise<void> {\n const failures: Array<HookFailure> = []\n let firstFailure: HookFailure | undefined\n\n for (const mw of this.middlewares) {\n const hook = mw.onFinish\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onFinish', () =>\n hook.call(mw, ctx, info),\n )\n if (failure !== undefined) {\n firstFailure ??= failure\n failures.push(failure)\n continue\n }\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 if (firstFailure !== undefined) {\n throw failures.length === 1\n ? firstFailure.error\n : new AggregateError(\n failures.map((f) => f.error),\n `${failures.length} middleware onFinish hooks failed: ` +\n failures.map((f) => f.middleware).join(', '),\n )\n }\n }\n\n /**\n * Run onAbort on all middleware in order.\n *\n * ISOLATED and DELIBERATELY SWALLOWED. `onAbort` is a pure teardown fan-out\n * released from `chat()`'s `finally`, on a path where the outcome is already\n * decided: the run stopped, and the caller is being told why. A throw here has\n * nothing better to report than the abort reason it would DISPLACE — the\n * `finally` would surface a flaky store's error in place of \"client\n * disconnected\" — so failures are logged on the `errors` channel and go no\n * further. That is not a silent failure; it is refusing to let teardown\n * rewrite an outcome it did not produce.\n *\n * Isolation matters independently: these hooks release PER-MIDDLEWARE\n * resources (`withSandbox.onAbort` detaches or destroys the sandbox and stamps\n * `detachedSince`; `withPersistence.onAbort` records the run status through the\n * store), so an unguarded loop turns one transient store error into a\n * permanently leaked sandbox for every middleware ordered after it.\n */\n async runOnAbort(\n ctx: ChatMiddlewareContext<TContext>,\n info: AbortInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n const hook = mw.onAbort\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onAbort', () =>\n hook.call(mw, ctx, info),\n )\n if (failure === undefined && !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 * ISOLATED and DELIBERATELY SWALLOWED, for the same reason as\n * {@link runOnAbort} and NOT merely because it is teardown: the run has\n * already failed, `info.error` IS that failure, and `chat()` rethrows it to the\n * caller the moment this fan-out returns. A propagated hook throw could only\n * REPLACE the run's real error with a teardown artifact — strictly less\n * information for the caller, who is already learning the run failed. Reporting\n * would buy nothing and cost the diagnosis, so failures are logged on the\n * `errors` channel and stop there.\n *\n * Contrast {@link runOnFinish}, where nothing else is telling the caller\n * anything is wrong — which is why that one reports.\n */\n async runOnError(\n ctx: ChatMiddlewareContext<TContext>,\n info: ErrorInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n const hook = mw.onError\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onError', () =>\n hook.call(mw, ctx, info),\n )\n if (failure === undefined && !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<TContext>,\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 onShouldContinue through middleware in order (AND semantics).\n * Any explicit `false` stops further iterations; `true` / void / undefined pass.\n * Called after `agentLoopStrategy` has already approved continuation.\n */\n async runOnShouldContinue(\n ctx: ChatMiddlewareContext<TContext>,\n state: AgentLoopState,\n ): Promise<boolean> {\n for (const mw of this.middlewares) {\n if (mw.onShouldContinue) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onShouldContinue(ctx, state)\n if (!skip) {\n this.logger.middleware(\n `hook=onShouldContinue middleware=${mw.name ?? 'unnamed'}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onShouldContinue',\n result,\n },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onShouldContinue',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: result === false,\n })\n }\n if (result === false) {\n return false\n }\n }\n }\n return true\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<TContext>,\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"],"mappings":"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"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { CapabilityHandle } from './capabilities.js';
|
|
2
2
|
import { ChatMiddleware } from './types.js';
|
|
3
|
+
import { InterruptDefinition } from '../../../interrupt-definition.js';
|
|
4
|
+
type AnyInterruptDefinition = InterruptDefinition<any, any, any, any>;
|
|
3
5
|
/**
|
|
4
6
|
* A middleware whose `requires`/`provides` tuple types are captured precisely
|
|
5
7
|
* (via `const` inference) for the array coverage check and the builder.
|
|
6
8
|
*/
|
|
7
|
-
export interface DefinedChatMiddleware<TContext, TRequires extends ReadonlyArray<CapabilityHandle>, TProvides extends ReadonlyArray<CapabilityHandle
|
|
9
|
+
export interface DefinedChatMiddleware<TContext, TRequires extends ReadonlyArray<CapabilityHandle>, TProvides extends ReadonlyArray<CapabilityHandle>, TInterruptDefinitions extends AnyInterruptDefinition = never> extends ChatMiddleware<TContext, TInterruptDefinitions> {
|
|
8
10
|
requires?: TRequires;
|
|
9
11
|
provides?: TProvides;
|
|
10
12
|
}
|
|
@@ -14,7 +16,8 @@ export interface DefinedChatMiddleware<TContext, TRequires extends ReadonlyArray
|
|
|
14
16
|
* `chat()` array coverage check and `createChatMiddleware` builder can read the
|
|
15
17
|
* exact `requires`/`provides`.
|
|
16
18
|
*/
|
|
17
|
-
export declare function defineChatMiddleware<TContext = unknown, const TRequires extends ReadonlyArray<CapabilityHandle> = readonly [], const TProvides extends ReadonlyArray<CapabilityHandle> = readonly []>(middleware: ChatMiddleware<TContext> & {
|
|
19
|
+
export declare function defineChatMiddleware<TContext = unknown, const TRequires extends ReadonlyArray<CapabilityHandle> = readonly [], const TProvides extends ReadonlyArray<CapabilityHandle> = readonly [], TInterruptDefinitions extends AnyInterruptDefinition = never>(middleware: ChatMiddleware<TContext, TInterruptDefinitions> & {
|
|
18
20
|
requires?: TRequires;
|
|
19
21
|
provides?: TProvides;
|
|
20
|
-
}): DefinedChatMiddleware<TContext, TRequires, TProvides>;
|
|
22
|
+
}): DefinedChatMiddleware<TContext, TRequires, TProvides, TInterruptDefinitions>;
|
|
23
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define.js","names":[],"sources":["../../../../../src/activities/chat/middleware/define.ts"],"sourcesContent":["import type { CapabilityHandle } from './capabilities'\nimport type { ChatMiddleware } from './types'\n\n/**\n * A middleware whose `requires`/`provides` tuple types are captured precisely\n * (via `const` inference) for the array coverage check and the builder.\n */\nexport interface DefinedChatMiddleware<\n TContext,\n TRequires extends ReadonlyArray<CapabilityHandle>,\n TProvides extends ReadonlyArray<CapabilityHandle>,\n> extends ChatMiddleware<TContext> {\n requires?: TRequires\n provides?: TProvides\n}\n\n/**\n * Identity helper for authoring middleware with precise capability inference.\n * Returns the middleware unchanged at runtime; only sharpens its type so the\n * `chat()` array coverage check and `createChatMiddleware` builder can read the\n * exact `requires`/`provides`.\n */\nexport function defineChatMiddleware<\n TContext = unknown,\n const TRequires extends ReadonlyArray<CapabilityHandle> = readonly [],\n const TProvides extends ReadonlyArray<CapabilityHandle> = readonly [],\n>(\n middleware: ChatMiddleware<TContext> & {\n requires?: TRequires\n provides?: TProvides\n },\n): DefinedChatMiddleware
|
|
1
|
+
{"version":3,"file":"define.js","names":[],"sources":["../../../../../src/activities/chat/middleware/define.ts"],"sourcesContent":["import type { CapabilityHandle } from './capabilities'\nimport type { ChatMiddleware } from './types'\nimport type { InterruptDefinition } from '../../../interrupt-definition'\n\ntype AnyInterruptDefinition = InterruptDefinition<any, any, any, any>\n\n/**\n * A middleware whose `requires`/`provides` tuple types are captured precisely\n * (via `const` inference) for the array coverage check and the builder.\n */\nexport interface DefinedChatMiddleware<\n TContext,\n TRequires extends ReadonlyArray<CapabilityHandle>,\n TProvides extends ReadonlyArray<CapabilityHandle>,\n TInterruptDefinitions extends AnyInterruptDefinition = never,\n> extends ChatMiddleware<TContext, TInterruptDefinitions> {\n requires?: TRequires\n provides?: TProvides\n}\n\n/**\n * Identity helper for authoring middleware with precise capability inference.\n * Returns the middleware unchanged at runtime; only sharpens its type so the\n * `chat()` array coverage check and `createChatMiddleware` builder can read the\n * exact `requires`/`provides`.\n */\nexport function defineChatMiddleware<\n TContext = unknown,\n const TRequires extends ReadonlyArray<CapabilityHandle> = readonly [],\n const TProvides extends ReadonlyArray<CapabilityHandle> = readonly [],\n TInterruptDefinitions extends AnyInterruptDefinition = never,\n>(\n middleware: ChatMiddleware<TContext, TInterruptDefinitions> & {\n requires?: TRequires\n provides?: TProvides\n },\n): DefinedChatMiddleware<\n TContext,\n TRequires,\n TProvides,\n TInterruptDefinitions\n> {\n return middleware\n}\n"],"mappings":";;;;;;;AA0BA,SAAgB,qBAMd,YASA;CACA,OAAO;AACT"}
|