@tanstack/ai 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/adapter.d.ts +6 -3
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +4 -2
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.d.ts +1 -0
- package/dist/esm/activities/chat/stream/message-updaters.js +3 -1
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.js +12 -4
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/types.d.ts +5 -0
- package/dist/esm/activities/chat/tools/tool-calls.js +1 -3
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/types.d.ts +15 -7
- package/package.json +2 -2
- package/src/activities/chat/adapter.ts +8 -2
- package/src/activities/chat/messages.ts +6 -0
- package/src/activities/chat/stream/message-updaters.ts +8 -0
- package/src/activities/chat/stream/processor.ts +12 -0
- package/src/activities/chat/stream/types.ts +5 -0
- package/src/activities/chat/tools/tool-calls.ts +1 -3
- package/src/types.ts +15 -7
|
@@ -44,8 +44,9 @@ export interface StructuredOutputResult<T = unknown> {
|
|
|
44
44
|
* - TInputModalities: Supported input modalities for this model (already resolved)
|
|
45
45
|
* - TMessageMetadata: Metadata types for content parts (already resolved)
|
|
46
46
|
* - TToolCapabilities: Tuple of tool-kind strings supported by this model, resolved from `supports.tools`
|
|
47
|
+
* - TToolCallMetadata: Metadata type that round-trips with tool calls (e.g. Gemini's `thoughtSignature`)
|
|
47
48
|
*/
|
|
48
|
-
export interface TextAdapter<TModel extends string, TProviderOptions extends Record<string, any>, TInputModalities extends ReadonlyArray<Modality>, TMessageMetadataByModality extends DefaultMessageMetadataByModality, TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string
|
|
49
|
+
export interface TextAdapter<TModel extends string, TProviderOptions extends Record<string, any>, TInputModalities extends ReadonlyArray<Modality>, TMessageMetadataByModality extends DefaultMessageMetadataByModality, TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>, TToolCallMetadata = unknown> {
|
|
49
50
|
/** Discriminator for adapter kind */
|
|
50
51
|
readonly kind: 'text';
|
|
51
52
|
/** Provider name identifier (e.g., 'openai', 'anthropic') */
|
|
@@ -60,6 +61,7 @@ export interface TextAdapter<TModel extends string, TProviderOptions extends Rec
|
|
|
60
61
|
inputModalities: TInputModalities;
|
|
61
62
|
messageMetadataByModality: TMessageMetadataByModality;
|
|
62
63
|
toolCapabilities: TToolCapabilities;
|
|
64
|
+
toolCallMetadata: TToolCallMetadata;
|
|
63
65
|
};
|
|
64
66
|
/**
|
|
65
67
|
* Stream text completions from the model
|
|
@@ -79,14 +81,14 @@ export interface TextAdapter<TModel extends string, TProviderOptions extends Rec
|
|
|
79
81
|
* A TextAdapter with any/unknown type parameters.
|
|
80
82
|
* Useful as a constraint in generic functions and interfaces.
|
|
81
83
|
*/
|
|
82
|
-
export type AnyTextAdapter = TextAdapter<any, any, any, any, any>;
|
|
84
|
+
export type AnyTextAdapter = TextAdapter<any, any, any, any, any, any>;
|
|
83
85
|
/**
|
|
84
86
|
* Abstract base class for text adapters.
|
|
85
87
|
* Extend this class to implement a text adapter for a specific provider.
|
|
86
88
|
*
|
|
87
89
|
* Generic parameters match TextAdapter - all pre-resolved by the provider function.
|
|
88
90
|
*/
|
|
89
|
-
export declare abstract class BaseTextAdapter<TModel extends string, TProviderOptions extends Record<string, any>, TInputModalities extends ReadonlyArray<Modality>, TMessageMetadataByModality extends DefaultMessageMetadataByModality, TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string
|
|
91
|
+
export declare abstract class BaseTextAdapter<TModel extends string, TProviderOptions extends Record<string, any>, TInputModalities extends ReadonlyArray<Modality>, TMessageMetadataByModality extends DefaultMessageMetadataByModality, TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>, TToolCallMetadata = unknown> implements TextAdapter<TModel, TProviderOptions, TInputModalities, TMessageMetadataByModality, TToolCapabilities, TToolCallMetadata> {
|
|
90
92
|
readonly kind: "text";
|
|
91
93
|
abstract readonly name: string;
|
|
92
94
|
readonly model: TModel;
|
|
@@ -95,6 +97,7 @@ export declare abstract class BaseTextAdapter<TModel extends string, TProviderOp
|
|
|
95
97
|
inputModalities: TInputModalities;
|
|
96
98
|
messageMetadataByModality: TMessageMetadataByModality;
|
|
97
99
|
toolCapabilities: TToolCapabilities;
|
|
100
|
+
toolCallMetadata: TToolCallMetadata;
|
|
98
101
|
};
|
|
99
102
|
protected config: TextAdapterConfig;
|
|
100
103
|
constructor(config: TextAdapterConfig | undefined, model: TModel);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sources":["../../../../src/activities/chat/adapter.ts"],"sourcesContent":["import type {\n DefaultMessageMetadataByModality,\n JSONSchema,\n Modality,\n StreamChunk,\n TextOptions,\n} from '../../types'\n\n/**\n * Configuration for adapter instances\n */\nexport interface TextAdapterConfig {\n apiKey?: string\n baseUrl?: string\n timeout?: number\n maxRetries?: number\n headers?: Record<string, string>\n}\n\n/**\n * Options for structured output generation.\n *\n * The internal logger is threaded through `chatOptions.logger` (inherited from\n * `TextOptions`). Adapter implementations must call `logger.request()` before\n * SDK calls, `logger.provider()` for each chunk received, and `logger.errors()`\n * in catch blocks.\n */\nexport interface StructuredOutputOptions<TProviderOptions extends object> {\n /** Text options for the request */\n chatOptions: TextOptions<TProviderOptions>\n /** JSON Schema for structured output - already converted from Zod in the ai layer */\n outputSchema: JSONSchema\n}\n\n/**\n * Result from structured output generation\n */\nexport interface StructuredOutputResult<T = unknown> {\n /** The parsed data conforming to the schema */\n data: T\n /** The raw text response from the model before parsing */\n rawText: string\n}\n\n/**\n * Text adapter interface with pre-resolved generics.\n *\n * An adapter is created by a provider function: `provider('model')` → `adapter`\n * All type resolution happens at the provider call site, not in this interface.\n *\n * Generic parameters:\n * - TModel: The specific model name (e.g., 'gpt-4o')\n * - TProviderOptions: Provider-specific options for this model (already resolved)\n * - TInputModalities: Supported input modalities for this model (already resolved)\n * - TMessageMetadata: Metadata types for content parts (already resolved)\n * - TToolCapabilities: Tuple of tool-kind strings supported by this model, resolved from `supports.tools`\n */\nexport interface TextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n> {\n /** Discriminator for adapter kind */\n readonly kind: 'text'\n /** Provider name identifier (e.g., 'openai', 'anthropic') */\n readonly name: string\n /** The model this adapter is configured for */\n readonly model: TModel\n\n /**\n * @internal Type-only properties for inference. Not assigned at runtime.\n */\n '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n toolCapabilities: TToolCapabilities\n }\n\n /**\n * Stream text completions from the model\n */\n chatStream: (\n options: TextOptions<TProviderOptions>,\n ) => AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * This method uses stream: false and sends the JSON schema to the provider\n * to ensure the response conforms to the expected structure.\n *\n * @param options - Structured output options containing chat options and JSON schema\n * @returns Promise with the raw data (validation is done in the chat function)\n */\n structuredOutput: (\n options: StructuredOutputOptions<TProviderOptions>,\n ) => Promise<StructuredOutputResult<unknown>>\n}\n\n/**\n * A TextAdapter with any/unknown type parameters.\n * Useful as a constraint in generic functions and interfaces.\n */\nexport type AnyTextAdapter = TextAdapter<any, any, any, any, any>\n\n/**\n * Abstract base class for text adapters.\n * Extend this class to implement a text adapter for a specific provider.\n *\n * Generic parameters match TextAdapter - all pre-resolved by the provider function.\n */\nexport abstract class BaseTextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n> implements TextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadataByModality,\n TToolCapabilities\n> {\n readonly kind = 'text' as const\n abstract readonly name: string\n readonly model: TModel\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n toolCapabilities: TToolCapabilities\n }\n\n protected config: TextAdapterConfig\n\n constructor(config: TextAdapterConfig = {}, model: TModel) {\n this.config = config\n this.model = model\n }\n\n abstract chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * Concrete implementations should override this to use provider-specific structured output.\n */\n abstract structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>>\n\n protected generateId(): string {\n return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"adapter.js","sources":["../../../../src/activities/chat/adapter.ts"],"sourcesContent":["import type {\n DefaultMessageMetadataByModality,\n JSONSchema,\n Modality,\n StreamChunk,\n TextOptions,\n} from '../../types'\n\n/**\n * Configuration for adapter instances\n */\nexport interface TextAdapterConfig {\n apiKey?: string\n baseUrl?: string\n timeout?: number\n maxRetries?: number\n headers?: Record<string, string>\n}\n\n/**\n * Options for structured output generation.\n *\n * The internal logger is threaded through `chatOptions.logger` (inherited from\n * `TextOptions`). Adapter implementations must call `logger.request()` before\n * SDK calls, `logger.provider()` for each chunk received, and `logger.errors()`\n * in catch blocks.\n */\nexport interface StructuredOutputOptions<TProviderOptions extends object> {\n /** Text options for the request */\n chatOptions: TextOptions<TProviderOptions>\n /** JSON Schema for structured output - already converted from Zod in the ai layer */\n outputSchema: JSONSchema\n}\n\n/**\n * Result from structured output generation\n */\nexport interface StructuredOutputResult<T = unknown> {\n /** The parsed data conforming to the schema */\n data: T\n /** The raw text response from the model before parsing */\n rawText: string\n}\n\n/**\n * Text adapter interface with pre-resolved generics.\n *\n * An adapter is created by a provider function: `provider('model')` → `adapter`\n * All type resolution happens at the provider call site, not in this interface.\n *\n * Generic parameters:\n * - TModel: The specific model name (e.g., 'gpt-4o')\n * - TProviderOptions: Provider-specific options for this model (already resolved)\n * - TInputModalities: Supported input modalities for this model (already resolved)\n * - TMessageMetadata: Metadata types for content parts (already resolved)\n * - TToolCapabilities: Tuple of tool-kind strings supported by this model, resolved from `supports.tools`\n * - TToolCallMetadata: Metadata type that round-trips with tool calls (e.g. Gemini's `thoughtSignature`)\n */\nexport interface TextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n TToolCallMetadata = unknown,\n> {\n /** Discriminator for adapter kind */\n readonly kind: 'text'\n /** Provider name identifier (e.g., 'openai', 'anthropic') */\n readonly name: string\n /** The model this adapter is configured for */\n readonly model: TModel\n\n /**\n * @internal Type-only properties for inference. Not assigned at runtime.\n */\n '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n toolCapabilities: TToolCapabilities\n toolCallMetadata: TToolCallMetadata\n }\n\n /**\n * Stream text completions from the model\n */\n chatStream: (\n options: TextOptions<TProviderOptions>,\n ) => AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * This method uses stream: false and sends the JSON schema to the provider\n * to ensure the response conforms to the expected structure.\n *\n * @param options - Structured output options containing chat options and JSON schema\n * @returns Promise with the raw data (validation is done in the chat function)\n */\n structuredOutput: (\n options: StructuredOutputOptions<TProviderOptions>,\n ) => Promise<StructuredOutputResult<unknown>>\n}\n\n/**\n * A TextAdapter with any/unknown type parameters.\n * Useful as a constraint in generic functions and interfaces.\n */\nexport type AnyTextAdapter = TextAdapter<any, any, any, any, any, any>\n\n/**\n * Abstract base class for text adapters.\n * Extend this class to implement a text adapter for a specific provider.\n *\n * Generic parameters match TextAdapter - all pre-resolved by the provider function.\n */\nexport abstract class BaseTextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n TToolCallMetadata = unknown,\n> implements TextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadataByModality,\n TToolCapabilities,\n TToolCallMetadata\n> {\n readonly kind = 'text' as const\n abstract readonly name: string\n readonly model: TModel\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n toolCapabilities: TToolCapabilities\n toolCallMetadata: TToolCallMetadata\n }\n\n protected config: TextAdapterConfig\n\n constructor(config: TextAdapterConfig = {}, model: TModel) {\n this.config = config\n this.model = model\n }\n\n abstract chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * Concrete implementations should override this to use provider-specific structured output.\n */\n abstract structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>>\n\n protected generateId(): string {\n return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n}\n"],"names":[],"mappings":"AAoHO,MAAe,gBAcpB;AAAA,EAgBA,YAAY,SAA4B,CAAA,GAAI,OAAe;AAf3D,SAAS,OAAO;AAgBd,SAAK,SAAS;AACd,SAAK,QAAQ;AAAA,EACf;AAAA,EAcU,aAAqB;AAC7B,WAAO,GAAG,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AAAA,EAC9E;AACF;"}
|
|
@@ -90,7 +90,8 @@ function buildAssistantMessages(uiMessage) {
|
|
|
90
90
|
function: {
|
|
91
91
|
name: part.name,
|
|
92
92
|
arguments: part.arguments
|
|
93
|
-
}
|
|
93
|
+
},
|
|
94
|
+
...part.metadata !== void 0 && { metadata: part.metadata }
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
break;
|
|
@@ -187,8 +188,9 @@ function modelMessageToUIMessage(modelMessage, id) {
|
|
|
187
188
|
id: toolCall.id,
|
|
188
189
|
name: toolCall.function.name,
|
|
189
190
|
arguments: toolCall.function.arguments,
|
|
190
|
-
state: "input-complete"
|
|
191
|
+
state: "input-complete",
|
|
191
192
|
// Model messages have complete arguments
|
|
193
|
+
...toolCall.metadata !== void 0 && { metadata: toolCall.metadata }
|
|
192
194
|
});
|
|
193
195
|
}
|
|
194
196
|
}
|
|
@@ -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 let pendingThinking: Array<{ content: string; signature?: string }> = []\n\n // Track emitted tool result IDs to avoid duplicates.\n // A tool call can have BOTH an explicit tool-result part AND an output\n // field on the tool-call part. We only want one per tool call ID.\n const emittedToolResultIds = new Set<string>()\n\n function flushSegment(): void {\n const content = collapseContentParts(current.contentParts)\n const hasContent = content !== null\n const hasToolCalls = current.toolCalls.length > 0\n\n if (hasContent || hasToolCalls) {\n messageList.push({\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\n })\n pendingThinking = []\n }\n current = createSegment()\n }\n\n for (const part of uiMessage.parts) {\n switch (part.type) {\n case 'text':\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n current.contentParts.push(part)\n break\n\n case 'tool-call':\n if (isToolCallIncluded(part)) {\n current.toolCalls.push({\n id: part.id,\n type: 'function' as const,\n function: {\n name: part.name,\n arguments: part.arguments,\n },\n })\n }\n break\n\n case 'tool-result':\n // Flush the current assistant segment before emitting the tool result\n flushSegment()\n\n // Emit the tool result\n if (\n (part.state === 'complete' || part.state === 'error') &&\n !emittedToolResultIds.has(part.toolCallId)\n ) {\n messageList.push({\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n })\n emittedToolResultIds.add(part.toolCallId)\n }\n break\n\n case 'thinking':\n if (part.content) {\n pendingThinking.push({\n content: part.content,\n ...(part.signature && { signature: part.signature }),\n })\n }\n break\n\n default:\n break\n }\n }\n\n // Flush any remaining accumulated content\n flushSegment()\n\n // Emit tool results from client tool-call parts with output or approval,\n // but only if not already covered by an explicit tool-result part above.\n // These are appended at the end since they don't have explicit tool-result\n // parts in the parts array to trigger inline emission.\n for (const part of uiMessage.parts) {\n if (part.type !== 'tool-call') continue\n\n // Output takes priority — if the tool has already produced a result,\n // emit the concrete output regardless of approval metadata.\n if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {\n messageList.push({\n role: 'tool',\n content: JSON.stringify(part.output),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response without output — emit approval status for iteration tracking\n if (\n part.output === undefined &&\n part.state === 'approval-responded' &&\n part.approval?.approved !== undefined &&\n !emittedToolResultIds.has(part.id)\n ) {\n const approved = part.approval.approved\n messageList.push({\n role: 'tool',\n content: JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n }),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n }\n\n // If no messages were produced (e.g., empty parts), emit a minimal assistant message\n if (messageList.length === 0) {\n messageList.push({\n role: 'assistant',\n content: null,\n })\n }\n\n return messageList\n}\n\n/**\n * Convert a ModelMessage to UIMessage\n *\n * This conversion creates a parts-based structure:\n * - content field → TextPart\n * - toolCalls array → ToolCallPart[]\n * - role=\"tool\" messages should be converted separately and merged\n *\n * @param modelMessage - The ModelMessage to convert\n * @param id - Optional ID for the UIMessage (generated if not provided)\n * @returns A UIMessage with parts\n */\nexport function modelMessageToUIMessage(\n modelMessage: ModelMessage,\n id?: string,\n): UIMessage {\n const parts: Array<MessagePart> = []\n\n if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {\n for (const thinking of modelMessage.thinking) {\n if (!thinking.content) continue\n parts.push({\n type: 'thinking',\n content: thinking.content,\n ...(thinking.signature && { signature: thinking.signature }),\n })\n }\n }\n\n // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n parts.push({\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n state: 'input-complete', // Model messages have complete arguments\n })\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 /** Provider-specific metadata that round-trips with the tool call.\n * Untyped at this framework layer; adapters narrow it via their\n * `TToolCallMetadata` generic. */\n metadata?: unknown\n }>\n}\n\nfunction createSegment(): AssistantSegment {\n return { contentParts: [], toolCalls: [] }\n}\n\nfunction isToolCallIncluded(part: ToolCallPart): boolean {\n return (\n part.state === 'input-complete' ||\n part.state === 'approval-responded' ||\n part.output !== undefined\n )\n}\n\n/**\n * Build ModelMessages for an assistant UIMessage, preserving the\n * sequential interleaving of text, tool calls, and tool results.\n *\n * Walks parts in order. Text and tool-call parts accumulate into the\n * current \"segment\". When a tool-result part is encountered, the\n * current segment is flushed as an assistant message, then the tool\n * result is emitted as a tool message.\n */\nfunction buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {\n const messageList: Array<ModelMessage> = []\n let current = createSegment()\n let pendingThinking: Array<{ content: string; signature?: string }> = []\n\n // Track emitted tool result IDs to avoid duplicates.\n // A tool call can have BOTH an explicit tool-result part AND an output\n // field on the tool-call part. We only want one per tool call ID.\n const emittedToolResultIds = new Set<string>()\n\n function flushSegment(): void {\n const content = collapseContentParts(current.contentParts)\n const hasContent = content !== null\n const hasToolCalls = current.toolCalls.length > 0\n\n if (hasContent || hasToolCalls) {\n messageList.push({\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\n })\n pendingThinking = []\n }\n current = createSegment()\n }\n\n for (const part of uiMessage.parts) {\n switch (part.type) {\n case 'text':\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n current.contentParts.push(part)\n break\n\n case 'tool-call':\n if (isToolCallIncluded(part)) {\n current.toolCalls.push({\n id: part.id,\n type: 'function' as const,\n function: {\n name: part.name,\n arguments: part.arguments,\n },\n ...(part.metadata !== undefined && { metadata: part.metadata }),\n })\n }\n break\n\n case 'tool-result':\n // Flush the current assistant segment before emitting the tool result\n flushSegment()\n\n // Emit the tool result\n if (\n (part.state === 'complete' || part.state === 'error') &&\n !emittedToolResultIds.has(part.toolCallId)\n ) {\n messageList.push({\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n })\n emittedToolResultIds.add(part.toolCallId)\n }\n break\n\n case 'thinking':\n if (part.content) {\n pendingThinking.push({\n content: part.content,\n ...(part.signature && { signature: part.signature }),\n })\n }\n break\n\n default:\n break\n }\n }\n\n // Flush any remaining accumulated content\n flushSegment()\n\n // Emit tool results from client tool-call parts with output or approval,\n // but only if not already covered by an explicit tool-result part above.\n // These are appended at the end since they don't have explicit tool-result\n // parts in the parts array to trigger inline emission.\n for (const part of uiMessage.parts) {\n if (part.type !== 'tool-call') continue\n\n // Output takes priority — if the tool has already produced a result,\n // emit the concrete output regardless of approval metadata.\n if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {\n messageList.push({\n role: 'tool',\n content: JSON.stringify(part.output),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response without output — emit approval status for iteration tracking\n if (\n part.output === undefined &&\n part.state === 'approval-responded' &&\n part.approval?.approved !== undefined &&\n !emittedToolResultIds.has(part.id)\n ) {\n const approved = part.approval.approved\n messageList.push({\n role: 'tool',\n content: JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n }),\n toolCallId: part.id,\n })\n emittedToolResultIds.add(part.id)\n }\n }\n\n // If no messages were produced (e.g., empty parts), emit a minimal assistant message\n if (messageList.length === 0) {\n messageList.push({\n role: 'assistant',\n content: null,\n })\n }\n\n return messageList\n}\n\n/**\n * Convert a ModelMessage to UIMessage\n *\n * This conversion creates a parts-based structure:\n * - content field → TextPart\n * - toolCalls array → ToolCallPart[]\n * - role=\"tool\" messages should be converted separately and merged\n *\n * @param modelMessage - The ModelMessage to convert\n * @param id - Optional ID for the UIMessage (generated if not provided)\n * @returns A UIMessage with parts\n */\nexport function modelMessageToUIMessage(\n modelMessage: ModelMessage,\n id?: string,\n): UIMessage {\n const parts: Array<MessagePart> = []\n\n if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {\n for (const thinking of modelMessage.thinking) {\n if (!thinking.content) continue\n parts.push({\n type: 'thinking',\n content: thinking.content,\n ...(thinking.signature && { signature: thinking.signature }),\n })\n }\n }\n\n // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n parts.push({\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n state: 'input-complete', // Model messages have complete arguments\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n }\n }\n\n return {\n id: id || generateMessageId(),\n role: modelMessage.role === 'tool' ? 'assistant' : modelMessage.role,\n parts,\n }\n}\n\n/**\n * Convert an array of ModelMessages to UIMessages\n *\n * This handles merging tool result messages with their corresponding assistant messages\n *\n * @param modelMessages - Array of ModelMessages to convert\n * @returns Array of UIMessages\n */\nexport function modelMessagesToUIMessages(\n modelMessages: Array<ModelMessage>,\n): Array<UIMessage> {\n const uiMessages: Array<UIMessage> = []\n let currentAssistantMessage: UIMessage | null = null\n\n for (const msg of modelMessages) {\n if (msg.role === 'tool') {\n // Tool result - merge into the last assistant message if possible\n if (\n currentAssistantMessage &&\n currentAssistantMessage.role === 'assistant'\n ) {\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId!,\n content: getTextContent(msg.content),\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message\n const uiMessage = modelMessageToUIMessage(msg)\n uiMessages.push(uiMessage)\n\n // Track assistant messages for potential tool result merging\n if (msg.role === 'assistant') {\n currentAssistantMessage = uiMessage\n } else {\n currentAssistantMessage = null\n }\n }\n }\n\n return uiMessages\n}\n\n/**\n * Normalize a message (UIMessage or ModelMessage) to a UIMessage\n * Ensures the message has an ID and createdAt timestamp\n *\n * @param message - Either a UIMessage or ModelMessage\n * @param generateId - Function to generate a message ID if needed\n * @returns A UIMessage with guaranteed id and createdAt\n */\nexport function normalizeToUIMessage(\n message: UIMessage | ModelMessage,\n generateId: () => string,\n): UIMessage {\n if ('parts' in message) {\n // Already a UIMessage\n return {\n ...message,\n id: message.id || generateId(),\n createdAt: message.createdAt || new Date(),\n }\n } else {\n // ModelMessage - convert to UIMessage\n return {\n ...modelMessageToUIMessage(message, generateId()),\n createdAt: new Date(),\n }\n }\n}\n\n/**\n * Generate a unique message ID\n */\nexport function generateMessageId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"names":[],"mappings":"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;AAgBA,SAAS,gBAAkC;AACzC,SAAO,EAAE,cAAc,IAAI,WAAW,CAAA,EAAC;AACzC;AAEA,SAAS,mBAAmB,MAA6B;AACvD,SACE,KAAK,UAAU,oBACf,KAAK,UAAU,wBACf,KAAK,WAAW;AAEpB;AAWA,SAAS,uBAAuB,WAA2C;AACzE,QAAM,cAAmC,CAAA;AACzC,MAAI,UAAU,cAAA;AACd,MAAI,kBAAkE,CAAA;AAKtE,QAAM,2CAA2B,IAAA;AAEjC,WAAS,eAAqB;AAC5B,UAAM,UAAU,qBAAqB,QAAQ,YAAY;AACzD,UAAM,aAAa,YAAY;AAC/B,UAAM,eAAe,QAAQ,UAAU,SAAS;AAEhD,QAAI,cAAc,cAAc;AAC9B,kBAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN;AAAA,QACA,GAAI,gBAAgB,EAAE,WAAW,QAAQ,UAAA;AAAA,QACzC,GAAI,gBAAgB,SAAS,KAAK,EAAE,UAAU,gBAAA;AAAA,MAAgB,CAC/D;AACD,wBAAkB,CAAA;AAAA,IACpB;AACA,cAAU,cAAA;AAAA,EACZ;AAEA,aAAW,QAAQ,UAAU,OAAO;AAClC,YAAQ,KAAK,MAAA;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,gBAAQ,aAAa,KAAK,IAAI;AAC9B;AAAA,MAEF,KAAK;AACH,YAAI,mBAAmB,IAAI,GAAG;AAC5B,kBAAQ,UAAU,KAAK;AAAA,YACrB,IAAI,KAAK;AAAA,YACT,MAAM;AAAA,YACN,UAAU;AAAA,cACR,MAAM,KAAK;AAAA,cACX,WAAW,KAAK;AAAA,YAAA;AAAA,YAElB,GAAI,KAAK,aAAa,UAAa,EAAE,UAAU,KAAK,SAAA;AAAA,UAAS,CAC9D;AAAA,QACH;AACA;AAAA,MAEF,KAAK;AAEH,qBAAA;AAGA,aACG,KAAK,UAAU,cAAc,KAAK,UAAU,YAC7C,CAAC,qBAAqB,IAAI,KAAK,UAAU,GACzC;AACA,sBAAY,KAAK;AAAA,YACf,MAAM;AAAA,YACN,SAAS,KAAK;AAAA,YACd,YAAY,KAAK;AAAA,UAAA,CAClB;AACD,+BAAqB,IAAI,KAAK,UAAU;AAAA,QAC1C;AACA;AAAA,MAEF,KAAK;AACH,YAAI,KAAK,SAAS;AAChB,0BAAgB,KAAK;AAAA,YACnB,SAAS,KAAK;AAAA,YACd,GAAI,KAAK,aAAa,EAAE,WAAW,KAAK,UAAA;AAAA,UAAU,CACnD;AAAA,QACH;AACA;AAAA,IAGA;AAAA,EAEN;AAGA,eAAA;AAMA,aAAW,QAAQ,UAAU,OAAO;AAClC,QAAI,KAAK,SAAS,YAAa;AAI/B,QAAI,KAAK,WAAW,UAAa,CAAC,qBAAqB,IAAI,KAAK,EAAE,GAAG;AACnE,kBAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS,KAAK,UAAU,KAAK,MAAM;AAAA,QACnC,YAAY,KAAK;AAAA,MAAA,CAClB;AACD,2BAAqB,IAAI,KAAK,EAAE;AAAA,IAClC;AAGA,QACE,KAAK,WAAW,UAChB,KAAK,UAAU,wBACf,KAAK,UAAU,aAAa,UAC5B,CAAC,qBAAqB,IAAI,KAAK,EAAE,GACjC;AACA,YAAM,WAAW,KAAK,SAAS;AAC/B,kBAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS,KAAK,UAAU;AAAA,UACtB;AAAA,UACA,GAAI,YAAY,EAAE,kBAAkB,KAAA;AAAA,UACpC,SAAS,WACL,8BACA;AAAA,QAAA,CACL;AAAA,QACD,YAAY,KAAK;AAAA,MAAA,CAClB;AACD,2BAAqB,IAAI,KAAK,EAAE;AAAA,IAClC;AAAA,EACF;AAGA,MAAI,YAAY,WAAW,GAAG;AAC5B,gBAAY,KAAK;AAAA,MACf,MAAM;AAAA,MACN,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAEA,SAAO;AACT;AAcO,SAAS,wBACd,cACA,IACW;AACX,QAAM,QAA4B,CAAA;AAElC,MAAI,aAAa,SAAS,eAAe,aAAa,UAAU,QAAQ;AACtE,eAAW,YAAY,aAAa,UAAU;AAC5C,UAAI,CAAC,SAAS,QAAS;AACvB,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,SAAS,SAAS;AAAA,QAClB,GAAI,SAAS,aAAa,EAAE,WAAW,SAAS,UAAA;AAAA,MAAU,CAC3D;AAAA,IACH;AAAA,EACF;AAIA,MAAI,aAAa,SAAS,UAAU,aAAa,YAAY;AAC3D,UAAM,KAAK;AAAA,MACT,MAAM;AAAA,MACN,YAAY,aAAa;AAAA,MACzB,SAAS,eAAe,aAAa,OAAO;AAAA,MAC5C,OAAO;AAAA,IAAA,CACR;AAAA,EACH,WAAW,MAAM,QAAQ,aAAa,OAAO,GAAG;AAE9C,eAAW,QAAQ,aAAa,SAAS;AACvC,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF,OAAO;AAEL,UAAM,cAAc,eAAe,aAAa,OAAO;AACvD,QAAI,aAAa;AACf,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,SAAS;AAAA,MAAA,CACV;AAAA,IACH;AAAA,EACF;AAGA,MAAI,aAAa,aAAa,aAAa,UAAU,SAAS,GAAG;AAC/D,eAAW,YAAY,aAAa,WAAW;AAC7C,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,IAAI,SAAS;AAAA,QACb,MAAM,SAAS,SAAS;AAAA,QACxB,WAAW,SAAS,SAAS;AAAA,QAC7B,OAAO;AAAA;AAAA,QACP,GAAI,SAAS,aAAa,UAAa,EAAE,UAAU,SAAS,SAAA;AAAA,MAAS,CACtE;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,MAAM,kBAAA;AAAA,IACV,MAAM,aAAa,SAAS,SAAS,cAAc,aAAa;AAAA,IAChE;AAAA,EAAA;AAEJ;AAUO,SAAS,0BACd,eACkB;AAClB,QAAM,aAA+B,CAAA;AACrC,MAAI,0BAA4C;AAEhD,aAAW,OAAO,eAAe;AAC/B,QAAI,IAAI,SAAS,QAAQ;AAEvB,UACE,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;"}
|
|
@@ -15,6 +15,7 @@ export declare function updateToolCallPart(messages: Array<UIMessage>, messageId
|
|
|
15
15
|
name: string;
|
|
16
16
|
arguments: string;
|
|
17
17
|
state: ToolCallState;
|
|
18
|
+
metadata?: Record<string, unknown>;
|
|
18
19
|
}): Array<UIMessage>;
|
|
19
20
|
/**
|
|
20
21
|
* Update or add a tool result part to a message.
|
|
@@ -22,6 +22,7 @@ function updateToolCallPart(messages, messageId, toolCall) {
|
|
|
22
22
|
const existing = parts.find(
|
|
23
23
|
(p) => p.type === "tool-call" && p.id === toolCall.id
|
|
24
24
|
);
|
|
25
|
+
const metadata = toolCall.metadata ?? existing?.metadata;
|
|
25
26
|
const toolCallPart = {
|
|
26
27
|
type: "tool-call",
|
|
27
28
|
id: toolCall.id,
|
|
@@ -30,7 +31,8 @@ function updateToolCallPart(messages, messageId, toolCall) {
|
|
|
30
31
|
state: toolCall.state,
|
|
31
32
|
// Carry forward approval and output from the existing part
|
|
32
33
|
...existing?.approval && { approval: { ...existing.approval } },
|
|
33
|
-
...existing?.output !== void 0 && { output: existing.output }
|
|
34
|
+
...existing?.output !== void 0 && { output: existing.output },
|
|
35
|
+
...metadata !== void 0 && { metadata }
|
|
34
36
|
};
|
|
35
37
|
if (existing) {
|
|
36
38
|
parts[parts.indexOf(existing)] = toolCallPart;
|
|
@@ -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 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, keyed by stepId.\n * Each distinct stepId produces its own ThinkingPart.\n */\nexport function updateThinkingPart(\n messages: Array<UIMessage>,\n messageId: string,\n stepId: string,\n content: string,\n signature?: 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(\n (p) => p.type === 'thinking' && p.stepId === stepId,\n )\n\n const thinkingPart: ThinkingPart = {\n type: 'thinking',\n content,\n stepId,\n ...(signature && { signature }),\n }\n\n if (thinkingPartIndex >= 0) {\n // Update existing thinking part for this step\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 metadata?: Record<string, unknown>\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 // Carry forward metadata from either the new toolCall or the existing\n // part. Once the adapter has emitted metadata for a tool call (e.g.\n // Gemini's thoughtSignature on TOOL_CALL_START) we must not lose it on\n // subsequent updates that don't re-supply it.\n const metadata = toolCall.metadata ?? existing?.metadata\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 ...(metadata !== undefined && { metadata }),\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, keyed by stepId.\n * Each distinct stepId produces its own ThinkingPart.\n */\nexport function updateThinkingPart(\n messages: Array<UIMessage>,\n messageId: string,\n stepId: string,\n content: string,\n signature?: 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(\n (p) => p.type === 'thinking' && p.stepId === stepId,\n )\n\n const thinkingPart: ThinkingPart = {\n type: 'thinking',\n content,\n stepId,\n ...(signature && { signature }),\n }\n\n if (thinkingPartIndex >= 0) {\n // Update existing thinking part for this step\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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"}
|
|
@@ -595,13 +595,15 @@ class StreamProcessor {
|
|
|
595
595
|
if (!existingToolCall) {
|
|
596
596
|
const initialState = "awaiting-input";
|
|
597
597
|
const toolName = chunk.toolCallName ?? chunk.toolName;
|
|
598
|
+
const chunkMetadata = chunk.metadata;
|
|
598
599
|
const newToolCall = {
|
|
599
600
|
id: chunk.toolCallId,
|
|
600
601
|
name: toolName,
|
|
601
602
|
arguments: "",
|
|
602
603
|
state: initialState,
|
|
603
604
|
parsedArguments: void 0,
|
|
604
|
-
index: chunk.index ?? state.toolCalls.size
|
|
605
|
+
index: chunk.index ?? state.toolCalls.size,
|
|
606
|
+
...chunkMetadata !== void 0 && { metadata: chunkMetadata }
|
|
605
607
|
};
|
|
606
608
|
state.toolCalls.set(toolCallId, newToolCall);
|
|
607
609
|
state.toolCallOrder.push(toolCallId);
|
|
@@ -610,7 +612,8 @@ class StreamProcessor {
|
|
|
610
612
|
id: chunk.toolCallId,
|
|
611
613
|
name: toolName,
|
|
612
614
|
arguments: "",
|
|
613
|
-
state: initialState
|
|
615
|
+
state: initialState,
|
|
616
|
+
...chunkMetadata !== void 0 && { metadata: chunkMetadata }
|
|
614
617
|
});
|
|
615
618
|
this.emitMessagesChange();
|
|
616
619
|
this.events.onToolCallStateChange?.(
|
|
@@ -1010,7 +1013,8 @@ class StreamProcessor {
|
|
|
1010
1013
|
id: toolCall.id,
|
|
1011
1014
|
name: toolCall.name,
|
|
1012
1015
|
arguments: toolCall.arguments,
|
|
1013
|
-
state: "input-complete"
|
|
1016
|
+
state: "input-complete",
|
|
1017
|
+
...toolCall.metadata !== void 0 && { metadata: toolCall.metadata }
|
|
1014
1018
|
});
|
|
1015
1019
|
this.emitMessagesChange();
|
|
1016
1020
|
this.events.onToolCallStateChange?.(
|
|
@@ -1099,7 +1103,11 @@ class StreamProcessor {
|
|
|
1099
1103
|
function: {
|
|
1100
1104
|
name: tc.name,
|
|
1101
1105
|
arguments: tc.arguments
|
|
1102
|
-
}
|
|
1106
|
+
},
|
|
1107
|
+
// Preserve provider metadata (e.g. Gemini thoughtSignature) on
|
|
1108
|
+
// ProcessorResult.toolCalls so callers using process()/getResult()
|
|
1109
|
+
// get the same round-trip support as the streaming UI path.
|
|
1110
|
+
...tc.metadata !== void 0 && { metadata: tc.metadata }
|
|
1103
1111
|
});
|
|
1104
1112
|
}
|
|
1105
1113
|
}
|