@tanstack/ai 0.43.1 → 0.44.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.js +18 -0
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +21 -8
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/embed/adapter.d.ts +69 -0
- package/dist/esm/activities/embed/adapter.js +23 -0
- package/dist/esm/activities/embed/adapter.js.map +1 -0
- package/dist/esm/activities/embed/index.d.ts +117 -0
- package/dist/esm/activities/embed/index.js +166 -0
- package/dist/esm/activities/embed/index.js.map +1 -0
- package/dist/esm/activities/error-payload.d.ts +8 -0
- package/dist/esm/activities/error-payload.js +29 -17
- package/dist/esm/activities/error-payload.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +12 -0
- package/dist/esm/activities/generateAudio/index.js +19 -6
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/index.d.ts +12 -0
- package/dist/esm/activities/generateImage/index.js +21 -7
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +17 -1
- package/dist/esm/activities/generateSpeech/index.js +19 -6
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +17 -1
- package/dist/esm/activities/generateTranscription/index.js +19 -6
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +18 -0
- package/dist/esm/activities/generateVideo/index.js +54 -15
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/index.d.ts +8 -2
- package/dist/esm/activities/index.js +11 -7
- package/dist/esm/activities/middleware/types.d.ts +1 -1
- package/dist/esm/activities/rerank/adapter.d.ts +63 -0
- package/dist/esm/activities/rerank/adapter.js +23 -0
- package/dist/esm/activities/rerank/adapter.js.map +1 -0
- package/dist/esm/activities/rerank/index.d.ts +92 -0
- package/dist/esm/activities/rerank/index.js +163 -0
- package/dist/esm/activities/rerank/index.js.map +1 -0
- package/dist/esm/activities/summarize/index.d.ts +17 -1
- package/dist/esm/activities/summarize/index.js +19 -5
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/index.d.ts +7 -2
- package/dist/esm/index.js +5 -1
- package/dist/esm/middlewares/otel.js +20 -2
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/types.d.ts +195 -0
- package/dist/esm/utilities/activity-abort.d.ts +53 -0
- package/dist/esm/utilities/activity-abort.js +150 -0
- package/dist/esm/utilities/activity-abort.js.map +1 -0
- package/dist/esm/utilities/embedding-input.d.ts +32 -0
- package/dist/esm/utilities/embedding-input.js +61 -0
- package/dist/esm/utilities/embedding-input.js.map +1 -0
- package/package.json +3 -3
- package/skills/ai-core/media-generation/SKILL.md +55 -23
- package/src/activities/chat/index.ts +27 -2
- package/src/activities/chat/messages.ts +30 -1
- package/src/activities/embed/adapter.ts +112 -0
- package/src/activities/embed/index.ts +318 -0
- package/src/activities/error-payload.ts +41 -9
- package/src/activities/generateAudio/index.ts +47 -5
- package/src/activities/generateImage/index.ts +48 -5
- package/src/activities/generateSpeech/index.ts +52 -9
- package/src/activities/generateTranscription/index.ts +52 -9
- package/src/activities/generateVideo/index.ts +131 -33
- package/src/activities/index.ts +44 -0
- package/src/activities/middleware/types.ts +2 -0
- package/src/activities/rerank/adapter.ts +90 -0
- package/src/activities/rerank/index.ts +302 -0
- package/src/activities/summarize/index.ts +59 -19
- package/src/index.ts +19 -0
- package/src/middlewares/otel.ts +38 -3
- package/src/types.ts +219 -0
- package/src/utilities/activity-abort.ts +197 -0
- package/src/utilities/embedding-input.ts +83 -0
|
@@ -101,8 +101,10 @@ function buildUserOrToolMessage(uiMessage) {
|
|
|
101
101
|
const contentParts = [];
|
|
102
102
|
for (const part of uiMessage.parts) if (isContentPart(part)) contentParts.push(part);
|
|
103
103
|
return {
|
|
104
|
+
id: uiMessage.id,
|
|
104
105
|
role: uiMessage.role,
|
|
105
|
-
content: collapseContentParts(contentParts)
|
|
106
|
+
content: collapseContentParts(contentParts),
|
|
107
|
+
...uiMessage.createdAt !== void 0 && { createdAt: uiMessage.createdAt }
|
|
106
108
|
};
|
|
107
109
|
}
|
|
108
110
|
function createSegment() {
|
|
@@ -134,10 +136,12 @@ function buildAssistantMessages(uiMessage) {
|
|
|
134
136
|
const hasToolCalls = current.toolCalls.length > 0;
|
|
135
137
|
if (hasContent || hasToolCalls) {
|
|
136
138
|
messageList.push({
|
|
139
|
+
id: uiMessage.id,
|
|
137
140
|
role: "assistant",
|
|
138
141
|
content,
|
|
139
142
|
...hasToolCalls && { toolCalls: current.toolCalls },
|
|
140
|
-
...pendingThinking.length > 0 && { thinking: pendingThinking }
|
|
143
|
+
...pendingThinking.length > 0 && { thinking: pendingThinking },
|
|
144
|
+
...uiMessage.createdAt !== void 0 && { createdAt: uiMessage.createdAt }
|
|
141
145
|
});
|
|
142
146
|
pendingThinking = [];
|
|
143
147
|
}
|
|
@@ -166,9 +170,11 @@ function buildAssistantMessages(uiMessage) {
|
|
|
166
170
|
flushSegment();
|
|
167
171
|
if ((part.state === "complete" || part.state === "error") && !emittedToolResultIds.has(part.toolCallId)) {
|
|
168
172
|
messageList.push({
|
|
173
|
+
id: uiMessage.id,
|
|
169
174
|
role: "tool",
|
|
170
175
|
content: part.content,
|
|
171
|
-
toolCallId: part.toolCallId
|
|
176
|
+
toolCallId: part.toolCallId,
|
|
177
|
+
...uiMessage.createdAt !== void 0 && { createdAt: uiMessage.createdAt }
|
|
172
178
|
});
|
|
173
179
|
emittedToolResultIds.add(part.toolCallId);
|
|
174
180
|
}
|
|
@@ -196,29 +202,35 @@ function buildAssistantMessages(uiMessage) {
|
|
|
196
202
|
if (part.type !== "tool-call") continue;
|
|
197
203
|
if (part.output !== void 0 && !emittedToolResultIds.has(part.id)) {
|
|
198
204
|
messageList.push({
|
|
205
|
+
id: uiMessage.id,
|
|
199
206
|
role: "tool",
|
|
200
207
|
content: normalizeToolResult(part.output),
|
|
201
|
-
toolCallId: part.id
|
|
208
|
+
toolCallId: part.id,
|
|
209
|
+
...uiMessage.createdAt !== void 0 && { createdAt: uiMessage.createdAt }
|
|
202
210
|
});
|
|
203
211
|
emittedToolResultIds.add(part.id);
|
|
204
212
|
}
|
|
205
213
|
if (part.output === void 0 && part.state === "approval-responded" && part.approval?.approved !== void 0 && !emittedToolResultIds.has(part.id)) {
|
|
206
214
|
const approved = part.approval.approved;
|
|
207
215
|
messageList.push({
|
|
216
|
+
id: uiMessage.id,
|
|
208
217
|
role: "tool",
|
|
209
218
|
content: JSON.stringify({
|
|
210
219
|
approved,
|
|
211
220
|
...approved && { pendingExecution: true },
|
|
212
221
|
message: approved ? "User approved this action" : "User denied this action"
|
|
213
222
|
}),
|
|
214
|
-
toolCallId: part.id
|
|
223
|
+
toolCallId: part.id,
|
|
224
|
+
...uiMessage.createdAt !== void 0 && { createdAt: uiMessage.createdAt }
|
|
215
225
|
});
|
|
216
226
|
emittedToolResultIds.add(part.id);
|
|
217
227
|
}
|
|
218
228
|
}
|
|
219
229
|
if (messageList.length === 0) messageList.push({
|
|
230
|
+
id: uiMessage.id,
|
|
220
231
|
role: "assistant",
|
|
221
|
-
content: null
|
|
232
|
+
content: null,
|
|
233
|
+
...uiMessage.createdAt !== void 0 && { createdAt: uiMessage.createdAt }
|
|
222
234
|
});
|
|
223
235
|
return messageList;
|
|
224
236
|
}
|
|
@@ -278,7 +290,8 @@ function modelMessageToUIMessage(modelMessage, id) {
|
|
|
278
290
|
return {
|
|
279
291
|
id: id || generateMessageId(),
|
|
280
292
|
role: modelMessage.role === "tool" ? "assistant" : modelMessage.role,
|
|
281
|
-
parts
|
|
293
|
+
parts,
|
|
294
|
+
...modelMessage.createdAt !== void 0 && { createdAt: modelMessage.createdAt }
|
|
282
295
|
};
|
|
283
296
|
}
|
|
284
297
|
/**
|
|
@@ -417,7 +430,7 @@ function normalizeToUIMessage(message, generateId) {
|
|
|
417
430
|
};
|
|
418
431
|
else return {
|
|
419
432
|
...modelMessageToUIMessage(message, generateId()),
|
|
420
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
433
|
+
createdAt: message.createdAt ?? /* @__PURE__ */ new Date()
|
|
421
434
|
};
|
|
422
435
|
}
|
|
423
436
|
/**
|
|
@@ -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 role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n }\n}\n\n// Accumulator for building an assistant segment (content + tool calls)\ninterface AssistantSegment {\n contentParts: Array<ContentPart>\n toolCalls: Array<{\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n /** Provider-specific metadata that round-trips with the tool call.\n * Untyped at this framework layer; adapters narrow it via their\n * `TToolCallMetadata` generic. */\n metadata?: unknown\n }>\n}\n\nfunction createSegment(): AssistantSegment {\n return { contentParts: [], toolCalls: [] }\n}\n\nfunction isToolCallIncluded(part: ToolCallPart): boolean {\n return (\n part.state === 'input-complete' ||\n part.state === '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 const messageList: Array<ModelMessage> = []\n let current = createSegment()\n let pendingThinking: Array<{ content: string; signature?: string }> = []\n\n // Track emitted tool result IDs to avoid duplicates.\n // A tool call can have BOTH an explicit tool-result part AND an output\n // field on the tool-call part. We only want one per tool call ID.\n const emittedToolResultIds = new Set<string>()\n\n function flushSegment(): void {\n const content = collapseContentParts(current.contentParts)\n const hasContent = content !== null\n const hasToolCalls = current.toolCalls.length > 0\n\n if (hasContent || hasToolCalls) {\n messageList.push({\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\n })\n pendingThinking = []\n }\n current = createSegment()\n }\n\n for (const part of uiMessage.parts) {\n switch (part.type) {\n case 'text':\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n current.contentParts.push(part)\n break\n\n case 'tool-call':\n if (isToolCallIncluded(part)) {\n current.toolCalls.push({\n id: part.id,\n type: 'function' as const,\n function: {\n name: part.name,\n arguments: part.arguments,\n },\n ...(part.metadata !== undefined && { metadata: part.metadata }),\n })\n }\n break\n\n case 'tool-result':\n // Flush the current assistant segment before emitting the tool result\n flushSegment()\n\n // Emit the tool result\n if (\n (part.state === 'complete' || part.state === 'error') &&\n !emittedToolResultIds.has(part.toolCallId)\n ) {\n messageList.push({\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n })\n emittedToolResultIds.add(part.toolCallId)\n }\n break\n\n case 'thinking':\n if (part.content) {\n pendingThinking.push({\n content: part.content,\n ...(part.signature && { signature: part.signature }),\n })\n }\n break\n\n case 'structured-output':\n // Only emit completed structured responses into history. Streaming or\n // errored buffers would push malformed JSON into the next LLM turn's\n // assistant content. `raw` is the source of truth; `data` is the\n // defensive fallback for terminal-only completes that didn't ship raw.\n if (part.status === 'complete') {\n const serialized =\n part.raw !== ''\n ? part.raw\n : part.data !== undefined\n ? safeJsonStringify(part.data)\n : ''\n if (serialized !== '') {\n current.contentParts.push({ type: 'text', content: serialized })\n }\n }\n break\n\n 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 role: 'tool',\n content: normalizeToolResult(part.output),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response without output — emit approval status for iteration tracking\n if (\n part.output === undefined &&\n part.state === 'approval-responded' &&\n part.approval?.approved !== undefined &&\n !emittedToolResultIds.has(part.id)\n ) {\n const approved = part.approval.approved\n messageList.push({\n role: 'tool',\n content: JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n }),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n }\n\n // If no messages were produced (e.g., empty parts), emit a minimal assistant message\n if (messageList.length === 0) {\n messageList.push({\n role: 'assistant',\n content: null,\n })\n }\n\n return messageList\n}\n\n/**\n * Convert a ModelMessage to UIMessage\n *\n * This conversion creates a parts-based structure:\n * - content field → TextPart\n * - toolCalls array → ToolCallPart[]\n * - role=\"tool\" messages should be converted separately and merged\n *\n * @param modelMessage - The ModelMessage to convert\n * @param id - Optional ID for the UIMessage (generated if not provided)\n * @returns A UIMessage with parts\n */\nexport function modelMessageToUIMessage(\n modelMessage: ModelMessage,\n id?: string,\n): UIMessage {\n const parts: Array<MessagePart> = []\n\n if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {\n for (const thinking of modelMessage.thinking) {\n if (!thinking.content) continue\n parts.push({\n type: 'thinking',\n content: thinking.content,\n ...(thinking.signature && { signature: thinking.signature }),\n })\n }\n }\n\n // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n // 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 }\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: 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 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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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { EmbeddingModelInputModalitiesByName, EmbeddingOptions, EmbeddingResult } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for embedding adapter instances
|
|
4
|
+
*/
|
|
5
|
+
export interface EmbeddingAdapterConfig {
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Embedding adapter interface with pre-resolved generics.
|
|
14
|
+
*
|
|
15
|
+
* An adapter is created by a provider function: `provider('model')` → `adapter`
|
|
16
|
+
* All type resolution happens at the provider call site, not in this interface.
|
|
17
|
+
*
|
|
18
|
+
* Generic parameters:
|
|
19
|
+
* - TModel: The specific model name (e.g., 'text-embedding-3-small')
|
|
20
|
+
* - TProviderOptions: Base provider-specific options (already resolved)
|
|
21
|
+
* - TModelProviderOptionsByName: Map from model name to its specific provider options
|
|
22
|
+
* - TModelInputModalitiesByName: Map from model name to the input modalities it
|
|
23
|
+
* accepts (constrains the `input` item types at compile time)
|
|
24
|
+
*/
|
|
25
|
+
export interface EmbeddingAdapter<TModel extends string = string, TProviderOptions extends object = Record<string, unknown>, TModelProviderOptionsByName extends Record<string, any> = Record<string, any>, TModelInputModalitiesByName extends EmbeddingModelInputModalitiesByName = EmbeddingModelInputModalitiesByName> {
|
|
26
|
+
/** Discriminator for adapter kind */
|
|
27
|
+
readonly kind: 'embedding';
|
|
28
|
+
/** Adapter name identifier */
|
|
29
|
+
readonly name: string;
|
|
30
|
+
/** The model this adapter is configured for */
|
|
31
|
+
readonly model: TModel;
|
|
32
|
+
/**
|
|
33
|
+
* @internal Type-only properties for inference. Not assigned at runtime.
|
|
34
|
+
*/
|
|
35
|
+
'~types': {
|
|
36
|
+
providerOptions: TProviderOptions;
|
|
37
|
+
modelProviderOptionsByName: TModelProviderOptionsByName;
|
|
38
|
+
modelInputModalitiesByName: TModelInputModalitiesByName;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Generate embeddings for the input items (one vector per item)
|
|
42
|
+
*/
|
|
43
|
+
createEmbeddings: (options: EmbeddingOptions<TProviderOptions>) => Promise<EmbeddingResult>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* An EmbeddingAdapter with any/unknown type parameters.
|
|
47
|
+
* Useful as a constraint in generic functions and interfaces.
|
|
48
|
+
*/
|
|
49
|
+
export type AnyEmbeddingAdapter = EmbeddingAdapter<any, any, any, any>;
|
|
50
|
+
/**
|
|
51
|
+
* Abstract base class for embedding adapters.
|
|
52
|
+
* Extend this class to implement an embedding adapter for a specific provider.
|
|
53
|
+
*
|
|
54
|
+
* Generic parameters match EmbeddingAdapter - all pre-resolved by the provider function.
|
|
55
|
+
*/
|
|
56
|
+
export declare abstract class BaseEmbeddingAdapter<TModel extends string = string, TProviderOptions extends object = Record<string, unknown>, TModelProviderOptionsByName extends Record<string, any> = Record<string, any>, TModelInputModalitiesByName extends EmbeddingModelInputModalitiesByName = EmbeddingModelInputModalitiesByName> implements EmbeddingAdapter<TModel, TProviderOptions, TModelProviderOptionsByName, TModelInputModalitiesByName> {
|
|
57
|
+
readonly kind: "embedding";
|
|
58
|
+
abstract readonly name: string;
|
|
59
|
+
readonly model: TModel;
|
|
60
|
+
'~types': {
|
|
61
|
+
providerOptions: TProviderOptions;
|
|
62
|
+
modelProviderOptionsByName: TModelProviderOptionsByName;
|
|
63
|
+
modelInputModalitiesByName: TModelInputModalitiesByName;
|
|
64
|
+
};
|
|
65
|
+
protected config: EmbeddingAdapterConfig;
|
|
66
|
+
constructor(model: TModel, config?: EmbeddingAdapterConfig);
|
|
67
|
+
abstract createEmbeddings(options: EmbeddingOptions<TProviderOptions>): Promise<EmbeddingResult>;
|
|
68
|
+
protected generateId(prefix?: string): string;
|
|
69
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/activities/embed/adapter.ts
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base class for embedding adapters.
|
|
4
|
+
* Extend this class to implement an embedding adapter for a specific provider.
|
|
5
|
+
*
|
|
6
|
+
* Generic parameters match EmbeddingAdapter - all pre-resolved by the provider function.
|
|
7
|
+
*/
|
|
8
|
+
var BaseEmbeddingAdapter = class {
|
|
9
|
+
kind = "embedding";
|
|
10
|
+
model;
|
|
11
|
+
config;
|
|
12
|
+
constructor(model, config = {}) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.model = model;
|
|
15
|
+
}
|
|
16
|
+
generateId(prefix) {
|
|
17
|
+
return `${prefix ?? this.name}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { BaseEmbeddingAdapter };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","names":[],"sources":["../../../../src/activities/embed/adapter.ts"],"sourcesContent":["import type {\n EmbeddingModelInputModalitiesByName,\n EmbeddingOptions,\n EmbeddingResult,\n} from '../../types'\n\n/**\n * Configuration for embedding adapter instances\n */\nexport interface EmbeddingAdapterConfig {\n apiKey?: string\n baseUrl?: string\n timeout?: number\n maxRetries?: number\n headers?: Record<string, string>\n}\n\n/**\n * Embedding adapter interface with pre-resolved generics.\n *\n * An adapter is created by a provider function: `provider('model')` → `adapter`\n * All type resolution happens at the provider call site, not in this interface.\n *\n * Generic parameters:\n * - TModel: The specific model name (e.g., 'text-embedding-3-small')\n * - TProviderOptions: Base provider-specific options (already resolved)\n * - TModelProviderOptionsByName: Map from model name to its specific provider options\n * - TModelInputModalitiesByName: Map from model name to the input modalities it\n * accepts (constrains the `input` item types at compile time)\n */\nexport interface EmbeddingAdapter<\n TModel extends string = string,\n TProviderOptions extends object = Record<string, unknown>,\n TModelProviderOptionsByName extends Record<string, any> = Record<string, any>,\n TModelInputModalitiesByName extends EmbeddingModelInputModalitiesByName =\n EmbeddingModelInputModalitiesByName,\n> {\n /** Discriminator for adapter kind */\n readonly kind: 'embedding'\n /** Adapter name identifier */\n readonly name: string\n /** The model this adapter is configured for */\n readonly model: TModel\n\n /**\n * @internal Type-only properties for inference. Not assigned at runtime.\n */\n '~types': {\n providerOptions: TProviderOptions\n modelProviderOptionsByName: TModelProviderOptionsByName\n modelInputModalitiesByName: TModelInputModalitiesByName\n }\n\n /**\n * Generate embeddings for the input items (one vector per item)\n */\n createEmbeddings: (\n options: EmbeddingOptions<TProviderOptions>,\n ) => Promise<EmbeddingResult>\n}\n\n/**\n * An EmbeddingAdapter with any/unknown type parameters.\n * Useful as a constraint in generic functions and interfaces.\n */\nexport type AnyEmbeddingAdapter = EmbeddingAdapter<any, any, any, any>\n\n/**\n * Abstract base class for embedding adapters.\n * Extend this class to implement an embedding adapter for a specific provider.\n *\n * Generic parameters match EmbeddingAdapter - all pre-resolved by the provider function.\n */\nexport abstract class BaseEmbeddingAdapter<\n TModel extends string = string,\n TProviderOptions extends object = Record<string, unknown>,\n TModelProviderOptionsByName extends Record<string, any> = Record<string, any>,\n TModelInputModalitiesByName extends EmbeddingModelInputModalitiesByName =\n EmbeddingModelInputModalitiesByName,\n> implements EmbeddingAdapter<\n TModel,\n TProviderOptions,\n TModelProviderOptionsByName,\n TModelInputModalitiesByName\n> {\n readonly kind = 'embedding' as const\n abstract readonly name: string\n readonly model: TModel\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: TProviderOptions\n modelProviderOptionsByName: TModelProviderOptionsByName\n modelInputModalitiesByName: TModelInputModalitiesByName\n }\n\n protected config: EmbeddingAdapterConfig\n\n constructor(model: TModel, config: EmbeddingAdapterConfig = {}) {\n this.config = config\n this.model = model\n }\n\n abstract createEmbeddings(\n options: EmbeddingOptions<TProviderOptions>,\n ): Promise<EmbeddingResult>\n\n protected generateId(prefix?: string): string {\n const p = prefix ?? this.name\n return `${p}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n }\n}\n"],"mappings":";;;;;;;AAyEA,IAAsB,uBAAtB,MAWE;CACA,OAAgB;CAEhB;CASA;CAEA,YAAY,OAAe,SAAiC,CAAC,GAAG;EAC9D,KAAK,SAAS;EACd,KAAK,QAAQ;CACf;CAMA,WAAqB,QAAyB;EAE5C,OAAO,GADG,UAAU,KAAK,KACb,GAAG,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;CACpE;AACF"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { DebugOption } from '../../logger/types.js';
|
|
2
|
+
import { GenerationMiddleware } from '../middleware/types.js';
|
|
3
|
+
import { EmbeddingAdapter } from './adapter.js';
|
|
4
|
+
import { EmbeddingInputItem, EmbeddingInputItemFor, EmbeddingResult } from '../../types.js';
|
|
5
|
+
/** The adapter kind this activity handles */
|
|
6
|
+
export declare const kind: "embedding";
|
|
7
|
+
/**
|
|
8
|
+
* Extract model-specific provider options from an EmbeddingAdapter via ~types.
|
|
9
|
+
* If the model has specific options defined in ModelProviderOptions (and not just via index signature),
|
|
10
|
+
* use those; otherwise fall back to base provider options.
|
|
11
|
+
*/
|
|
12
|
+
export type EmbedProviderOptionsForModel<TAdapter, TModel extends string> = TAdapter extends EmbeddingAdapter<any, infer BaseOptions, infer ModelOptions, any> ? string extends keyof ModelOptions ? BaseOptions : TModel extends keyof ModelOptions ? ModelOptions[TModel] : BaseOptions : object;
|
|
13
|
+
/**
|
|
14
|
+
* Extract the input type a model accepts from an EmbeddingAdapter via ~types.
|
|
15
|
+
* Adapters declare a per-model input-modality map; models in the map get an
|
|
16
|
+
* `input` narrowed to their supported item types (text-only models accept
|
|
17
|
+
* `string | TextPart`), so unsupported items fail at compile time. Adapters
|
|
18
|
+
* without a map fall back to the full EmbeddingInputItem union.
|
|
19
|
+
*/
|
|
20
|
+
export type EmbeddingInputForModel<TAdapter, TModel extends string> = TAdapter extends EmbeddingAdapter<any, any, any, infer ModsByName> ? string extends keyof ModsByName ? // No explicit map - accept the full union
|
|
21
|
+
EmbeddingInputItem | Array<EmbeddingInputItem> : TModel extends keyof ModsByName ? EmbeddingInputItemFor<ModsByName[TModel][number]> | Array<EmbeddingInputItemFor<ModsByName[TModel][number]>> : EmbeddingInputItem | Array<EmbeddingInputItem> : EmbeddingInputItem | Array<EmbeddingInputItem>;
|
|
22
|
+
/**
|
|
23
|
+
* Options for the embed activity.
|
|
24
|
+
* The model is extracted from the adapter's model property.
|
|
25
|
+
*
|
|
26
|
+
* @template TAdapter - The embedding adapter type
|
|
27
|
+
*/
|
|
28
|
+
export type EmbedOptions<TAdapter extends EmbeddingAdapter<string, any, any, any>> = {
|
|
29
|
+
/** The embedding adapter to use (must be created with a model) */
|
|
30
|
+
adapter: TAdapter & {
|
|
31
|
+
kind: typeof kind;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* What to embed: a single item or an array of items. Each item in the array
|
|
35
|
+
* produces exactly one vector. An item is a plain string, a text part, an
|
|
36
|
+
* image part, or — for models that embed text and image together — a fused
|
|
37
|
+
* item written as a nested array of parts (`[textPart, imagePart]`), the
|
|
38
|
+
* same `Array<ContentPart>` shape chat messages use. The accepted item types
|
|
39
|
+
* are narrowed per model via the adapter's input-modality map.
|
|
40
|
+
*/
|
|
41
|
+
input: EmbeddingInputForModel<TAdapter, TAdapter['model']>;
|
|
42
|
+
/**
|
|
43
|
+
* Requested output dimensionality. Supported by models with Matryoshka /
|
|
44
|
+
* configurable dimensions; adapters for fixed-dimension models throw a
|
|
45
|
+
* clear runtime error when this is set.
|
|
46
|
+
*/
|
|
47
|
+
dimensions?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Enable debug logging. Pass `true` to enable all categories, `false` to
|
|
50
|
+
* silence everything including errors, or a `DebugConfig` object for granular
|
|
51
|
+
* control and/or a custom `Logger`.
|
|
52
|
+
*/
|
|
53
|
+
debug?: DebugOption;
|
|
54
|
+
/**
|
|
55
|
+
* Observe-only middleware notified on start, usage, success, and error. Pass
|
|
56
|
+
* `otelMiddleware()` to emit OpenTelemetry spans, or implement the
|
|
57
|
+
* `GenerationMiddleware` contract for a custom backend.
|
|
58
|
+
*/
|
|
59
|
+
middleware?: Array<GenerationMiddleware>;
|
|
60
|
+
} & ({} extends EmbedProviderOptionsForModel<TAdapter, TAdapter['model']> ? {
|
|
61
|
+
/** Provider-specific options for embedding generation */ modelOptions?: EmbedProviderOptionsForModel<TAdapter, TAdapter['model']>;
|
|
62
|
+
} : {
|
|
63
|
+
/** Provider-specific options for embedding generation */ modelOptions: EmbedProviderOptionsForModel<TAdapter, TAdapter['model']>;
|
|
64
|
+
});
|
|
65
|
+
/**
|
|
66
|
+
* Embed activity - generates embedding vectors from text and image inputs.
|
|
67
|
+
*
|
|
68
|
+
* Accepts a single item or an array of items; the result always carries an
|
|
69
|
+
* `embeddings` array with one vector per input item, in input order.
|
|
70
|
+
*
|
|
71
|
+
* @example Embed a single text
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { embed } from '@tanstack/ai'
|
|
74
|
+
* import { openaiEmbedding } from '@tanstack/ai-openai'
|
|
75
|
+
*
|
|
76
|
+
* const result = await embed({
|
|
77
|
+
* adapter: openaiEmbedding('text-embedding-3-small'),
|
|
78
|
+
* input: 'a red guitar',
|
|
79
|
+
* })
|
|
80
|
+
*
|
|
81
|
+
* console.log(result.embeddings[0].vector)
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @example Batch with requested dimensions
|
|
85
|
+
* ```ts
|
|
86
|
+
* const result = await embed({
|
|
87
|
+
* adapter: openaiEmbedding('text-embedding-3-large'),
|
|
88
|
+
* input: ['a red guitar', 'a blue drum kit'],
|
|
89
|
+
* dimensions: 1024,
|
|
90
|
+
* })
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @example Multimodal embedding (text + image fused into one vector)
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { cohereEmbedding } from '@tanstack/ai-cohere'
|
|
96
|
+
*
|
|
97
|
+
* // A nested array of parts fuses them into a single vector. The outer array
|
|
98
|
+
* // is the item list, so this embeds one fused item into one vector.
|
|
99
|
+
* const result = await embed({
|
|
100
|
+
* adapter: cohereEmbedding('embed-v4.0'),
|
|
101
|
+
* input: [
|
|
102
|
+
* [
|
|
103
|
+
* { type: 'text', content: 'product photo' },
|
|
104
|
+
* { type: 'image', source: { type: 'data', value: base64, mimeType: 'image/png' } },
|
|
105
|
+
* ],
|
|
106
|
+
* ],
|
|
107
|
+
* modelOptions: { inputType: 'search_document' },
|
|
108
|
+
* })
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export declare function embed<TAdapter extends EmbeddingAdapter<string, any, any, any>>(options: EmbedOptions<TAdapter>): Promise<EmbeddingResult>;
|
|
112
|
+
/**
|
|
113
|
+
* Create typed options for the embed() function without executing.
|
|
114
|
+
*/
|
|
115
|
+
export declare function createEmbedOptions<TAdapter extends EmbeddingAdapter<string, any, any, any>>(options: EmbedOptions<TAdapter>): EmbedOptions<TAdapter>;
|
|
116
|
+
export type { EmbeddingAdapter, EmbeddingAdapterConfig, AnyEmbeddingAdapter, } from './adapter.js';
|
|
117
|
+
export { BaseEmbeddingAdapter } from './adapter.js';
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { resolveDebugOption } from "../../logger/resolve.js";
|
|
2
|
+
import { createGenerationContext, runGenerationError, runGenerationFinish, runGenerationStart, runGenerationUsage } from "../middleware/run.js";
|
|
3
|
+
import { countEmbeddingInputModalities } from "../../utilities/embedding-input.js";
|
|
4
|
+
import "./adapter.js";
|
|
5
|
+
import { aiEventClient } from "@tanstack/ai-event-client";
|
|
6
|
+
//#region src/activities/embed/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* Embed Activity
|
|
9
|
+
*
|
|
10
|
+
* Generates embedding vectors from text and (for multimodal models) image
|
|
11
|
+
* inputs. This is a self-contained module with implementation, types, and JSDoc.
|
|
12
|
+
*/
|
|
13
|
+
/** The adapter kind this activity handles */
|
|
14
|
+
var kind = "embedding";
|
|
15
|
+
function createId(prefix) {
|
|
16
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Embed activity - generates embedding vectors from text and image inputs.
|
|
20
|
+
*
|
|
21
|
+
* Accepts a single item or an array of items; the result always carries an
|
|
22
|
+
* `embeddings` array with one vector per input item, in input order.
|
|
23
|
+
*
|
|
24
|
+
* @example Embed a single text
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { embed } from '@tanstack/ai'
|
|
27
|
+
* import { openaiEmbedding } from '@tanstack/ai-openai'
|
|
28
|
+
*
|
|
29
|
+
* const result = await embed({
|
|
30
|
+
* adapter: openaiEmbedding('text-embedding-3-small'),
|
|
31
|
+
* input: 'a red guitar',
|
|
32
|
+
* })
|
|
33
|
+
*
|
|
34
|
+
* console.log(result.embeddings[0].vector)
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example Batch with requested dimensions
|
|
38
|
+
* ```ts
|
|
39
|
+
* const result = await embed({
|
|
40
|
+
* adapter: openaiEmbedding('text-embedding-3-large'),
|
|
41
|
+
* input: ['a red guitar', 'a blue drum kit'],
|
|
42
|
+
* dimensions: 1024,
|
|
43
|
+
* })
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example Multimodal embedding (text + image fused into one vector)
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { cohereEmbedding } from '@tanstack/ai-cohere'
|
|
49
|
+
*
|
|
50
|
+
* // A nested array of parts fuses them into a single vector. The outer array
|
|
51
|
+
* // is the item list, so this embeds one fused item into one vector.
|
|
52
|
+
* const result = await embed({
|
|
53
|
+
* adapter: cohereEmbedding('embed-v4.0'),
|
|
54
|
+
* input: [
|
|
55
|
+
* [
|
|
56
|
+
* { type: 'text', content: 'product photo' },
|
|
57
|
+
* { type: 'image', source: { type: 'data', value: base64, mimeType: 'image/png' } },
|
|
58
|
+
* ],
|
|
59
|
+
* ],
|
|
60
|
+
* modelOptions: { inputType: 'search_document' },
|
|
61
|
+
* })
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
async function embed(options) {
|
|
65
|
+
const { adapter, middleware } = options;
|
|
66
|
+
const model = adapter.model;
|
|
67
|
+
const requestId = createId("embedding");
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
const logger = resolveDebugOption(options.debug);
|
|
70
|
+
const modelOptions = options.modelOptions;
|
|
71
|
+
const inputItems = Array.isArray(options.input) ? options.input : [options.input];
|
|
72
|
+
const { textInputCount, imageInputCount } = countEmbeddingInputModalities(inputItems);
|
|
73
|
+
const mwCtx = createGenerationContext({
|
|
74
|
+
requestId,
|
|
75
|
+
activity: "embedding",
|
|
76
|
+
provider: adapter.name,
|
|
77
|
+
model,
|
|
78
|
+
modelOptions,
|
|
79
|
+
createId
|
|
80
|
+
});
|
|
81
|
+
await runGenerationStart(middleware, mwCtx);
|
|
82
|
+
aiEventClient.emit("embedding:request:started", {
|
|
83
|
+
requestId,
|
|
84
|
+
provider: adapter.name,
|
|
85
|
+
model,
|
|
86
|
+
inputCount: inputItems.length,
|
|
87
|
+
textInputCount,
|
|
88
|
+
imageInputCount,
|
|
89
|
+
dimensions: options.dimensions,
|
|
90
|
+
modelOptions,
|
|
91
|
+
timestamp: startTime
|
|
92
|
+
});
|
|
93
|
+
logger.request(`activity=embed provider=${adapter.name} model=${model}`, {
|
|
94
|
+
provider: adapter.name,
|
|
95
|
+
model
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
const result = await adapter.createEmbeddings({
|
|
99
|
+
model,
|
|
100
|
+
input: inputItems,
|
|
101
|
+
dimensions: options.dimensions,
|
|
102
|
+
modelOptions,
|
|
103
|
+
logger
|
|
104
|
+
});
|
|
105
|
+
const duration = Date.now() - startTime;
|
|
106
|
+
aiEventClient.emit("embedding:request:completed", {
|
|
107
|
+
requestId,
|
|
108
|
+
provider: adapter.name,
|
|
109
|
+
model,
|
|
110
|
+
embeddingCount: result.embeddings.length,
|
|
111
|
+
dimensions: result.embeddings[0]?.vector.length,
|
|
112
|
+
duration,
|
|
113
|
+
modelOptions,
|
|
114
|
+
timestamp: Date.now()
|
|
115
|
+
});
|
|
116
|
+
logger.output(`activity=embed count=${result.embeddings.length}`, { embeddingCount: result.embeddings.length });
|
|
117
|
+
if (result.usage) {
|
|
118
|
+
aiEventClient.emit("embedding:usage", {
|
|
119
|
+
requestId,
|
|
120
|
+
model,
|
|
121
|
+
usage: result.usage,
|
|
122
|
+
timestamp: Date.now()
|
|
123
|
+
});
|
|
124
|
+
await runGenerationUsage(middleware, mwCtx, result.usage);
|
|
125
|
+
}
|
|
126
|
+
await runGenerationFinish(middleware, mwCtx, {
|
|
127
|
+
duration,
|
|
128
|
+
usage: result.usage
|
|
129
|
+
});
|
|
130
|
+
return result;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const duration = Date.now() - startTime;
|
|
133
|
+
const err = error;
|
|
134
|
+
aiEventClient.emit("embedding:request:error", {
|
|
135
|
+
requestId,
|
|
136
|
+
provider: adapter.name,
|
|
137
|
+
model,
|
|
138
|
+
error: {
|
|
139
|
+
message: err.message,
|
|
140
|
+
name: err.name
|
|
141
|
+
},
|
|
142
|
+
duration,
|
|
143
|
+
modelOptions,
|
|
144
|
+
timestamp: Date.now()
|
|
145
|
+
});
|
|
146
|
+
await runGenerationError(middleware, mwCtx, {
|
|
147
|
+
error,
|
|
148
|
+
duration
|
|
149
|
+
});
|
|
150
|
+
logger.errors("embed activity failed", {
|
|
151
|
+
error,
|
|
152
|
+
source: "embed"
|
|
153
|
+
});
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Create typed options for the embed() function without executing.
|
|
159
|
+
*/
|
|
160
|
+
function createEmbedOptions(options) {
|
|
161
|
+
return options;
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { createEmbedOptions, embed, kind };
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=index.js.map
|