@tanstack/ai 0.6.1 → 0.6.3
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/messages.js +2 -2
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +7 -4
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.js +9 -0
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateImage/index.d.ts +19 -6
- package/dist/esm/activities/generateImage/index.js +11 -2
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +19 -6
- package/dist/esm/activities/generateSpeech/index.js +11 -2
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +30 -6
- package/dist/esm/activities/generateTranscription/index.js +13 -2
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +45 -7
- package/dist/esm/activities/generateVideo/index.js +90 -1
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.d.ts +14 -0
- package/dist/esm/activities/stream-generation-result.js +40 -0
- package/dist/esm/activities/stream-generation-result.js.map +1 -0
- package/dist/esm/activities/summarize/index.js +2 -17
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/package.json +1 -1
- package/src/activities/chat/messages.ts +5 -7
- package/src/activities/chat/stream/message-updaters.ts +6 -4
- package/src/activities/chat/tools/tool-calls.ts +14 -0
- package/src/activities/generateImage/index.ts +49 -7
- package/src/activities/generateSpeech/index.ts +41 -7
- package/src/activities/generateTranscription/index.ts +59 -9
- package/src/activities/generateVideo/index.ts +173 -6
- package/src/activities/stream-generation-result.ts +62 -0
- package/src/activities/summarize/index.ts +3 -22
|
@@ -107,7 +107,7 @@ function buildAssistantMessages(uiMessage) {
|
|
|
107
107
|
flushSegment();
|
|
108
108
|
for (const part of uiMessage.parts) {
|
|
109
109
|
if (part.type !== "tool-call") continue;
|
|
110
|
-
if (part.output !== void 0 && !
|
|
110
|
+
if (part.output !== void 0 && !emittedToolResultIds.has(part.id)) {
|
|
111
111
|
messageList.push({
|
|
112
112
|
role: "tool",
|
|
113
113
|
content: JSON.stringify(part.output),
|
|
@@ -115,7 +115,7 @@ function buildAssistantMessages(uiMessage) {
|
|
|
115
115
|
});
|
|
116
116
|
emittedToolResultIds.add(part.id);
|
|
117
117
|
}
|
|
118
|
-
if (part.state === "approval-responded" && part.approval?.approved !== void 0 && !emittedToolResultIds.has(part.id)) {
|
|
118
|
+
if (part.output === void 0 && part.state === "approval-responded" && part.approval?.approved !== void 0 && !emittedToolResultIds.has(part.id)) {
|
|
119
119
|
const approved = part.approval.approved;
|
|
120
120
|
messageList.push({
|
|
121
121
|
role: "tool",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sources":["../../../../src/activities/chat/messages.ts"],"sourcesContent":["import type {\n ContentPart,\n MessagePart,\n ModelMessage,\n TextPart,\n ToolCallPart,\n UIMessage,\n} from '../../types'\n// ===========================\n// Message Converters\n// ===========================\n\n/**\n * Check if a MessagePart is a content part (text, image, audio, video, document)\n * that maps directly to a ModelMessage ContentPart.\n */\nfunction isContentPart(part: MessagePart): part is ContentPart {\n return (\n part.type === 'text' ||\n part.type === 'image' ||\n part.type === 'audio' ||\n part.type === 'video' ||\n part.type === 'document'\n )\n}\n\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 const modelMessages: Array<ModelMessage> = []\n for (const msg of messages) {\n if ('parts' in msg) {\n // UIMessage - convert to ModelMessages\n modelMessages.push(...uiMessageToModelMessages(msg))\n } else {\n // Already ModelMessage\n modelMessages.push(msg)\n }\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 }>\n}\n\nfunction createSegment(): AssistantSegment {\n return { contentParts: [], toolCalls: [] }\n}\n\nfunction isToolCallIncluded(part: ToolCallPart): boolean {\n return (\n part.state === 'input-complete' ||\n part.state === 'approval-responded' ||\n part.output !== undefined\n )\n}\n\n/**\n * Build ModelMessages for an assistant UIMessage, preserving the\n * sequential interleaving of text, tool calls, and tool results.\n *\n * Walks parts in order. Text and tool-call parts accumulate into the\n * current \"segment\". When a tool-result part is encountered, the\n * current segment is flushed as an assistant message, then the tool\n * result is emitted as a tool message.\n */\nfunction buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {\n const messageList: Array<ModelMessage> = []\n let current = createSegment()\n\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 })\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 })\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 // thinking parts are skipped - they're UI-only\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 // Client tool with output - add as tool result (if not already emitted)\n if (\n part.output !== undefined &&\n !part.approval &&\n !emittedToolResultIds.has(part.id)\n ) {\n messageList.push({\n role: 'tool',\n content: JSON.stringify(part.output),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response - add as tool result for iteration tracking\n if (\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 // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n parts.push({\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n state: 'input-complete', // Model messages have complete arguments\n })\n }\n }\n\n return {\n id: id || generateMessageId(),\n role: modelMessage.role === 'tool' ? 'assistant' : modelMessage.role,\n parts,\n }\n}\n\n/**\n * Convert an array of ModelMessages to UIMessages\n *\n * This handles merging tool result messages with their corresponding assistant messages\n *\n * @param modelMessages - Array of ModelMessages to convert\n * @returns Array of UIMessages\n */\nexport function modelMessagesToUIMessages(\n modelMessages: Array<ModelMessage>,\n): Array<UIMessage> {\n const uiMessages: Array<UIMessage> = []\n let currentAssistantMessage: UIMessage | null = null\n\n for (const msg of modelMessages) {\n if (msg.role === 'tool') {\n // Tool result - merge into the last assistant message if possible\n if (\n currentAssistantMessage &&\n currentAssistantMessage.role === 'assistant'\n ) {\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId!,\n content: getTextContent(msg.content),\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message\n const uiMessage = modelMessageToUIMessage(msg)\n uiMessages.push(uiMessage)\n\n // Track assistant messages for potential tool result merging\n if (msg.role === 'assistant') {\n currentAssistantMessage = uiMessage\n } else {\n currentAssistantMessage = null\n }\n }\n }\n\n return uiMessages\n}\n\n/**\n * Normalize a message (UIMessage or ModelMessage) to a UIMessage\n * Ensures the message has an ID and createdAt timestamp\n *\n * @param message - Either a UIMessage or ModelMessage\n * @param generateId - Function to generate a message ID if needed\n * @returns A UIMessage with guaranteed id and createdAt\n */\nexport function normalizeToUIMessage(\n message: UIMessage | ModelMessage,\n generateId: () => string,\n): UIMessage {\n if ('parts' in message) {\n // Already a UIMessage\n return {\n ...message,\n id: message.id || generateId(),\n createdAt: message.createdAt || new Date(),\n }\n } else {\n // ModelMessage - convert to UIMessage\n return {\n ...modelMessageToUIMessage(message, generateId()),\n createdAt: new Date(),\n }\n }\n}\n\n/**\n * Generate a unique message ID\n */\nexport function generateMessageId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"messages.js","sources":["../../../../src/activities/chat/messages.ts"],"sourcesContent":["import type {\n ContentPart,\n MessagePart,\n ModelMessage,\n TextPart,\n ToolCallPart,\n UIMessage,\n} from '../../types'\n// ===========================\n// Message Converters\n// ===========================\n\n/**\n * Check if a MessagePart is a content part (text, image, audio, video, document)\n * that maps directly to a ModelMessage ContentPart.\n */\nfunction isContentPart(part: MessagePart): part is ContentPart {\n return (\n part.type === 'text' ||\n part.type === 'image' ||\n part.type === 'audio' ||\n part.type === 'video' ||\n part.type === 'document'\n )\n}\n\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 const modelMessages: Array<ModelMessage> = []\n for (const msg of messages) {\n if ('parts' in msg) {\n // UIMessage - convert to ModelMessages\n modelMessages.push(...uiMessageToModelMessages(msg))\n } else {\n // Already ModelMessage\n modelMessages.push(msg)\n }\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 }>\n}\n\nfunction createSegment(): AssistantSegment {\n return { contentParts: [], toolCalls: [] }\n}\n\nfunction isToolCallIncluded(part: ToolCallPart): boolean {\n return (\n part.state === 'input-complete' ||\n part.state === 'approval-responded' ||\n part.output !== undefined\n )\n}\n\n/**\n * Build ModelMessages for an assistant UIMessage, preserving the\n * sequential interleaving of text, tool calls, and tool results.\n *\n * Walks parts in order. Text and tool-call parts accumulate into the\n * current \"segment\". When a tool-result part is encountered, the\n * current segment is flushed as an assistant message, then the tool\n * result is emitted as a tool message.\n */\nfunction buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {\n const messageList: Array<ModelMessage> = []\n let current = createSegment()\n\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 })\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 })\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 // thinking parts are skipped - they're UI-only\n default:\n break\n }\n }\n\n // Flush any remaining accumulated content\n flushSegment()\n\n // Emit tool results from client tool-call parts with output or approval,\n // but only if not already covered by an explicit tool-result part above.\n // These are appended at the end since they don't have explicit tool-result\n // parts in the parts array to trigger inline emission.\n for (const part of uiMessage.parts) {\n if (part.type !== 'tool-call') continue\n\n // Output takes priority — if the tool has already produced a result,\n // emit the concrete output regardless of approval metadata.\n if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {\n messageList.push({\n role: 'tool',\n content: JSON.stringify(part.output),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response without output — emit approval status for iteration tracking\n if (\n part.output === undefined &&\n part.state === 'approval-responded' &&\n part.approval?.approved !== undefined &&\n !emittedToolResultIds.has(part.id)\n ) {\n const approved = part.approval.approved\n messageList.push({\n role: 'tool',\n content: JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n }),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n }\n\n // If no messages were produced (e.g., empty parts), emit a minimal assistant message\n if (messageList.length === 0) {\n messageList.push({\n role: 'assistant',\n content: null,\n })\n }\n\n return messageList\n}\n\n/**\n * Convert a ModelMessage to UIMessage\n *\n * This conversion creates a parts-based structure:\n * - content field → TextPart\n * - toolCalls array → ToolCallPart[]\n * - role=\"tool\" messages should be converted separately and merged\n *\n * @param modelMessage - The ModelMessage to convert\n * @param id - Optional ID for the UIMessage (generated if not provided)\n * @returns A UIMessage with parts\n */\nexport function modelMessageToUIMessage(\n modelMessage: ModelMessage,\n id?: string,\n): UIMessage {\n const parts: Array<MessagePart> = []\n\n // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n parts.push({\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n state: 'input-complete', // Model messages have complete arguments\n })\n }\n }\n\n return {\n id: id || generateMessageId(),\n role: modelMessage.role === 'tool' ? 'assistant' : modelMessage.role,\n parts,\n }\n}\n\n/**\n * Convert an array of ModelMessages to UIMessages\n *\n * This handles merging tool result messages with their corresponding assistant messages\n *\n * @param modelMessages - Array of ModelMessages to convert\n * @returns Array of UIMessages\n */\nexport function modelMessagesToUIMessages(\n modelMessages: Array<ModelMessage>,\n): Array<UIMessage> {\n const uiMessages: Array<UIMessage> = []\n let currentAssistantMessage: UIMessage | null = null\n\n for (const msg of modelMessages) {\n if (msg.role === 'tool') {\n // Tool result - merge into the last assistant message if possible\n if (\n currentAssistantMessage &&\n currentAssistantMessage.role === 'assistant'\n ) {\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId!,\n content: getTextContent(msg.content),\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message\n const uiMessage = modelMessageToUIMessage(msg)\n uiMessages.push(uiMessage)\n\n // Track assistant messages for potential tool result merging\n if (msg.role === 'assistant') {\n currentAssistantMessage = uiMessage\n } else {\n currentAssistantMessage = null\n }\n }\n }\n\n return uiMessages\n}\n\n/**\n * Normalize a message (UIMessage or ModelMessage) to a UIMessage\n * Ensures the message has an ID and createdAt timestamp\n *\n * @param message - Either a UIMessage or ModelMessage\n * @param generateId - Function to generate a message ID if needed\n * @returns A UIMessage with guaranteed id and createdAt\n */\nexport function normalizeToUIMessage(\n message: UIMessage | ModelMessage,\n generateId: () => string,\n): UIMessage {\n if ('parts' in message) {\n // Already a UIMessage\n return {\n ...message,\n id: message.id || generateId(),\n createdAt: message.createdAt || new Date(),\n }\n } else {\n // ModelMessage - convert to UIMessage\n return {\n ...modelMessageToUIMessage(message, generateId()),\n createdAt: new Date(),\n }\n }\n}\n\n/**\n * Generate a unique message ID\n */\nexport function generateMessageId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"names":[],"mappings":"AAgBA,SAAS,cAAc,MAAwC;AAC7D,SACE,KAAK,SAAS,UACd,KAAK,SAAS,WACd,KAAK,SAAS,WACd,KAAK,SAAS,WACd,KAAK,SAAS;AAElB;AAQA,SAAS,qBACP,OACoC;AACpC,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,UAAU,MAAM,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM;AACpD,MAAI,SAAS;AACX,UAAM,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE;AAClD,WAAO,UAAU;AAAA,EACnB;AAEA,SAAO;AACT;AAMA,SAAS,eAAe,SAAqD;AAC3E,MAAI,YAAY,KAAM,QAAO;AAC7B,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,SAAO,QACJ,OAAO,CAAC,SAA2B,KAAK,SAAS,MAAM,EACvD,IAAI,CAAC,SAAS,KAAK,OAAO,EAC1B,KAAK,EAAE;AACZ;AAKO,SAAS,+BACd,UACqB;AACrB,QAAM,gBAAqC,CAAA;AAC3C,aAAW,OAAO,UAAU;AAC1B,QAAI,WAAW,KAAK;AAElB,oBAAc,KAAK,GAAG,yBAAyB,GAAG,CAAC;AAAA,IACrD,OAAO;AAEL,oBAAc,KAAK,GAAG;AAAA,IACxB;AAAA,EACF;AACA,SAAO;AACT;AAqBO,SAAS,yBACd,WACqB;AAErB,MAAI,UAAU,SAAS,UAAU;AAC/B,WAAO,CAAA;AAAA,EACT;AAIA,MAAI,UAAU,SAAS,aAAa;AAClC,WAAO,CAAC,uBAAuB,SAAS,CAAC;AAAA,EAC3C;AAGA,SAAO,uBAAuB,SAAS;AACzC;AAMA,SAAS,uBAAuB,WAAoC;AAClE,QAAM,eAAmC,CAAA;AACzC,aAAW,QAAQ,UAAU,OAAO;AAClC,QAAI,cAAc,IAAI,GAAG;AACvB,mBAAa,KAAK,IAAI;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,UAAU;AAAA,IAChB,SAAS,qBAAqB,YAAY;AAAA,EAAA;AAE9C;AAYA,SAAS,gBAAkC;AACzC,SAAO,EAAE,cAAc,IAAI,WAAW,CAAA,EAAC;AACzC;AAEA,SAAS,mBAAmB,MAA6B;AACvD,SACE,KAAK,UAAU,oBACf,KAAK,UAAU,wBACf,KAAK,WAAW;AAEpB;AAWA,SAAS,uBAAuB,WAA2C;AACzE,QAAM,cAAmC,CAAA;AACzC,MAAI,UAAU,cAAA;AAKd,QAAM,2CAA2B,IAAA;AAEjC,WAAS,eAAqB;AAC5B,UAAM,UAAU,qBAAqB,QAAQ,YAAY;AACzD,UAAM,aAAa,YAAY;AAC/B,UAAM,eAAe,QAAQ,UAAU,SAAS;AAEhD,QAAI,cAAc,cAAc;AAC9B,kBAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN;AAAA,QACA,GAAI,gBAAgB,EAAE,WAAW,QAAQ,UAAA;AAAA,MAAU,CACpD;AAAA,IACH;AACA,cAAU,cAAA;AAAA,EACZ;AAEA,aAAW,QAAQ,UAAU,OAAO;AAClC,YAAQ,KAAK,MAAA;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,gBAAQ,aAAa,KAAK,IAAI;AAC9B;AAAA,MAEF,KAAK;AACH,YAAI,mBAAmB,IAAI,GAAG;AAC5B,kBAAQ,UAAU,KAAK;AAAA,YACrB,IAAI,KAAK;AAAA,YACT,MAAM;AAAA,YACN,UAAU;AAAA,cACR,MAAM,KAAK;AAAA,cACX,WAAW,KAAK;AAAA,YAAA;AAAA,UAClB,CACD;AAAA,QACH;AACA;AAAA,MAEF,KAAK;AAEH,qBAAA;AAGA,aACG,KAAK,UAAU,cAAc,KAAK,UAAU,YAC7C,CAAC,qBAAqB,IAAI,KAAK,UAAU,GACzC;AACA,sBAAY,KAAK;AAAA,YACf,MAAM;AAAA,YACN,SAAS,KAAK;AAAA,YACd,YAAY,KAAK;AAAA,UAAA,CAClB;AACD,+BAAqB,IAAI,KAAK,UAAU;AAAA,QAC1C;AACA;AAAA,IAIA;AAAA,EAEN;AAGA,eAAA;AAMA,aAAW,QAAQ,UAAU,OAAO;AAClC,QAAI,KAAK,SAAS,YAAa;AAI/B,QAAI,KAAK,WAAW,UAAa,CAAC,qBAAqB,IAAI,KAAK,EAAE,GAAG;AACnE,kBAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS,KAAK,UAAU,KAAK,MAAM;AAAA,QACnC,YAAY,KAAK;AAAA,MAAA,CAClB;AACD,2BAAqB,IAAI,KAAK,EAAE;AAAA,IAClC;AAGA,QACE,KAAK,WAAW,UAChB,KAAK,UAAU,wBACf,KAAK,UAAU,aAAa,UAC5B,CAAC,qBAAqB,IAAI,KAAK,EAAE,GACjC;AACA,YAAM,WAAW,KAAK,SAAS;AAC/B,kBAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS,KAAK,UAAU;AAAA,UACtB;AAAA,UACA,GAAI,YAAY,EAAE,kBAAkB,KAAA;AAAA,UACpC,SAAS,WACL,8BACA;AAAA,QAAA,CACL;AAAA,QACD,YAAY,KAAK;AAAA,MAAA,CAClB;AACD,2BAAqB,IAAI,KAAK,EAAE;AAAA,IAClC;AAAA,EACF;AAGA,MAAI,YAAY,WAAW,GAAG;AAC5B,gBAAY,KAAK;AAAA,MACf,MAAM;AAAA,MACN,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAEA,SAAO;AACT;AAcO,SAAS,wBACd,cACA,IACW;AACX,QAAM,QAA4B,CAAA;AAIlC,MAAI,aAAa,SAAS,UAAU,aAAa,YAAY;AAC3D,UAAM,KAAK;AAAA,MACT,MAAM;AAAA,MACN,YAAY,aAAa;AAAA,MACzB,SAAS,eAAe,aAAa,OAAO;AAAA,MAC5C,OAAO;AAAA,IAAA,CACR;AAAA,EACH,WAAW,MAAM,QAAQ,aAAa,OAAO,GAAG;AAE9C,eAAW,QAAQ,aAAa,SAAS;AACvC,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF,OAAO;AAEL,UAAM,cAAc,eAAe,aAAa,OAAO;AACvD,QAAI,aAAa;AACf,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,SAAS;AAAA,MAAA,CACV;AAAA,IACH;AAAA,EACF;AAGA,MAAI,aAAa,aAAa,aAAa,UAAU,SAAS,GAAG;AAC/D,eAAW,YAAY,aAAa,WAAW;AAC7C,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,IAAI,SAAS;AAAA,QACb,MAAM,SAAS,SAAS;AAAA,QACxB,WAAW,SAAS,SAAS;AAAA,QAC7B,OAAO;AAAA;AAAA,MAAA,CACR;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,MAAM,kBAAA;AAAA,IACV,MAAM,aAAa,SAAS,SAAS,cAAc,aAAa;AAAA,IAChE;AAAA,EAAA;AAEJ;AAUO,SAAS,0BACd,eACkB;AAClB,QAAM,aAA+B,CAAA;AACrC,MAAI,0BAA4C;AAEhD,aAAW,OAAO,eAAe;AAC/B,QAAI,IAAI,SAAS,QAAQ;AAEvB,UACE,2BACA,wBAAwB,SAAS,aACjC;AACA,gCAAwB,MAAM,KAAK;AAAA,UACjC,MAAM;AAAA,UACN,YAAY,IAAI;AAAA,UAChB,SAAS,eAAe,IAAI,OAAO;AAAA,UACnC,OAAO;AAAA,QAAA,CACR;AAAA,MACH,OAAO;AAEL,cAAM,sBAAsB,wBAAwB,GAAG;AACvD,mBAAW,KAAK,mBAAmB;AAAA,MACrC;AAAA,IACF,OAAO;AAEL,YAAM,YAAY,wBAAwB,GAAG;AAC7C,iBAAW,KAAK,SAAS;AAGzB,UAAI,IAAI,SAAS,aAAa;AAC5B,kCAA0B;AAAA,MAC5B,OAAO;AACL,kCAA0B;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAUO,SAAS,qBACd,SACA,YACW;AACX,MAAI,WAAW,SAAS;AAEtB,WAAO;AAAA,MACL,GAAG;AAAA,MACH,IAAI,QAAQ,MAAM,WAAA;AAAA,MAClB,WAAW,QAAQ,aAAa,oBAAI,KAAA;AAAA,IAAK;AAAA,EAE7C,OAAO;AAEL,WAAO;AAAA,MACL,GAAG,wBAAwB,SAAS,YAAY;AAAA,MAChD,+BAAe,KAAA;AAAA,IAAK;AAAA,EAExB;AACF;AAKO,SAAS,oBAA4B;AAC1C,SAAO,OAAO,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AACrE;"}
|
|
@@ -19,7 +19,7 @@ function updateToolCallPart(messages, messageId, toolCall) {
|
|
|
19
19
|
return msg;
|
|
20
20
|
}
|
|
21
21
|
const parts = [...msg.parts];
|
|
22
|
-
const
|
|
22
|
+
const existing = parts.find(
|
|
23
23
|
(p) => p.type === "tool-call" && p.id === toolCall.id
|
|
24
24
|
);
|
|
25
25
|
const toolCallPart = {
|
|
@@ -27,10 +27,13 @@ function updateToolCallPart(messages, messageId, toolCall) {
|
|
|
27
27
|
id: toolCall.id,
|
|
28
28
|
name: toolCall.name,
|
|
29
29
|
arguments: toolCall.arguments,
|
|
30
|
-
state: toolCall.state
|
|
30
|
+
state: toolCall.state,
|
|
31
|
+
// Carry forward approval and output from the existing part
|
|
32
|
+
...existing?.approval && { approval: { ...existing.approval } },
|
|
33
|
+
...existing?.output !== void 0 && { output: existing.output }
|
|
31
34
|
};
|
|
32
|
-
if (
|
|
33
|
-
parts[
|
|
35
|
+
if (existing) {
|
|
36
|
+
parts[parts.indexOf(existing)] = toolCallPart;
|
|
34
37
|
} else {
|
|
35
38
|
parts.push(toolCallPart);
|
|
36
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-updaters.js","sources":["../../../../../src/activities/chat/stream/message-updaters.ts"],"sourcesContent":["/**\n * Message Updaters (Internal)\n *\n * Internal helper functions for updating UIMessage parts.\n * These are used by StreamProcessor to manage the message array.\n */\n\nimport type {\n ThinkingPart,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n} from '../../../types'\nimport type { ToolCallState, ToolResultState } from './types'\n\n/**\n * Update or add a text part to a message.\n *\n * If the last part is a text part, update it (continuing the same text segment).\n * Otherwise, create a new text part (starting a new text segment after tool calls).\n */\nexport function updateTextPart(\n messages: Array<UIMessage>,\n messageId: string,\n content: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const lastPart = parts.length > 0 ? parts[parts.length - 1] : null\n\n if (lastPart && lastPart.type === 'text') {\n // Update the last text part (continuing same text segment)\n parts[parts.length - 1] = { type: 'text', content }\n } else {\n // Create new text part (starting new text segment after tool calls/results)\n parts.push({ type: 'text', content })\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a tool call part to a message.\n */\nexport function updateToolCallPart(\n messages: Array<UIMessage>,\n messageId: string,\n toolCall: {\n id: string\n name: string\n arguments: string\n state: ToolCallState\n },\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n // Find by ID, not index!\n const existingPartIndex = parts.findIndex(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCall.id,\n )\n\n const toolCallPart: ToolCallPart = {\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: toolCall.state,\n }\n\n if (existingPartIndex >= 0) {\n // Update existing tool call\n parts[existingPartIndex] = toolCallPart\n } else {\n // Add new tool call at the end (preserve natural streaming order)\n parts.push(toolCallPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a tool result part to a message.\n */\nexport function updateToolResultPart(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n content: string,\n state: ToolResultState,\n error?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const resultPartIndex = parts.findIndex(\n (p): p is ToolResultPart =>\n p.type === 'tool-result' && p.toolCallId === toolCallId,\n )\n\n const toolResultPart: ToolResultPart = {\n type: 'tool-result',\n toolCallId,\n content,\n state,\n ...(error && { error }),\n }\n\n if (resultPartIndex >= 0) {\n parts[resultPartIndex] = toolResultPart\n } else {\n parts.push(toolResultPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with approval request metadata.\n */\nexport function updateToolCallApproval(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n approvalId: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.state = 'approval-requested'\n toolCallPart.approval = {\n id: approvalId,\n needsApproval: true,\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part's state (e.g., to \"input-complete\").\n */\nexport function updateToolCallState(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n state: ToolCallState,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.state = state\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with output.\n * Searches all messages to find the tool call by ID.\n */\nexport function updateToolCallWithOutput(\n messages: Array<UIMessage>,\n toolCallId: string,\n output: any,\n state?: ToolCallState,\n errorText?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.output = errorText ? { error: errorText } : output\n if (state) {\n toolCallPart.state = state\n } else {\n toolCallPart.state = 'input-complete'\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with approval response.\n * Searches all messages to find the tool call by approval ID.\n */\nexport function updateToolCallApprovalResponse(\n messages: Array<UIMessage>,\n approvalId: string,\n approved: boolean,\n): Array<UIMessage> {\n return messages.map((msg) => {\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart =>\n p.type === 'tool-call' && p.approval?.id === approvalId,\n )\n\n if (toolCallPart && toolCallPart.approval) {\n toolCallPart.approval.approved = approved\n toolCallPart.state = 'approval-responded'\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a thinking part to a message.\n */\nexport function updateThinkingPart(\n messages: Array<UIMessage>,\n messageId: string,\n content: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const thinkingPartIndex = parts.findIndex((p) => p.type === 'thinking')\n\n const thinkingPart: ThinkingPart = {\n type: 'thinking',\n content,\n }\n\n if (thinkingPartIndex >= 0) {\n // Update existing thinking part\n parts[thinkingPartIndex] = thinkingPart\n } else {\n // Add new thinking part at the end (preserve natural streaming order)\n parts.push(thinkingPart)\n }\n\n return { ...msg, parts }\n })\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"message-updaters.js","sources":["../../../../../src/activities/chat/stream/message-updaters.ts"],"sourcesContent":["/**\n * Message Updaters (Internal)\n *\n * Internal helper functions for updating UIMessage parts.\n * These are used by StreamProcessor to manage the message array.\n */\n\nimport type {\n ThinkingPart,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n} from '../../../types'\nimport type { ToolCallState, ToolResultState } from './types'\n\n/**\n * Update or add a text part to a message.\n *\n * If the last part is a text part, update it (continuing the same text segment).\n * Otherwise, create a new text part (starting a new text segment after tool calls).\n */\nexport function updateTextPart(\n messages: Array<UIMessage>,\n messageId: string,\n content: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const lastPart = parts.length > 0 ? parts[parts.length - 1] : null\n\n if (lastPart && lastPart.type === 'text') {\n // Update the last text part (continuing same text segment)\n parts[parts.length - 1] = { type: 'text', content }\n } else {\n // Create new text part (starting new text segment after tool calls/results)\n parts.push({ type: 'text', content })\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a tool call part to a message.\n */\nexport function updateToolCallPart(\n messages: Array<UIMessage>,\n messageId: string,\n toolCall: {\n id: string\n name: string\n arguments: string\n state: ToolCallState\n },\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const existing = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCall.id,\n )\n\n const toolCallPart: ToolCallPart = {\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n state: toolCall.state,\n // Carry forward approval and output from the existing part\n ...(existing?.approval && { approval: { ...existing.approval } }),\n ...(existing?.output !== undefined && { output: existing.output }),\n }\n\n if (existing) {\n // Update existing tool call\n parts[parts.indexOf(existing)] = toolCallPart\n } else {\n // Add new tool call at the end (preserve natural streaming order)\n parts.push(toolCallPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a tool result part to a message.\n */\nexport function updateToolResultPart(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n content: string,\n state: ToolResultState,\n error?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const resultPartIndex = parts.findIndex(\n (p): p is ToolResultPart =>\n p.type === 'tool-result' && p.toolCallId === toolCallId,\n )\n\n const toolResultPart: ToolResultPart = {\n type: 'tool-result',\n toolCallId,\n content,\n state,\n ...(error && { error }),\n }\n\n if (resultPartIndex >= 0) {\n parts[resultPartIndex] = toolResultPart\n } else {\n parts.push(toolResultPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with approval request metadata.\n */\nexport function updateToolCallApproval(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n approvalId: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.state = 'approval-requested'\n toolCallPart.approval = {\n id: approvalId,\n needsApproval: true,\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part's state (e.g., to \"input-complete\").\n */\nexport function updateToolCallState(\n messages: Array<UIMessage>,\n messageId: string,\n toolCallId: string,\n state: ToolCallState,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.state = state\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with output.\n * Searches all messages to find the tool call by ID.\n */\nexport function updateToolCallWithOutput(\n messages: Array<UIMessage>,\n toolCallId: string,\n output: any,\n state?: ToolCallState,\n errorText?: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.output = errorText ? { error: errorText } : output\n if (state) {\n toolCallPart.state = state\n } else {\n toolCallPart.state = 'input-complete'\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update a tool call part with approval response.\n * Searches all messages to find the tool call by approval ID.\n */\nexport function updateToolCallApprovalResponse(\n messages: Array<UIMessage>,\n approvalId: string,\n approved: boolean,\n): Array<UIMessage> {\n return messages.map((msg) => {\n const parts = [...msg.parts]\n const toolCallPart = parts.find(\n (p): p is ToolCallPart =>\n p.type === 'tool-call' && p.approval?.id === approvalId,\n )\n\n if (toolCallPart && toolCallPart.approval) {\n toolCallPart.approval.approved = approved\n toolCallPart.state = 'approval-responded'\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Update or add a thinking part to a message.\n */\nexport function updateThinkingPart(\n messages: Array<UIMessage>,\n messageId: string,\n content: string,\n): Array<UIMessage> {\n return messages.map((msg) => {\n if (msg.id !== messageId) {\n return msg\n }\n\n const parts = [...msg.parts]\n const thinkingPartIndex = parts.findIndex((p) => p.type === 'thinking')\n\n const thinkingPart: ThinkingPart = {\n type: 'thinking',\n content,\n }\n\n if (thinkingPartIndex >= 0) {\n // Update existing thinking part\n parts[thinkingPartIndex] = thinkingPart\n } else {\n // Add new thinking part at the end (preserve natural streaming order)\n parts.push(thinkingPart)\n }\n\n return { ...msg, parts }\n })\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -160,6 +160,10 @@ async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ n
|
|
|
160
160
|
for (const tool of tools) {
|
|
161
161
|
toolMap.set(tool.name, tool);
|
|
162
162
|
}
|
|
163
|
+
const hasPendingApprovals = toolCalls.some((tc) => {
|
|
164
|
+
const t = toolMap.get(tc.function.name);
|
|
165
|
+
return t?.needsApproval && !approvals.has(`approval_${tc.id}`);
|
|
166
|
+
});
|
|
163
167
|
for (const toolCall of toolCalls) {
|
|
164
168
|
const tool = toolMap.get(toolCall.function.name);
|
|
165
169
|
const toolName = toolCall.function.name;
|
|
@@ -172,6 +176,11 @@ async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ n
|
|
|
172
176
|
});
|
|
173
177
|
continue;
|
|
174
178
|
}
|
|
179
|
+
if (hasPendingApprovals) {
|
|
180
|
+
if (!tool.needsApproval || approvals.has(`approval_${toolCall.id}`)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
175
184
|
let input = {};
|
|
176
185
|
const argsStr = toolCall.function.arguments.trim() || "{}";
|
|
177
186
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-calls.js","sources":["../../../../../src/activities/chat/tools/tool-calls.ts"],"sourcesContent":["import { isStandardSchema, parseWithStandardSchema } from './schema-converter'\nimport type {\n CustomEvent,\n ModelMessage,\n RunFinishedEvent,\n Tool,\n ToolCall,\n ToolCallArgsEvent,\n ToolCallEndEvent,\n ToolCallStartEvent,\n ToolExecutionContext,\n} from '../../../types'\n\n/**\n * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.\n *\n * Responsibilities:\n * - Accumulates streaming tool call events (ID, name, arguments)\n * - Validates tool calls (filters out incomplete ones)\n * - Executes tool `execute` functions with parsed arguments\n * - Emits `TOOL_CALL_END` events for client visibility\n * - Returns tool result messages for conversation history\n *\n * This class is used internally by the AI.chat() method to handle the automatic\n * tool execution loop. It can also be used independently for custom tool execution logic.\n *\n * @example\n * ```typescript\n * const manager = new ToolCallManager(tools);\n *\n * // During streaming, accumulate tool calls\n * for await (const chunk of stream) {\n * if (chunk.type === 'TOOL_CALL_START') {\n * manager.addToolCallStartEvent(chunk);\n * } else if (chunk.type === 'TOOL_CALL_ARGS') {\n * manager.addToolCallArgsEvent(chunk);\n * }\n * }\n *\n * // After stream completes, execute tools\n * if (manager.hasToolCalls()) {\n * const toolResults = yield* manager.executeTools(finishEvent);\n * messages = [...messages, ...toolResults];\n * manager.clear();\n * }\n * ```\n */\nexport class ToolCallManager {\n private toolCallsMap = new Map<number, ToolCall>()\n private tools: ReadonlyArray<Tool>\n\n constructor(tools: ReadonlyArray<Tool>) {\n this.tools = tools\n }\n\n /**\n * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)\n */\n addToolCallStartEvent(event: ToolCallStartEvent): void {\n const index = event.index ?? this.toolCallsMap.size\n this.toolCallsMap.set(index, {\n id: event.toolCallId,\n type: 'function',\n function: {\n name: event.toolName,\n arguments: '',\n },\n })\n }\n\n /**\n * Add a TOOL_CALL_ARGS event to accumulate arguments (AG-UI)\n */\n addToolCallArgsEvent(event: ToolCallArgsEvent): void {\n // Find the tool call by ID\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n toolCall.function.arguments += event.delta\n break\n }\n }\n }\n\n /**\n * Complete a tool call with its final input\n * Called when TOOL_CALL_END is received\n */\n completeToolCall(event: ToolCallEndEvent): void {\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n if (event.input !== undefined) {\n toolCall.function.arguments = JSON.stringify(event.input)\n }\n break\n }\n }\n }\n\n /**\n * Check if there are any complete tool calls to execute\n */\n hasToolCalls(): boolean {\n return this.getToolCalls().length > 0\n }\n\n /**\n * Get all complete tool calls (filtered for valid ID and name)\n */\n getToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCallsMap.values()).filter(\n (tc) => tc.id && tc.function.name && tc.function.name.trim().length > 0,\n )\n }\n\n /**\n * Execute all tool calls and return tool result messages\n * Yields TOOL_CALL_END events for streaming\n * @param finishEvent - RUN_FINISHED event from the stream\n */\n async *executeTools(\n finishEvent: RunFinishedEvent,\n ): AsyncGenerator<ToolCallEndEvent, Array<ModelMessage>, void> {\n const toolCallsArray = this.getToolCalls()\n const toolResults: Array<ModelMessage> = []\n\n for (const toolCall of toolCallsArray) {\n const tool = this.tools.find((t) => t.name === toolCall.function.name)\n\n let toolResultContent: string\n if (tool?.execute) {\n try {\n // Parse arguments (normalize \"null\" to \"{}\" for empty tool_use blocks)\n let args: unknown\n try {\n const argsString = toolCall.function.arguments.trim() || '{}'\n args = JSON.parse(argsString === 'null' ? '{}' : argsString)\n } catch (parseError) {\n throw new Error(\n `Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`,\n )\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n args = parseWithStandardSchema(tool.inputSchema, args)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Input validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n // Execute the tool\n let result = await tool.execute(args)\n\n // Validate output against outputSchema if provided (for Standard Schema compliant schemas)\n if (\n tool.outputSchema &&\n isStandardSchema(tool.outputSchema) &&\n result !== undefined &&\n result !== null\n ) {\n try {\n result = parseWithStandardSchema(tool.outputSchema, result)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n toolResultContent =\n typeof result === 'string' ? result : JSON.stringify(result)\n } catch (error: unknown) {\n // If tool execution fails, add error message\n const message =\n error instanceof Error ? error.message : 'Unknown error'\n toolResultContent = `Error executing tool: ${message}`\n }\n } else {\n // Tool doesn't have execute function, add placeholder\n toolResultContent = `Tool ${toolCall.function.name} does not have an execute function`\n }\n\n // Emit TOOL_CALL_END event\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n model: finishEvent.model,\n timestamp: Date.now(),\n result: toolResultContent,\n }\n\n // Add tool result message\n toolResults.push({\n role: 'tool',\n content: toolResultContent,\n toolCallId: toolCall.id,\n })\n }\n\n return toolResults\n }\n\n /**\n * Clear the tool calls map for the next iteration\n */\n clear(): void {\n this.toolCallsMap.clear()\n }\n}\n\nexport interface ToolResult {\n toolCallId: string\n toolName: string\n result: any\n state?: 'output-available' | 'output-error'\n /** Duration of tool execution in milliseconds (only for server-executed tools) */\n duration?: number\n}\n\nexport interface ApprovalRequest {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n}\n\nexport interface ClientToolRequest {\n toolCallId: string\n toolName: string\n input: any\n}\n\ninterface ExecuteToolCallsResult {\n /** Tool results ready to send to LLM */\n results: Array<ToolResult>\n /** Tools that need user approval before execution */\n needsApproval: Array<ApprovalRequest>\n /** Tools that need client-side execution */\n needsClientExecution: Array<ClientToolRequest>\n}\n\n/**\n * Helper that runs a tool execution promise while polling for pending custom events.\n * Yields any custom events that are emitted during execution, then returns the\n * execution result.\n */\nasync function* executeWithEventPolling<T>(\n executionPromise: Promise<T>,\n pendingEvents: Array<CustomEvent>,\n): AsyncGenerator<CustomEvent, T, void> {\n // Use an object to track mutable state across the async boundary\n const state = { done: false, result: undefined as T }\n const executionWithFlag = executionPromise.then((r) => {\n state.done = true\n state.result = r\n return r\n })\n\n while (!state.done) {\n // Wait for either the execution to complete or a short timeout\n await Promise.race([\n executionWithFlag,\n new Promise((resolve) => setTimeout(resolve, 10)),\n ])\n\n // Flush any pending events\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n }\n\n // Final flush in case events were emitted right at completion\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n\n return state.result\n}\n\n/**\n * Execute tool calls based on their configuration.\n * Yields CustomEvent chunks during tool execution for real-time progress updates.\n *\n * Handles three cases:\n * 1. Client tools (no execute) - request client to execute\n * 2. Server tools with approval - check approval before executing\n * 3. Normal server tools - execute immediately\n *\n * @param toolCalls - Tool calls from the LLM\n * @param tools - Available tools with their configurations\n * @param approvals - Map of approval decisions (approval.id -> approved boolean)\n * @param clientResults - Map of client-side execution results (toolCallId -> result)\n * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)\n */\nexport async function* executeToolCalls(\n toolCalls: Array<ToolCall>,\n tools: ReadonlyArray<Tool>,\n approvals: Map<string, boolean> = new Map(),\n clientResults: Map<string, any> = new Map(),\n createCustomEventChunk?: (\n eventName: string,\n value: Record<string, any>,\n ) => CustomEvent,\n): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\n\n // Create tool lookup map\n const toolMap = new Map<string, Tool>()\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n for (const toolCall of toolCalls) {\n const tool = toolMap.get(toolCall.function.name)\n const toolName = toolCall.function.name\n\n if (!tool) {\n // Unknown tool - return error\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: `Unknown tool: ${toolName}` },\n state: 'output-error',\n })\n continue\n }\n\n // Parse arguments, throwing error if invalid JSON\n let input: unknown = {}\n const argsStr = toolCall.function.arguments.trim() || '{}'\n if (argsStr) {\n try {\n input = JSON.parse(argsStr)\n } catch (parseError) {\n // If parsing fails, throw error to fail fast\n throw new Error(`Failed to parse tool arguments as JSON: ${argsStr}`)\n }\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n input = parseWithStandardSchema(tool.inputSchema, input)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Input validation failed for tool ${tool.name}: ${message}`,\n },\n state: 'output-error',\n })\n continue\n }\n }\n\n // Create a ToolExecutionContext for this tool call with event emission\n const pendingEvents: Array<CustomEvent> = []\n const context: ToolExecutionContext = {\n toolCallId: toolCall.id,\n emitCustomEvent: (eventName: string, value: Record<string, any>) => {\n if (createCustomEventChunk) {\n pendingEvents.push(\n createCustomEventChunk(eventName, {\n ...value,\n toolCallId: toolCall.id,\n }),\n )\n }\n },\n }\n\n // Helper to flush any pending events\n function* flushEvents(): Generator<CustomEvent> {\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n }\n\n // CASE 1: Client-side tool (no execute function)\n if (!tool.execute) {\n // Check if tool needs approval\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n\n // Check if approval decision exists\n if (approvals.has(approvalId)) {\n const approved = approvals.get(approvalId)\n\n if (approved) {\n // Approved - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: clientResults.get(toolCall.id),\n })\n } else {\n // Approved but not executed yet - request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'User declined tool execution' },\n state: 'output-error',\n })\n }\n } else {\n // Need approval first\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n input,\n approvalId,\n })\n }\n } else {\n // No approval needed - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: clientResults.get(toolCall.id),\n })\n } else {\n // Request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n }\n continue\n }\n\n // CASE 2: Server tool with approval required\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n\n // Check if approval decision exists\n if (approvals.has(approvalId)) {\n const approved = approvals.get(approvalId)\n\n if (approved) {\n // Execute after approval\n const startTime = Date.now()\n try {\n const executionPromise = Promise.resolve(\n tool.execute(input, context),\n )\n let result = yield* executeWithEventPolling(\n executionPromise,\n pendingEvents,\n )\n const duration = Date.now() - startTime\n yield* flushEvents()\n\n // Validate output against outputSchema if provided (for Standard Schema compliant schemas)\n if (\n tool.outputSchema &&\n isStandardSchema(tool.outputSchema) &&\n result !== undefined &&\n result !== null\n ) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof result === 'string'\n ? JSON.parse(result)\n : result || null,\n duration,\n })\n } catch (error: unknown) {\n const duration = Date.now() - startTime\n yield* flushEvents()\n const message =\n error instanceof Error ? error.message : 'Unknown error'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: message },\n state: 'output-error',\n duration,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'User declined tool execution' },\n state: 'output-error',\n })\n }\n } else {\n // Need approval\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n approvalId,\n })\n }\n continue\n }\n\n // CASE 3: Normal server tool - execute immediately\n const startTime = Date.now()\n try {\n const executionPromise = Promise.resolve(tool.execute(input, context))\n let result = yield* executeWithEventPolling(\n executionPromise,\n pendingEvents,\n )\n const duration = Date.now() - startTime\n yield* flushEvents()\n\n // Validate output against outputSchema if provided (for Standard Schema compliant schemas)\n if (\n tool.outputSchema &&\n isStandardSchema(tool.outputSchema) &&\n result !== undefined &&\n result !== null\n ) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof result === 'string' ? JSON.parse(result) : result || null,\n duration,\n })\n } catch (error: unknown) {\n const duration = Date.now() - startTime\n yield* flushEvents()\n const message = error instanceof Error ? error.message : 'Unknown error'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: message },\n state: 'output-error',\n duration,\n })\n }\n }\n\n return { results, needsApproval, needsClientExecution }\n}\n"],"names":["startTime"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool-calls.js","sources":["../../../../../src/activities/chat/tools/tool-calls.ts"],"sourcesContent":["import { isStandardSchema, parseWithStandardSchema } from './schema-converter'\nimport type {\n CustomEvent,\n ModelMessage,\n RunFinishedEvent,\n Tool,\n ToolCall,\n ToolCallArgsEvent,\n ToolCallEndEvent,\n ToolCallStartEvent,\n ToolExecutionContext,\n} from '../../../types'\n\n/**\n * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.\n *\n * Responsibilities:\n * - Accumulates streaming tool call events (ID, name, arguments)\n * - Validates tool calls (filters out incomplete ones)\n * - Executes tool `execute` functions with parsed arguments\n * - Emits `TOOL_CALL_END` events for client visibility\n * - Returns tool result messages for conversation history\n *\n * This class is used internally by the AI.chat() method to handle the automatic\n * tool execution loop. It can also be used independently for custom tool execution logic.\n *\n * @example\n * ```typescript\n * const manager = new ToolCallManager(tools);\n *\n * // During streaming, accumulate tool calls\n * for await (const chunk of stream) {\n * if (chunk.type === 'TOOL_CALL_START') {\n * manager.addToolCallStartEvent(chunk);\n * } else if (chunk.type === 'TOOL_CALL_ARGS') {\n * manager.addToolCallArgsEvent(chunk);\n * }\n * }\n *\n * // After stream completes, execute tools\n * if (manager.hasToolCalls()) {\n * const toolResults = yield* manager.executeTools(finishEvent);\n * messages = [...messages, ...toolResults];\n * manager.clear();\n * }\n * ```\n */\nexport class ToolCallManager {\n private toolCallsMap = new Map<number, ToolCall>()\n private tools: ReadonlyArray<Tool>\n\n constructor(tools: ReadonlyArray<Tool>) {\n this.tools = tools\n }\n\n /**\n * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)\n */\n addToolCallStartEvent(event: ToolCallStartEvent): void {\n const index = event.index ?? this.toolCallsMap.size\n this.toolCallsMap.set(index, {\n id: event.toolCallId,\n type: 'function',\n function: {\n name: event.toolName,\n arguments: '',\n },\n })\n }\n\n /**\n * Add a TOOL_CALL_ARGS event to accumulate arguments (AG-UI)\n */\n addToolCallArgsEvent(event: ToolCallArgsEvent): void {\n // Find the tool call by ID\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n toolCall.function.arguments += event.delta\n break\n }\n }\n }\n\n /**\n * Complete a tool call with its final input\n * Called when TOOL_CALL_END is received\n */\n completeToolCall(event: ToolCallEndEvent): void {\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n if (event.input !== undefined) {\n toolCall.function.arguments = JSON.stringify(event.input)\n }\n break\n }\n }\n }\n\n /**\n * Check if there are any complete tool calls to execute\n */\n hasToolCalls(): boolean {\n return this.getToolCalls().length > 0\n }\n\n /**\n * Get all complete tool calls (filtered for valid ID and name)\n */\n getToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCallsMap.values()).filter(\n (tc) => tc.id && tc.function.name && tc.function.name.trim().length > 0,\n )\n }\n\n /**\n * Execute all tool calls and return tool result messages\n * Yields TOOL_CALL_END events for streaming\n * @param finishEvent - RUN_FINISHED event from the stream\n */\n async *executeTools(\n finishEvent: RunFinishedEvent,\n ): AsyncGenerator<ToolCallEndEvent, Array<ModelMessage>, void> {\n const toolCallsArray = this.getToolCalls()\n const toolResults: Array<ModelMessage> = []\n\n for (const toolCall of toolCallsArray) {\n const tool = this.tools.find((t) => t.name === toolCall.function.name)\n\n let toolResultContent: string\n if (tool?.execute) {\n try {\n // Parse arguments (normalize \"null\" to \"{}\" for empty tool_use blocks)\n let args: unknown\n try {\n const argsString = toolCall.function.arguments.trim() || '{}'\n args = JSON.parse(argsString === 'null' ? '{}' : argsString)\n } catch (parseError) {\n throw new Error(\n `Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`,\n )\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n args = parseWithStandardSchema(tool.inputSchema, args)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Input validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n // Execute the tool\n let result = await tool.execute(args)\n\n // Validate output against outputSchema if provided (for Standard Schema compliant schemas)\n if (\n tool.outputSchema &&\n isStandardSchema(tool.outputSchema) &&\n result !== undefined &&\n result !== null\n ) {\n try {\n result = parseWithStandardSchema(tool.outputSchema, result)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n toolResultContent =\n typeof result === 'string' ? result : JSON.stringify(result)\n } catch (error: unknown) {\n // If tool execution fails, add error message\n const message =\n error instanceof Error ? error.message : 'Unknown error'\n toolResultContent = `Error executing tool: ${message}`\n }\n } else {\n // Tool doesn't have execute function, add placeholder\n toolResultContent = `Tool ${toolCall.function.name} does not have an execute function`\n }\n\n // Emit TOOL_CALL_END event\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n model: finishEvent.model,\n timestamp: Date.now(),\n result: toolResultContent,\n }\n\n // Add tool result message\n toolResults.push({\n role: 'tool',\n content: toolResultContent,\n toolCallId: toolCall.id,\n })\n }\n\n return toolResults\n }\n\n /**\n * Clear the tool calls map for the next iteration\n */\n clear(): void {\n this.toolCallsMap.clear()\n }\n}\n\nexport interface ToolResult {\n toolCallId: string\n toolName: string\n result: any\n state?: 'output-available' | 'output-error'\n /** Duration of tool execution in milliseconds (only for server-executed tools) */\n duration?: number\n}\n\nexport interface ApprovalRequest {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n}\n\nexport interface ClientToolRequest {\n toolCallId: string\n toolName: string\n input: any\n}\n\ninterface ExecuteToolCallsResult {\n /** Tool results ready to send to LLM */\n results: Array<ToolResult>\n /** Tools that need user approval before execution */\n needsApproval: Array<ApprovalRequest>\n /** Tools that need client-side execution */\n needsClientExecution: Array<ClientToolRequest>\n}\n\n/**\n * Helper that runs a tool execution promise while polling for pending custom events.\n * Yields any custom events that are emitted during execution, then returns the\n * execution result.\n */\nasync function* executeWithEventPolling<T>(\n executionPromise: Promise<T>,\n pendingEvents: Array<CustomEvent>,\n): AsyncGenerator<CustomEvent, T, void> {\n // Use an object to track mutable state across the async boundary\n const state = { done: false, result: undefined as T }\n const executionWithFlag = executionPromise.then((r) => {\n state.done = true\n state.result = r\n return r\n })\n\n while (!state.done) {\n // Wait for either the execution to complete or a short timeout\n await Promise.race([\n executionWithFlag,\n new Promise((resolve) => setTimeout(resolve, 10)),\n ])\n\n // Flush any pending events\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n }\n\n // Final flush in case events were emitted right at completion\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n\n return state.result\n}\n\n/**\n * Execute tool calls based on their configuration.\n * Yields CustomEvent chunks during tool execution for real-time progress updates.\n *\n * Handles three cases:\n * 1. Client tools (no execute) - request client to execute\n * 2. Server tools with approval - check approval before executing\n * 3. Normal server tools - execute immediately\n *\n * @param toolCalls - Tool calls from the LLM\n * @param tools - Available tools with their configurations\n * @param approvals - Map of approval decisions (approval.id -> approved boolean)\n * @param clientResults - Map of client-side execution results (toolCallId -> result)\n * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)\n */\nexport async function* executeToolCalls(\n toolCalls: Array<ToolCall>,\n tools: ReadonlyArray<Tool>,\n approvals: Map<string, boolean> = new Map(),\n clientResults: Map<string, any> = new Map(),\n createCustomEventChunk?: (\n eventName: string,\n value: Record<string, any>,\n ) => CustomEvent,\n): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\n\n // Create tool lookup map\n const toolMap = new Map<string, Tool>()\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n // Batch gating: when any tool in the batch still needs an approval decision,\n // defer all execution so side effects don't happen before the user decides.\n const hasPendingApprovals = toolCalls.some((tc) => {\n const t = toolMap.get(tc.function.name)\n return t?.needsApproval && !approvals.has(`approval_${tc.id}`)\n })\n\n for (const toolCall of toolCalls) {\n const tool = toolMap.get(toolCall.function.name)\n const toolName = toolCall.function.name\n\n if (!tool) {\n // Unknown tool - return error\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: `Unknown tool: ${toolName}` },\n state: 'output-error',\n })\n continue\n }\n\n // Skip non-pending tools while approvals are outstanding\n if (hasPendingApprovals) {\n if (!tool.needsApproval || approvals.has(`approval_${toolCall.id}`)) {\n continue\n }\n }\n\n // Parse arguments, throwing error if invalid JSON\n let input: unknown = {}\n const argsStr = toolCall.function.arguments.trim() || '{}'\n if (argsStr) {\n try {\n input = JSON.parse(argsStr)\n } catch (parseError) {\n // If parsing fails, throw error to fail fast\n throw new Error(`Failed to parse tool arguments as JSON: ${argsStr}`)\n }\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n input = parseWithStandardSchema(tool.inputSchema, input)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Input validation failed for tool ${tool.name}: ${message}`,\n },\n state: 'output-error',\n })\n continue\n }\n }\n\n // Create a ToolExecutionContext for this tool call with event emission\n const pendingEvents: Array<CustomEvent> = []\n const context: ToolExecutionContext = {\n toolCallId: toolCall.id,\n emitCustomEvent: (eventName: string, value: Record<string, any>) => {\n if (createCustomEventChunk) {\n pendingEvents.push(\n createCustomEventChunk(eventName, {\n ...value,\n toolCallId: toolCall.id,\n }),\n )\n }\n },\n }\n\n // Helper to flush any pending events\n function* flushEvents(): Generator<CustomEvent> {\n while (pendingEvents.length > 0) {\n yield pendingEvents.shift()!\n }\n }\n\n // CASE 1: Client-side tool (no execute function)\n if (!tool.execute) {\n // Check if tool needs approval\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n\n // Check if approval decision exists\n if (approvals.has(approvalId)) {\n const approved = approvals.get(approvalId)\n\n if (approved) {\n // Approved - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: clientResults.get(toolCall.id),\n })\n } else {\n // Approved but not executed yet - request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'User declined tool execution' },\n state: 'output-error',\n })\n }\n } else {\n // Need approval first\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n input,\n approvalId,\n })\n }\n } else {\n // No approval needed - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: clientResults.get(toolCall.id),\n })\n } else {\n // Request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n }\n continue\n }\n\n // CASE 2: Server tool with approval required\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n\n // Check if approval decision exists\n if (approvals.has(approvalId)) {\n const approved = approvals.get(approvalId)\n\n if (approved) {\n // Execute after approval\n const startTime = Date.now()\n try {\n const executionPromise = Promise.resolve(\n tool.execute(input, context),\n )\n let result = yield* executeWithEventPolling(\n executionPromise,\n pendingEvents,\n )\n const duration = Date.now() - startTime\n yield* flushEvents()\n\n // Validate output against outputSchema if provided (for Standard Schema compliant schemas)\n if (\n tool.outputSchema &&\n isStandardSchema(tool.outputSchema) &&\n result !== undefined &&\n result !== null\n ) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof result === 'string'\n ? JSON.parse(result)\n : result || null,\n duration,\n })\n } catch (error: unknown) {\n const duration = Date.now() - startTime\n yield* flushEvents()\n const message =\n error instanceof Error ? error.message : 'Unknown error'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: message },\n state: 'output-error',\n duration,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'User declined tool execution' },\n state: 'output-error',\n })\n }\n } else {\n // Need approval\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n approvalId,\n })\n }\n continue\n }\n\n // CASE 3: Normal server tool - execute immediately\n const startTime = Date.now()\n try {\n const executionPromise = Promise.resolve(tool.execute(input, context))\n let result = yield* executeWithEventPolling(\n executionPromise,\n pendingEvents,\n )\n const duration = Date.now() - startTime\n yield* flushEvents()\n\n // Validate output against outputSchema if provided (for Standard Schema compliant schemas)\n if (\n tool.outputSchema &&\n isStandardSchema(tool.outputSchema) &&\n result !== undefined &&\n result !== null\n ) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof result === 'string' ? JSON.parse(result) : result || null,\n duration,\n })\n } catch (error: unknown) {\n const duration = Date.now() - startTime\n yield* flushEvents()\n const message = error instanceof Error ? error.message : 'Unknown error'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: message },\n state: 'output-error',\n duration,\n })\n }\n }\n\n return { results, needsApproval, needsClientExecution }\n}\n"],"names":["startTime"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ImageAdapter } from './adapter.js';
|
|
2
|
-
import { ImageGenerationResult } from '../../types.js';
|
|
2
|
+
import { ImageGenerationResult, StreamChunk } from '../../types.js';
|
|
3
3
|
/** The adapter kind this activity handles */
|
|
4
4
|
export declare const kind: "image";
|
|
5
5
|
/**
|
|
@@ -18,8 +18,9 @@ export type ImageSizeForModel<TAdapter, TModel extends string> = TAdapter extend
|
|
|
18
18
|
* The model is extracted from the adapter's model property.
|
|
19
19
|
*
|
|
20
20
|
* @template TAdapter - The image adapter type
|
|
21
|
+
* @template TStream - Whether to stream the output
|
|
21
22
|
*/
|
|
22
|
-
export type ImageActivityOptions<TAdapter extends ImageAdapter<string, any, any, any
|
|
23
|
+
export type ImageActivityOptions<TAdapter extends ImageAdapter<string, any, any, any>, TStream extends boolean = false> = {
|
|
23
24
|
/** The image adapter to use (must be created with a model) */
|
|
24
25
|
adapter: TAdapter & {
|
|
25
26
|
kind: typeof kind;
|
|
@@ -30,13 +31,25 @@ export type ImageActivityOptions<TAdapter extends ImageAdapter<string, any, any,
|
|
|
30
31
|
numberOfImages?: number;
|
|
31
32
|
/** Image size in WIDTHxHEIGHT format (e.g., "1024x1024") */
|
|
32
33
|
size?: ImageSizeForModel<TAdapter, TAdapter['model']>;
|
|
34
|
+
/**
|
|
35
|
+
* Whether to stream the image generation result.
|
|
36
|
+
* When true, returns an AsyncIterable<StreamChunk> for streaming transport.
|
|
37
|
+
* When false or not provided, returns a Promise<ImageGenerationResult>.
|
|
38
|
+
*
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
stream?: TStream;
|
|
33
42
|
} & ({} extends ImageProviderOptionsForModel<TAdapter, TAdapter['model']> ? {
|
|
34
43
|
/** Provider-specific options for image generation */ modelOptions?: ImageProviderOptionsForModel<TAdapter, TAdapter['model']>;
|
|
35
44
|
} : {
|
|
36
45
|
/** Provider-specific options for image generation */ modelOptions: ImageProviderOptionsForModel<TAdapter, TAdapter['model']>;
|
|
37
46
|
});
|
|
38
|
-
/**
|
|
39
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Result type for the image activity.
|
|
49
|
+
* - If stream is true: AsyncIterable<StreamChunk>
|
|
50
|
+
* - Otherwise: Promise<ImageGenerationResult>
|
|
51
|
+
*/
|
|
52
|
+
export type ImageActivityResult<TStream extends boolean = false> = TStream extends true ? AsyncIterable<StreamChunk> : Promise<ImageGenerationResult>;
|
|
40
53
|
/**
|
|
41
54
|
* Image activity - generates images from text prompts.
|
|
42
55
|
*
|
|
@@ -82,10 +95,10 @@ export type ImageActivityResult = Promise<ImageGenerationResult>;
|
|
|
82
95
|
* })
|
|
83
96
|
* ```
|
|
84
97
|
*/
|
|
85
|
-
export declare function generateImage<TAdapter extends ImageAdapter<string, any, any, any
|
|
98
|
+
export declare function generateImage<TAdapter extends ImageAdapter<string, any, any, any>, TStream extends boolean = false>(options: ImageActivityOptions<TAdapter, TStream>): ImageActivityResult<TStream>;
|
|
86
99
|
/**
|
|
87
100
|
* Create typed options for the generateImage() function without executing.
|
|
88
101
|
*/
|
|
89
|
-
export declare function createImageOptions<TAdapter extends ImageAdapter<string, any, any, any
|
|
102
|
+
export declare function createImageOptions<TAdapter extends ImageAdapter<string, any, any, any>, TStream extends boolean = false>(options: ImageActivityOptions<TAdapter, TStream>): ImageActivityOptions<TAdapter, TStream>;
|
|
90
103
|
export type { ImageAdapter, ImageAdapterConfig, AnyImageAdapter, } from './adapter.js';
|
|
91
104
|
export { BaseImageAdapter } from './adapter.js';
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { aiEventClient } from "../../event-client.js";
|
|
2
|
+
import { streamGenerationResult } from "../stream-generation-result.js";
|
|
2
3
|
const kind = "image";
|
|
3
4
|
function createId(prefix) {
|
|
4
5
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
function generateImage(options) {
|
|
8
|
+
if (options.stream) {
|
|
9
|
+
return streamGenerationResult(
|
|
10
|
+
() => runGenerateImage(options)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
return runGenerateImage(options);
|
|
14
|
+
}
|
|
15
|
+
async function runGenerateImage(options) {
|
|
16
|
+
const { adapter, stream: _stream, ...rest } = options;
|
|
8
17
|
const model = adapter.model;
|
|
9
18
|
const requestId = createId("image");
|
|
10
19
|
const startTime = Date.now();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/activities/generateImage/index.ts"],"sourcesContent":["/**\n * Image Activity\n *\n * Generates images from text prompts.\n * This is a self-contained module with implementation, types, and JSDoc.\n */\n\nimport { aiEventClient } from '../../event-client.js'\nimport type { ImageAdapter } from './adapter'\nimport type { ImageGenerationResult } from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'image' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract model-specific provider options from an ImageAdapter via ~types.\n * If the model has specific options defined in ModelProviderOptions (and not just via index signature),\n * use those; otherwise fall back to base provider options.\n */\nexport type ImageProviderOptionsForModel<TAdapter, TModel extends string> =\n TAdapter extends ImageAdapter<any, infer BaseOptions, infer ModelOptions, any>\n ? string extends keyof ModelOptions\n ? // ModelOptions is Record<string, unknown> or has index signature - use BaseOptions\n BaseOptions\n : // ModelOptions has explicit keys - check if TModel is one of them\n TModel extends keyof ModelOptions\n ? ModelOptions[TModel]\n : BaseOptions\n : object\n\n/**\n * Extract model-specific size options from an ImageAdapter via ~types.\n * If the model has specific sizes defined, use those; otherwise fall back to string.\n */\nexport type ImageSizeForModel<TAdapter, TModel extends string> =\n TAdapter extends ImageAdapter<any, any, any, infer SizeByName>\n ? string extends keyof SizeByName\n ? // SizeByName has index signature - fall back to string\n string\n : // SizeByName has explicit keys - check if TModel is one of them\n TModel extends keyof SizeByName\n ? SizeByName[TModel]\n : string\n : string\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the image activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The image adapter type\n */\nexport type ImageActivityOptions<\n TAdapter extends ImageAdapter<string, any, any, any>,\n> = {\n /** The image adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** Text description of the desired image(s) */\n prompt: string\n /** Number of images to generate (default: 1) */\n numberOfImages?: number\n /** Image size in WIDTHxHEIGHT format (e.g., \"1024x1024\") */\n size?: ImageSizeForModel<TAdapter, TAdapter['model']>\n} & ({} extends ImageProviderOptionsForModel<TAdapter, TAdapter['model']>\n ? {\n /** Provider-specific options for image generation */ modelOptions?: ImageProviderOptionsForModel<\n TAdapter,\n TAdapter['model']\n >\n }\n : {\n /** Provider-specific options for image generation */ modelOptions: ImageProviderOptionsForModel<\n TAdapter,\n TAdapter['model']\n >\n })\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/activities/generateImage/index.ts"],"sourcesContent":["/**\n * Image Activity\n *\n * Generates images from text prompts.\n * This is a self-contained module with implementation, types, and JSDoc.\n */\n\nimport { aiEventClient } from '../../event-client.js'\nimport { streamGenerationResult } from '../stream-generation-result.js'\nimport type { ImageAdapter } from './adapter'\nimport type { ImageGenerationResult, StreamChunk } from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'image' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract model-specific provider options from an ImageAdapter via ~types.\n * If the model has specific options defined in ModelProviderOptions (and not just via index signature),\n * use those; otherwise fall back to base provider options.\n */\nexport type ImageProviderOptionsForModel<TAdapter, TModel extends string> =\n TAdapter extends ImageAdapter<any, infer BaseOptions, infer ModelOptions, any>\n ? string extends keyof ModelOptions\n ? // ModelOptions is Record<string, unknown> or has index signature - use BaseOptions\n BaseOptions\n : // ModelOptions has explicit keys - check if TModel is one of them\n TModel extends keyof ModelOptions\n ? ModelOptions[TModel]\n : BaseOptions\n : object\n\n/**\n * Extract model-specific size options from an ImageAdapter via ~types.\n * If the model has specific sizes defined, use those; otherwise fall back to string.\n */\nexport type ImageSizeForModel<TAdapter, TModel extends string> =\n TAdapter extends ImageAdapter<any, any, any, infer SizeByName>\n ? string extends keyof SizeByName\n ? // SizeByName has index signature - fall back to string\n string\n : // SizeByName has explicit keys - check if TModel is one of them\n TModel extends keyof SizeByName\n ? SizeByName[TModel]\n : string\n : string\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the image activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The image adapter type\n * @template TStream - Whether to stream the output\n */\nexport type ImageActivityOptions<\n TAdapter extends ImageAdapter<string, any, any, any>,\n TStream extends boolean = false,\n> = {\n /** The image adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** Text description of the desired image(s) */\n prompt: string\n /** Number of images to generate (default: 1) */\n numberOfImages?: number\n /** Image size in WIDTHxHEIGHT format (e.g., \"1024x1024\") */\n size?: ImageSizeForModel<TAdapter, TAdapter['model']>\n /**\n * Whether to stream the image generation result.\n * When true, returns an AsyncIterable<StreamChunk> for streaming transport.\n * When false or not provided, returns a Promise<ImageGenerationResult>.\n *\n * @default false\n */\n stream?: TStream\n} & ({} extends ImageProviderOptionsForModel<TAdapter, TAdapter['model']>\n ? {\n /** Provider-specific options for image generation */ modelOptions?: ImageProviderOptionsForModel<\n TAdapter,\n TAdapter['model']\n >\n }\n : {\n /** Provider-specific options for image generation */ modelOptions: ImageProviderOptionsForModel<\n TAdapter,\n TAdapter['model']\n >\n })\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n/**\n * Result type for the image activity.\n * - If stream is true: AsyncIterable<StreamChunk>\n * - Otherwise: Promise<ImageGenerationResult>\n */\nexport type ImageActivityResult<TStream extends boolean = false> =\n TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<ImageGenerationResult>\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Image activity - generates images from text prompts.\n *\n * Uses AI image generation models to create images based on natural language descriptions.\n *\n * @example Generate a single image\n * ```ts\n * import { generateImage } from '@tanstack/ai'\n * import { openaiImage } from '@tanstack/ai-openai'\n *\n * const result = await generateImage({\n * adapter: openaiImage('dall-e-3'),\n * prompt: 'A serene mountain landscape at sunset'\n * })\n *\n * console.log(result.images[0].url)\n * ```\n *\n * @example Generate multiple images\n * ```ts\n * const result = await generateImage({\n * adapter: openaiImage('dall-e-2'),\n * prompt: 'A cute robot mascot',\n * numberOfImages: 4,\n * size: '512x512'\n * })\n *\n * result.images.forEach((image, i) => {\n * console.log(`Image ${i + 1}: ${image.url}`)\n * })\n * ```\n *\n * @example With provider-specific options\n * ```ts\n * const result = await generateImage({\n * adapter: openaiImage('dall-e-3'),\n * prompt: 'A professional headshot photo',\n * size: '1024x1024',\n * modelOptions: {\n * quality: 'hd',\n * style: 'natural'\n * }\n * })\n * ```\n */\nexport function generateImage<\n TAdapter extends ImageAdapter<string, any, any, any>,\n TStream extends boolean = false,\n>(\n options: ImageActivityOptions<TAdapter, TStream>,\n): ImageActivityResult<TStream> {\n if (options.stream) {\n return streamGenerationResult(() =>\n runGenerateImage(options),\n ) as ImageActivityResult<TStream>\n }\n\n return runGenerateImage(options) as ImageActivityResult<TStream>\n}\n\n/**\n * Internal implementation of image generation (always non-streaming).\n * Contains all devtools event emission logic.\n */\nasync function runGenerateImage<\n TAdapter extends ImageAdapter<string, any, any, any>,\n>(\n options: ImageActivityOptions<TAdapter, boolean>,\n): Promise<ImageGenerationResult> {\n const { adapter, stream: _stream, ...rest } = options\n const model = adapter.model\n const requestId = createId('image')\n const startTime = Date.now()\n\n aiEventClient.emit('image:request:started', {\n requestId,\n provider: adapter.name,\n model,\n prompt: rest.prompt,\n numberOfImages: rest.numberOfImages,\n size: rest.size,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: startTime,\n })\n\n return adapter.generateImages({ ...rest, model }).then((result) => {\n const duration = Date.now() - startTime\n\n aiEventClient.emit('image:request:completed', {\n requestId,\n provider: adapter.name,\n model,\n images: result.images.map((image) => ({\n url: image.url,\n b64Json: image.b64Json,\n })),\n duration,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: Date.now(),\n })\n\n if (result.usage) {\n aiEventClient.emit('image:usage', {\n requestId,\n model,\n usage: result.usage,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: Date.now(),\n })\n }\n\n return result\n })\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the generateImage() function without executing.\n */\nexport function createImageOptions<\n TAdapter extends ImageAdapter<string, any, any, any>,\n TStream extends boolean = false,\n>(\n options: ImageActivityOptions<TAdapter, TStream>,\n): ImageActivityOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n ImageAdapter,\n ImageAdapterConfig,\n AnyImageAdapter,\n} from './adapter'\nexport { BaseImageAdapter } from './adapter'\n"],"names":[],"mappings":";;AAiBO,MAAM,OAAO;AAgGpB,SAAS,SAAS,QAAwB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAmDO,SAAS,cAId,SAC8B;AAC9B,MAAI,QAAQ,QAAQ;AAClB,WAAO;AAAA,MAAuB,MAC5B,iBAAiB,OAAO;AAAA,IAAA;AAAA,EAE5B;AAEA,SAAO,iBAAiB,OAAO;AACjC;AAMA,eAAe,iBAGb,SACgC;AAChC,QAAM,EAAE,SAAS,QAAQ,SAAS,GAAG,SAAS;AAC9C,QAAM,QAAQ,QAAQ;AACtB,QAAM,YAAY,SAAS,OAAO;AAClC,QAAM,YAAY,KAAK,IAAA;AAEvB,gBAAc,KAAK,yBAAyB;AAAA,IAC1C;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,gBAAgB,KAAK;AAAA,IACrB,MAAM,KAAK;AAAA,IACX,cAAc,KAAK;AAAA,IACnB,WAAW;AAAA,EAAA,CACZ;AAED,SAAO,QAAQ,eAAe,EAAE,GAAG,MAAM,OAAO,EAAE,KAAK,CAAC,WAAW;AACjE,UAAM,WAAW,KAAK,IAAA,IAAQ;AAE9B,kBAAc,KAAK,2BAA2B;AAAA,MAC5C;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB;AAAA,MACA,QAAQ,OAAO,OAAO,IAAI,CAAC,WAAW;AAAA,QACpC,KAAK,MAAM;AAAA,QACX,SAAS,MAAM;AAAA,MAAA,EACf;AAAA,MACF;AAAA,MACA,cAAc,KAAK;AAAA,MACnB,WAAW,KAAK,IAAA;AAAA,IAAI,CACrB;AAED,QAAI,OAAO,OAAO;AAChB,oBAAc,KAAK,eAAe;AAAA,QAChC;AAAA,QACA;AAAA,QACA,OAAO,OAAO;AAAA,QACd,cAAc,KAAK;AAAA,QACnB,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB;AAAA,IACH;AAEA,WAAO;AAAA,EACT,CAAC;AACH;AASO,SAAS,mBAId,SACyC;AACzC,SAAO;AACT;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TTSAdapter } from './adapter.js';
|
|
2
|
-
import { TTSResult } from '../../types.js';
|
|
2
|
+
import { StreamChunk, TTSResult } from '../../types.js';
|
|
3
3
|
/** The adapter kind this activity handles */
|
|
4
4
|
export declare const kind: "tts";
|
|
5
5
|
/**
|
|
@@ -11,8 +11,9 @@ export type TTSProviderOptions<TAdapter> = TAdapter extends TTSAdapter<any, any>
|
|
|
11
11
|
* The model is extracted from the adapter's model property.
|
|
12
12
|
*
|
|
13
13
|
* @template TAdapter - The TTS adapter type
|
|
14
|
+
* @template TStream - Whether to stream the output
|
|
14
15
|
*/
|
|
15
|
-
export interface TTSActivityOptions<TAdapter extends TTSAdapter<string, object
|
|
16
|
+
export interface TTSActivityOptions<TAdapter extends TTSAdapter<string, object>, TStream extends boolean = false> {
|
|
16
17
|
/** The TTS adapter to use (must be created with a model) */
|
|
17
18
|
adapter: TAdapter & {
|
|
18
19
|
kind: typeof kind;
|
|
@@ -27,9 +28,21 @@ export interface TTSActivityOptions<TAdapter extends TTSAdapter<string, object>>
|
|
|
27
28
|
speed?: number;
|
|
28
29
|
/** Provider-specific options for TTS generation */
|
|
29
30
|
modelOptions?: TTSProviderOptions<TAdapter>;
|
|
31
|
+
/**
|
|
32
|
+
* Whether to stream the generation result.
|
|
33
|
+
* When true, returns an AsyncIterable<StreamChunk> for streaming transport.
|
|
34
|
+
* When false or not provided, returns a Promise<TTSResult>.
|
|
35
|
+
*
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
stream?: TStream;
|
|
30
39
|
}
|
|
31
|
-
/**
|
|
32
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Result type for the TTS activity.
|
|
42
|
+
* - If stream is true: AsyncIterable<StreamChunk>
|
|
43
|
+
* - Otherwise: Promise<TTSResult>
|
|
44
|
+
*/
|
|
45
|
+
export type TTSActivityResult<TStream extends boolean = false> = TStream extends true ? AsyncIterable<StreamChunk> : Promise<TTSResult>;
|
|
33
46
|
/**
|
|
34
47
|
* TTS activity - generates speech from text.
|
|
35
48
|
*
|
|
@@ -60,10 +73,10 @@ export type TTSActivityResult = Promise<TTSResult>;
|
|
|
60
73
|
* })
|
|
61
74
|
* ```
|
|
62
75
|
*/
|
|
63
|
-
export declare function generateSpeech<TAdapter extends TTSAdapter<string, object
|
|
76
|
+
export declare function generateSpeech<TAdapter extends TTSAdapter<string, object>, TStream extends boolean = false>(options: TTSActivityOptions<TAdapter, TStream>): TTSActivityResult<TStream>;
|
|
64
77
|
/**
|
|
65
78
|
* Create typed options for the generateSpeech() function without executing.
|
|
66
79
|
*/
|
|
67
|
-
export declare function createSpeechOptions<TAdapter extends TTSAdapter<string, object
|
|
80
|
+
export declare function createSpeechOptions<TAdapter extends TTSAdapter<string, object>, TStream extends boolean = false>(options: TTSActivityOptions<TAdapter, TStream>): TTSActivityOptions<TAdapter, TStream>;
|
|
68
81
|
export type { TTSAdapter, TTSAdapterConfig, AnyTTSAdapter } from './adapter.js';
|
|
69
82
|
export { BaseTTSAdapter } from './adapter.js';
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { aiEventClient } from "../../event-client.js";
|
|
2
|
+
import { streamGenerationResult } from "../stream-generation-result.js";
|
|
2
3
|
const kind = "tts";
|
|
3
4
|
function createId(prefix) {
|
|
4
5
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
function generateSpeech(options) {
|
|
8
|
+
if (options.stream) {
|
|
9
|
+
return streamGenerationResult(
|
|
10
|
+
() => runGenerateSpeech(options)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
return runGenerateSpeech(options);
|
|
14
|
+
}
|
|
15
|
+
async function runGenerateSpeech(options) {
|
|
16
|
+
const { adapter, stream: _stream, ...rest } = options;
|
|
8
17
|
const model = adapter.model;
|
|
9
18
|
const requestId = createId("speech");
|
|
10
19
|
const startTime = Date.now();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/activities/generateSpeech/index.ts"],"sourcesContent":["/**\n * TTS Activity\n *\n * Generates speech audio from text using text-to-speech models.\n * This is a self-contained module with implementation, types, and JSDoc.\n */\n\nimport { aiEventClient } from '../../event-client.js'\nimport type { TTSAdapter } from './adapter'\nimport type { TTSResult } from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'tts' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract provider options from a TTSAdapter via ~types.\n */\nexport type TTSProviderOptions<TAdapter> =\n TAdapter extends TTSAdapter<any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the TTS activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The TTS adapter type\n */\nexport interface TTSActivityOptions<\n TAdapter extends TTSAdapter<string, object>,\n> {\n /** The TTS adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** The text to convert to speech */\n text: string\n /** The voice to use for generation */\n voice?: string\n /** The output audio format */\n format?: 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'\n /** The speed of the generated audio (0.25 to 4.0) */\n speed?: number\n /** Provider-specific options for TTS generation */\n modelOptions?: TTSProviderOptions<TAdapter>\n}\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/activities/generateSpeech/index.ts"],"sourcesContent":["/**\n * TTS Activity\n *\n * Generates speech audio from text using text-to-speech models.\n * This is a self-contained module with implementation, types, and JSDoc.\n */\n\nimport { aiEventClient } from '../../event-client.js'\nimport { streamGenerationResult } from '../stream-generation-result.js'\nimport type { TTSAdapter } from './adapter'\nimport type { StreamChunk, TTSResult } from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'tts' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract provider options from a TTSAdapter via ~types.\n */\nexport type TTSProviderOptions<TAdapter> =\n TAdapter extends TTSAdapter<any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the TTS activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The TTS adapter type\n * @template TStream - Whether to stream the output\n */\nexport interface TTSActivityOptions<\n TAdapter extends TTSAdapter<string, object>,\n TStream extends boolean = false,\n> {\n /** The TTS adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** The text to convert to speech */\n text: string\n /** The voice to use for generation */\n voice?: string\n /** The output audio format */\n format?: 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'\n /** The speed of the generated audio (0.25 to 4.0) */\n speed?: number\n /** Provider-specific options for TTS generation */\n modelOptions?: TTSProviderOptions<TAdapter>\n /**\n * Whether to stream the generation result.\n * When true, returns an AsyncIterable<StreamChunk> for streaming transport.\n * When false or not provided, returns a Promise<TTSResult>.\n *\n * @default false\n */\n stream?: TStream\n}\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n/**\n * Result type for the TTS activity.\n * - If stream is true: AsyncIterable<StreamChunk>\n * - Otherwise: Promise<TTSResult>\n */\nexport type TTSActivityResult<TStream extends boolean = false> =\n TStream extends true ? AsyncIterable<StreamChunk> : Promise<TTSResult>\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * TTS activity - generates speech from text.\n *\n * Uses AI text-to-speech models to create audio from natural language text.\n *\n * @example Generate speech from text\n * ```ts\n * import { generateSpeech } from '@tanstack/ai'\n * import { openaiTTS } from '@tanstack/ai-openai'\n *\n * const result = await generateSpeech({\n * adapter: openaiTTS('tts-1-hd'),\n * text: 'Hello, welcome to TanStack AI!',\n * voice: 'nova'\n * })\n *\n * console.log(result.audio) // base64-encoded audio\n * ```\n *\n * @example With format and speed options\n * ```ts\n * const result = await generateSpeech({\n * adapter: openaiTTS('tts-1'),\n * text: 'This is slower speech.',\n * voice: 'alloy',\n * format: 'wav',\n * speed: 0.8\n * })\n * ```\n */\nexport function generateSpeech<\n TAdapter extends TTSAdapter<string, object>,\n TStream extends boolean = false,\n>(options: TTSActivityOptions<TAdapter, TStream>): TTSActivityResult<TStream> {\n if (options.stream) {\n return streamGenerationResult(() =>\n runGenerateSpeech(options),\n ) as TTSActivityResult<TStream>\n }\n return runGenerateSpeech(options) as TTSActivityResult<TStream>\n}\n\n/**\n * Run the core TTS generation logic (non-streaming).\n */\nasync function runGenerateSpeech<TAdapter extends TTSAdapter<string, object>>(\n options: TTSActivityOptions<TAdapter, boolean>,\n): Promise<TTSResult> {\n const { adapter, stream: _stream, ...rest } = options\n const model = adapter.model\n const requestId = createId('speech')\n const startTime = Date.now()\n\n aiEventClient.emit('speech:request:started', {\n requestId,\n provider: adapter.name,\n model,\n text: rest.text,\n voice: rest.voice,\n format: rest.format,\n speed: rest.speed,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: startTime,\n })\n\n return adapter.generateSpeech({ ...rest, model }).then((result) => {\n const duration = Date.now() - startTime\n\n aiEventClient.emit('speech:request:completed', {\n requestId,\n provider: adapter.name,\n model,\n audio: result.audio,\n format: result.format,\n audioDuration: result.duration,\n contentType: result.contentType,\n duration,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: Date.now(),\n })\n\n return result\n })\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the generateSpeech() function without executing.\n */\nexport function createSpeechOptions<\n TAdapter extends TTSAdapter<string, object>,\n TStream extends boolean = false,\n>(\n options: TTSActivityOptions<TAdapter, TStream>,\n): TTSActivityOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type { TTSAdapter, TTSAdapterConfig, AnyTTSAdapter } from './adapter'\nexport { BaseTTSAdapter } from './adapter'\n"],"names":[],"mappings":";;AAiBO,MAAM,OAAO;AA+DpB,SAAS,SAAS,QAAwB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAoCO,SAAS,eAGd,SAA4E;AAC5E,MAAI,QAAQ,QAAQ;AAClB,WAAO;AAAA,MAAuB,MAC5B,kBAAkB,OAAO;AAAA,IAAA;AAAA,EAE7B;AACA,SAAO,kBAAkB,OAAO;AAClC;AAKA,eAAe,kBACb,SACoB;AACpB,QAAM,EAAE,SAAS,QAAQ,SAAS,GAAG,SAAS;AAC9C,QAAM,QAAQ,QAAQ;AACtB,QAAM,YAAY,SAAS,QAAQ;AACnC,QAAM,YAAY,KAAK,IAAA;AAEvB,gBAAc,KAAK,0BAA0B;AAAA,IAC3C;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,OAAO,KAAK;AAAA,IACZ,cAAc,KAAK;AAAA,IACnB,WAAW;AAAA,EAAA,CACZ;AAED,SAAO,QAAQ,eAAe,EAAE,GAAG,MAAM,OAAO,EAAE,KAAK,CAAC,WAAW;AACjE,UAAM,WAAW,KAAK,IAAA,IAAQ;AAE9B,kBAAc,KAAK,4BAA4B;AAAA,MAC7C;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB;AAAA,MACA,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,eAAe,OAAO;AAAA,MACtB,aAAa,OAAO;AAAA,MACpB;AAAA,MACA,cAAc,KAAK;AAAA,MACnB,WAAW,KAAK,IAAA;AAAA,IAAI,CACrB;AAED,WAAO;AAAA,EACT,CAAC;AACH;AASO,SAAS,oBAId,SACuC;AACvC,SAAO;AACT;"}
|