@tanstack/ai 0.28.0 → 0.31.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 +10 -0
- package/dist/esm/activities/chat/adapter.js +1 -0
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +3 -2
- package/dist/esm/activities/chat/index.js +23 -11
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +1 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.d.ts +46 -0
- package/dist/esm/activities/chat/middleware/builder.js +17 -0
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -0
- package/dist/esm/activities/chat/middleware/capabilities.d.ts +93 -0
- package/dist/esm/activities/chat/middleware/capabilities.js +45 -0
- package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -0
- package/dist/esm/activities/chat/middleware/compose.d.ts +10 -0
- package/dist/esm/activities/chat/middleware/compose.js +47 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.d.ts +20 -0
- package/dist/esm/activities/chat/middleware/define.js +7 -0
- package/dist/esm/activities/chat/middleware/define.js.map +1 -0
- package/dist/esm/activities/chat/middleware/index.d.ts +8 -0
- package/dist/esm/activities/chat/middleware/types.d.ts +51 -0
- package/dist/esm/activities/chat/middleware/validate.d.ts +19 -0
- package/dist/esm/activities/chat/middleware/validate.js +30 -0
- package/dist/esm/activities/chat/middleware/validate.js.map +1 -0
- package/dist/esm/activities/chat/stream/message-updaters.js +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +6 -0
- package/dist/esm/activities/chat/stream/processor.js +18 -3
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +2 -1
- package/dist/esm/activities/generateVideo/index.js +12 -2
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger/console-logger.d.ts +18 -0
- package/dist/esm/logger/console-logger.js +64 -8
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/types.d.ts +4 -4
- package/dist/esm/middlewares/otel.js +40 -12
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +1 -3
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/types.d.ts +7 -1
- package/package.json +10 -2
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -2
- package/skills/ai-core/media-generation/SKILL.md +29 -1
- package/src/activities/chat/adapter.ts +11 -0
- package/src/activities/chat/index.ts +34 -14
- package/src/activities/chat/messages.ts +1 -0
- package/src/activities/chat/middleware/builder.ts +109 -0
- package/src/activities/chat/middleware/capabilities.ts +162 -0
- package/src/activities/chat/middleware/compose.ts +51 -0
- package/src/activities/chat/middleware/define.ts +34 -0
- package/src/activities/chat/middleware/index.ts +20 -0
- package/src/activities/chat/middleware/types.ts +60 -0
- package/src/activities/chat/middleware/validate.ts +55 -0
- package/src/activities/chat/stream/message-updaters.ts +1 -1
- package/src/activities/chat/stream/processor.ts +30 -3
- package/src/activities/generateVideo/index.ts +12 -0
- package/src/index.ts +14 -0
- package/src/logger/console-logger.ts +112 -17
- package/src/logger/types.ts +4 -4
- package/src/middlewares/otel.ts +57 -12
- package/src/realtime/index.ts +1 -3
- package/src/types.ts +7 -0
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
export type { ChatMiddleware, ChatMiddlewareContext, ChatMiddlewarePhase, ChatMiddlewareConfig, StructuredOutputMiddlewareConfig, ToolCallHookContext, BeforeToolCallDecision, AfterToolCallInfo, IterationInfo, ToolPhaseCompleteInfo, UsageInfo, FinishInfo, AbortInfo, ErrorInfo, } from './types.js';
|
|
2
2
|
export { MiddlewareRunner } from './compose.js';
|
|
3
|
+
export { createCapability, CapabilityRegistry } from './capabilities.js';
|
|
4
|
+
export type { Capability, CapabilityHandle, CapabilityContext, CapabilityGetter, CapabilityProvider, CapabilityGetOptions, } from './capabilities.js';
|
|
5
|
+
export { defineChatMiddleware } from './define.js';
|
|
6
|
+
export type { DefinedChatMiddleware } from './define.js';
|
|
7
|
+
export { createChatMiddleware } from './builder.js';
|
|
8
|
+
export type { ChatMiddlewareBuilder, MissingCapabilities, NamesOf, } from './builder.js';
|
|
9
|
+
export { validateCapabilities } from './validate.js';
|
|
10
|
+
export type { AnyChatMiddleware } from './types.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { JSONSchema, ModelMessage, StreamChunk, TokenUsage, Tool, ToolCall } from '../../../types.js';
|
|
2
2
|
import { SystemPrompt } from '../../../system-prompts.js';
|
|
3
|
+
import { Capability, CapabilityHandle, CapabilityRegistry } from './capabilities.js';
|
|
3
4
|
/**
|
|
4
5
|
* Phase of the chat middleware lifecycle.
|
|
5
6
|
* - 'init': Initial config transform before the chat engine starts
|
|
@@ -81,6 +82,28 @@ export interface ChatMiddlewareContext<TContext = unknown> {
|
|
|
81
82
|
messages: ReadonlyArray<ModelMessage>;
|
|
82
83
|
/** Generate a unique ID with the given prefix */
|
|
83
84
|
createId: (prefix: string) => string;
|
|
85
|
+
/**
|
|
86
|
+
* Capability bookkeeping for this request. Populated by middleware `setup`
|
|
87
|
+
* hooks (via `provide` accessors) and read by later middleware (via `get`
|
|
88
|
+
* accessors). Prefer the accessors returned by `createCapability` over using
|
|
89
|
+
* this directly. Orthogonal to `context` (the user runtime context).
|
|
90
|
+
*/
|
|
91
|
+
capabilities: CapabilityRegistry;
|
|
92
|
+
/**
|
|
93
|
+
* Read a provided capability by its handle. Equivalent to the handle's own
|
|
94
|
+
* `get` accessor (`getX(ctx)`); throws if the capability was never provided.
|
|
95
|
+
*/
|
|
96
|
+
get: <TValue>(capability: Capability<TValue>) => TValue;
|
|
97
|
+
/**
|
|
98
|
+
* Read a capability by its handle, returning `undefined` if it was never
|
|
99
|
+
* provided (never throws).
|
|
100
|
+
*/
|
|
101
|
+
getOptional: <TValue>(capability: Capability<TValue>) => TValue | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Provide a capability value. Equivalent to the handle's own `provide`
|
|
104
|
+
* accessor (`provideX(ctx, value)`). Typically called from `setup`.
|
|
105
|
+
*/
|
|
106
|
+
provide: <TValue>(capability: Capability<TValue>, value: TValue) => void;
|
|
84
107
|
}
|
|
85
108
|
/**
|
|
86
109
|
* Chat configuration that middleware can observe or transform.
|
|
@@ -270,6 +293,32 @@ export interface ErrorInfo {
|
|
|
270
293
|
export interface ChatMiddleware<TContext = unknown> {
|
|
271
294
|
/** Optional name for debugging and identification */
|
|
272
295
|
name?: string;
|
|
296
|
+
/**
|
|
297
|
+
* Capabilities this middleware requires. `chat()` validates that some
|
|
298
|
+
* middleware (or the adapter) provides each one; unsatisfied requirements are
|
|
299
|
+
* a compile-time error (array coverage / builder) and a runtime error before
|
|
300
|
+
* the adapter runs.
|
|
301
|
+
*/
|
|
302
|
+
requires?: ReadonlyArray<CapabilityHandle>;
|
|
303
|
+
/**
|
|
304
|
+
* Capabilities this middleware provides. Each declared capability MUST be
|
|
305
|
+
* provided (via its `provide` accessor) inside `setup`, or `chat()` throws
|
|
306
|
+
* after the setup phase.
|
|
307
|
+
*/
|
|
308
|
+
provides?: ReadonlyArray<CapabilityHandle>;
|
|
309
|
+
/**
|
|
310
|
+
* Capabilities this middleware uses if present but does not require.
|
|
311
|
+
* Non-gating: never causes a validation error. Read with
|
|
312
|
+
* `getX(ctx, { optional: true })`.
|
|
313
|
+
*/
|
|
314
|
+
optionalRequires?: ReadonlyArray<CapabilityHandle>;
|
|
315
|
+
/**
|
|
316
|
+
* Provisioning hook. Runs FIRST — before `onConfig` (init) — across all
|
|
317
|
+
* middleware in array order. Use it to call `provide` accessors so later
|
|
318
|
+
* middleware (`onConfig` onward) can consume the capabilities. Receives the
|
|
319
|
+
* stable context; does NOT receive the mutable config.
|
|
320
|
+
*/
|
|
321
|
+
setup?: (ctx: ChatMiddlewareContext<TContext>) => void | Promise<void>;
|
|
273
322
|
/**
|
|
274
323
|
* Called to observe or transform the chat configuration.
|
|
275
324
|
* Called at init and at the beginning of each agent iteration.
|
|
@@ -345,3 +394,5 @@ export interface ChatMiddleware<TContext = unknown> {
|
|
|
345
394
|
*/
|
|
346
395
|
onError?: (ctx: ChatMiddlewareContext<TContext>, info: ErrorInfo) => void | Promise<void>;
|
|
347
396
|
}
|
|
397
|
+
/** A `ChatMiddleware` with a permissive context — for use as a constraint. */
|
|
398
|
+
export type AnyChatMiddleware = ChatMiddleware<any>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CapabilityHandle } from './capabilities.js';
|
|
2
|
+
import { AnyChatMiddleware } from './types.js';
|
|
3
|
+
/** Minimal adapter shape needed for capability validation. */
|
|
4
|
+
interface CapabilityRequiringAdapter {
|
|
5
|
+
name: string;
|
|
6
|
+
requires?: ReadonlyArray<CapabilityHandle>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Runtime validation: every required capability (from middleware `requires` and
|
|
10
|
+
* the adapter's `requires`) must be provided by some middleware's `provides`.
|
|
11
|
+
* `optionalRequires` is never gating. Throws a clear error otherwise.
|
|
12
|
+
*
|
|
13
|
+
* Presence only — ORDER is not validated here (a provider may appear after its
|
|
14
|
+
* consumer in the array and still pass). Use the `createChatMiddleware()`
|
|
15
|
+
* builder for compile-time order enforcement; at runtime, a consumer that reads
|
|
16
|
+
* a not-yet-provided capability during `setup` fails loud via its getter.
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateCapabilities(middlewares: ReadonlyArray<AnyChatMiddleware>, adapter: CapabilityRequiringAdapter): void;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function validateCapabilities(middlewares, adapter) {
|
|
2
|
+
const provided = /* @__PURE__ */ new Set();
|
|
3
|
+
for (const mw of middlewares) {
|
|
4
|
+
for (const handle of mw.provides ?? []) provided.add(handle);
|
|
5
|
+
}
|
|
6
|
+
const providedNames = () => {
|
|
7
|
+
const names = [...provided].map((h) => h.capabilityName);
|
|
8
|
+
return names.length ? names.join(", ") : "none";
|
|
9
|
+
};
|
|
10
|
+
for (const handle of adapter.requires ?? []) {
|
|
11
|
+
if (!provided.has(handle)) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`Adapter "${adapter.name}" requires capability "${handle.capabilityName}". Provided capabilities: ${providedNames()}. Add a middleware that provides "${handle.capabilityName}".`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
for (const mw of middlewares) {
|
|
18
|
+
for (const handle of mw.requires ?? []) {
|
|
19
|
+
if (!provided.has(handle)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Middleware "${mw.name ?? "unnamed"}" requires capability "${handle.capabilityName}". Provided capabilities: ${providedNames()}. Add a middleware that provides "${handle.capabilityName}".`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
validateCapabilities
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sources":["../../../../../src/activities/chat/middleware/validate.ts"],"sourcesContent":["import type { CapabilityHandle } from './capabilities'\nimport type { AnyChatMiddleware } from './types'\n\n/** Minimal adapter shape needed for capability validation. */\ninterface CapabilityRequiringAdapter {\n name: string\n requires?: ReadonlyArray<CapabilityHandle>\n}\n\n/**\n * Runtime validation: every required capability (from middleware `requires` and\n * the adapter's `requires`) must be provided by some middleware's `provides`.\n * `optionalRequires` is never gating. Throws a clear error otherwise.\n *\n * Presence only — ORDER is not validated here (a provider may appear after its\n * consumer in the array and still pass). Use the `createChatMiddleware()`\n * builder for compile-time order enforcement; at runtime, a consumer that reads\n * a not-yet-provided capability during `setup` fails loud via its getter.\n */\nexport function validateCapabilities(\n middlewares: ReadonlyArray<AnyChatMiddleware>,\n adapter: CapabilityRequiringAdapter,\n): void {\n const provided = new Set<CapabilityHandle>()\n for (const mw of middlewares) {\n for (const handle of mw.provides ?? []) provided.add(handle)\n }\n\n const providedNames = (): string => {\n const names = [...provided].map((h) => h.capabilityName)\n return names.length ? names.join(', ') : 'none'\n }\n\n for (const handle of adapter.requires ?? []) {\n if (!provided.has(handle)) {\n throw new Error(\n `Adapter \"${adapter.name}\" requires capability \"${handle.capabilityName}\". ` +\n `Provided capabilities: ${providedNames()}. ` +\n `Add a middleware that provides \"${handle.capabilityName}\".`,\n )\n }\n }\n\n for (const mw of middlewares) {\n for (const handle of mw.requires ?? []) {\n if (!provided.has(handle)) {\n throw new Error(\n `Middleware \"${mw.name ?? 'unnamed'}\" requires capability ` +\n `\"${handle.capabilityName}\". Provided capabilities: ${providedNames()}. ` +\n `Add a middleware that provides \"${handle.capabilityName}\".`,\n )\n }\n }\n }\n}\n"],"names":[],"mappings":"AAmBO,SAAS,qBACd,aACA,SACM;AACN,QAAM,+BAAe,IAAA;AACrB,aAAW,MAAM,aAAa;AAC5B,eAAW,UAAU,GAAG,YAAY,CAAA,EAAI,UAAS,IAAI,MAAM;AAAA,EAC7D;AAEA,QAAM,gBAAgB,MAAc;AAClC,UAAM,QAAQ,CAAC,GAAG,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,cAAc;AACvD,WAAO,MAAM,SAAS,MAAM,KAAK,IAAI,IAAI;AAAA,EAC3C;AAEA,aAAW,UAAU,QAAQ,YAAY,CAAA,GAAI;AAC3C,QAAI,CAAC,SAAS,IAAI,MAAM,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,YAAY,QAAQ,IAAI,0BAA0B,OAAO,cAAc,6BAC3C,cAAA,CAAe,qCACN,OAAO,cAAc;AAAA,MAAA;AAAA,IAE9D;AAAA,EACF;AAEA,aAAW,MAAM,aAAa;AAC5B,eAAW,UAAU,GAAG,YAAY,CAAA,GAAI;AACtC,UAAI,CAAC,SAAS,IAAI,MAAM,GAAG;AACzB,cAAM,IAAI;AAAA,UACR,eAAe,GAAG,QAAQ,SAAS,0BAC7B,OAAO,cAAc,6BAA6B,cAAA,CAAe,qCAClC,OAAO,cAAc;AAAA,QAAA;AAAA,MAE9D;AAAA,IACF;AAAA,EACF;AACF;"}
|
|
@@ -101,7 +101,7 @@ function updateToolCallWithOutput(messages, toolCallId, output, state, errorText
|
|
|
101
101
|
parts[index] = {
|
|
102
102
|
...toolCallPart,
|
|
103
103
|
output: errorText ? { error: errorText } : output,
|
|
104
|
-
state: state ?? (errorText ? "
|
|
104
|
+
state: state ?? (errorText ? "error" : "complete")
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
return { ...msg, parts };
|
|
@@ -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 { parsePartialJSON } from './json-parser'\nimport type {\n ContentPart,\n StructuredOutputPart,\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 | Array<ContentPart>,\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 const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n state: 'approval-requested',\n approval: {\n id: approvalId,\n needsApproval: true,\n },\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 const index = parts.indexOf(toolCallPart)\n parts[index] = { ...toolCallPart, 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 const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n output: errorText ? { error: errorText } : output,\n state: state ?? (errorText ? 'input-complete' : '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 const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n approval: { ...toolCallPart.approval, approved },\n state: 'approval-responded',\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Append a delta to the structured-output part on `messageId`, or create one\n * if absent. Progressive parse of the accumulated buffer fills `partial`.\n *\n * Callers must only invoke this while the part is still in flight — the\n * helper unconditionally writes `status: 'streaming'`, so feeding it a delta\n * after a `complete`/`error` terminal would regress the part. In practice the\n * processor gates calls via `structuredMessageIds`, which is dropped on\n * terminal events.\n *\n * If the progressive parse returns null/undefined (the buffer is not yet a\n * parseable JSON prefix), the previously-good `partial` is preserved so the\n * UI doesn't flicker back to empty for a single render.\n */\nexport function appendStructuredOutputDelta(\n messages: Array<UIMessage>,\n messageId: string,\n delta: 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 existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n const existing =\n existingIndex >= 0 ? (parts[existingIndex] as StructuredOutputPart) : null\n\n const nextRaw = (existing?.raw ?? '') + delta\n const progressive = parsePartialJSON(nextRaw)\n const nextPartial =\n progressive !== undefined && progressive !== null\n ? progressive\n : existing?.partial\n\n const nextPart: StructuredOutputPart = {\n type: 'structured-output',\n status: 'streaming',\n raw: nextRaw,\n ...(nextPartial !== undefined ? { partial: nextPartial } : {}),\n ...(existing?.reasoning !== undefined\n ? { reasoning: existing.reasoning }\n : {}),\n }\n\n if (existingIndex >= 0) {\n parts[existingIndex] = nextPart\n } else {\n parts.push(nextPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Snap the structured-output part on `messageId` to `complete` with the\n * validated `data`. Picks the freshest available `raw` so the wire\n * round-trip stays internally consistent:\n *\n * 1. Caller-supplied `raw` (the original streamed bytes from the model).\n * 2. The existing part's `raw` (deltas accumulated before this terminal).\n * 3. `JSON.stringify(data)` as a defensive fallback for terminal-only\n * completes that never shipped raw — keeps the part self-consistent\n * so downstream consumers never see a complete part with empty raw.\n */\nexport function completeStructuredOutputPart(\n messages: Array<UIMessage>,\n messageId: string,\n data: unknown,\n raw: string,\n reasoning?: 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 existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n\n const existingRaw =\n existingIndex >= 0\n ? (parts[existingIndex] as StructuredOutputPart).raw\n : ''\n let resolvedRaw = raw || existingRaw\n if (resolvedRaw === '' && data !== undefined) {\n try {\n resolvedRaw = JSON.stringify(data)\n } catch {\n // Unserializable (circular, BigInt, throwing toJSON). Leave raw\n // empty. Both downstream paths handle this: `ag-ui-wire.ts`\n // `collectText` skips complete parts with empty raw entirely, and\n // `uiMessageToModelMessages` falls back to a defensive\n // `safeJsonStringify(data)` which itself returns `''` for the same\n // unserializable inputs — so the turn is silently dropped from the\n // next request rather than shipping garbage or crashing the stream.\n }\n }\n\n const nextPart: StructuredOutputPart = {\n type: 'structured-output',\n status: 'complete',\n data,\n partial: data,\n raw: resolvedRaw,\n ...(reasoning !== undefined ? { reasoning } : {}),\n }\n\n if (existingIndex >= 0) {\n parts[existingIndex] = nextPart\n } else {\n parts.push(nextPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Mark the structured-output part on `messageId` as errored. If no part\n * exists yet — RUN_ERROR fired after `structured-output.start` but before\n * any delta — create an empty errored placeholder so consumers have\n * something renderable. Existing complete parts are left alone (an error\n * after a successful complete should not retroactively un-complete it).\n */\nexport function errorStructuredOutputPart(\n messages: Array<UIMessage>,\n messageId: string,\n errorMessage: 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 existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n\n if (existingIndex < 0) {\n parts.push({\n type: 'structured-output',\n status: 'error',\n raw: '',\n errorMessage,\n })\n return { ...msg, parts }\n }\n\n const existing = parts[existingIndex] as StructuredOutputPart\n if (existing.status === 'complete') {\n return msg\n }\n parts[existingIndex] = {\n ...existing,\n status: 'error',\n errorMessage,\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":";AAwBO,SAAS,eACd,UACA,WACA,SACkB;AAClB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,QAAI,IAAI,OAAO,WAAW;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;AAC3B,UAAM,WAAW,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,CAAC,IAAI;AAE9D,QAAI,YAAY,SAAS,SAAS,QAAQ;AAExC,YAAM,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,QAAQ,QAAA;AAAA,IAC5C,OAAO;AAEL,YAAM,KAAK,EAAE,MAAM,QAAQ,SAAS;AAAA,IACtC;AAEA,WAAO,EAAE,GAAG,KAAK,MAAA;AAAA,EACnB,CAAC;AACH;AAKO,SAAS,mBACd,UACA,WACA,UAOkB;AAClB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,QAAI,IAAI,OAAO,WAAW;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;AAC3B,UAAM,WAAW,MAAM;AAAA,MACrB,CAAC,MAAyB,EAAE,SAAS,eAAe,EAAE,OAAO,SAAS;AAAA,IAAA;AAOxE,UAAM,WAAW,SAAS,YAAY,UAAU;AAEhD,UAAM,eAA6B;AAAA,MACjC,MAAM;AAAA,MACN,IAAI,SAAS;AAAA,MACb,MAAM,SAAS;AAAA,MACf,WAAW,SAAS;AAAA,MACpB,OAAO,SAAS;AAAA;AAAA,MAEhB,GAAI,UAAU,YAAY,EAAE,UAAU,EAAE,GAAG,SAAS,WAAS;AAAA,MAC7D,GAAI,UAAU,WAAW,UAAa,EAAE,QAAQ,SAAS,OAAA;AAAA,MACzD,GAAI,aAAa,UAAa,EAAE,SAAA;AAAA,IAAS;AAG3C,QAAI,UAAU;AAEZ,YAAM,MAAM,QAAQ,QAAQ,CAAC,IAAI;AAAA,IACnC,OAAO;AAEL,YAAM,KAAK,YAAY;AAAA,IACzB;AAEA,WAAO,EAAE,GAAG,KAAK,MAAA;AAAA,EACnB,CAAC;AACH;AAKO,SAAS,qBACd,UACA,WACA,YACA,SACA,OACA,OACkB;AAClB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,QAAI,IAAI,OAAO,WAAW;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;AAC3B,UAAM,kBAAkB,MAAM;AAAA,MAC5B,CAAC,MACC,EAAE,SAAS,iBAAiB,EAAE,eAAe;AAAA,IAAA;AAGjD,UAAM,iBAAiC;AAAA,MACrC,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,SAAS,EAAE,MAAA;AAAA,IAAM;AAGvB,QAAI,mBAAmB,GAAG;AACxB,YAAM,eAAe,IAAI;AAAA,IAC3B,OAAO;AACL,YAAM,KAAK,cAAc;AAAA,IAC3B;AAEA,WAAO,EAAE,GAAG,KAAK,MAAA;AAAA,EACnB,CAAC;AACH;AAKO,SAAS,uBACd,UACA,WACA,YACA,YACkB;AAClB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,QAAI,IAAI,OAAO,WAAW;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;AAC3B,UAAM,eAAe,MAAM;AAAA,MACzB,CAAC,MAAyB,EAAE,SAAS,eAAe,EAAE,OAAO;AAAA,IAAA;AAG/D,QAAI,cAAc;AAChB,YAAM,QAAQ,MAAM,QAAQ,YAAY;AACxC,YAAM,KAAK,IAAI;AAAA,QACb,GAAG;AAAA,QACH,OAAO;AAAA,QACP,UAAU;AAAA,UACR,IAAI;AAAA,UACJ,eAAe;AAAA,QAAA;AAAA,MACjB;AAAA,IAEJ;AAEA,WAAO,EAAE,GAAG,KAAK,MAAA;AAAA,EACnB,CAAC;AACH;AAkCO,SAAS,yBACd,UACA,YACA,QACA,OACA,WACkB;AAClB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,UAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;AAC3B,UAAM,eAAe,MAAM;AAAA,MACzB,CAAC,MAAyB,EAAE,SAAS,eAAe,EAAE,OAAO;AAAA,IAAA;AAG/D,QAAI,cAAc;AAChB,YAAM,QAAQ,MAAM,QAAQ,YAAY;AACxC,YAAM,KAAK,IAAI;AAAA,QACb,GAAG;AAAA,QACH,QAAQ,YAAY,EAAE,OAAO,cAAc;AAAA,QAC3C,OAAO,UAAU,YAAY,mBAAmB;AAAA,MAAA;AAAA,IAEpD;AAEA,WAAO,EAAE,GAAG,KAAK,MAAA;AAAA,EACnB,CAAC;AACH;AAMO,SAAS,+BACd,UACA,YACA,UACkB;AAClB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,UAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;AAC3B,UAAM,eAAe,MAAM;AAAA,MACzB,CAAC,MACC,EAAE,SAAS,eAAe,EAAE,UAAU,OAAO;AAAA,IAAA;AAGjD,QAAI,gBAAgB,aAAa,UAAU;AACzC,YAAM,QAAQ,MAAM,QAAQ,YAAY;AACxC,YAAM,KAAK,IAAI;AAAA,QACb,GAAG;AAAA,QACH,UAAU,EAAE,GAAG,aAAa,UAAU,SAAA;AAAA,QACtC,OAAO;AAAA,MAAA;AAAA,IAEX;AAEA,WAAO,EAAE,GAAG,KAAK,MAAA;AAAA,EACnB,CAAC;AACH;AAgBO,SAAS,4BACd,UACA,WACA,OACkB;AAClB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,QAAI,IAAI,OAAO,WAAW;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;AAC3B,UAAM,gBAAgB,MAAM;AAAA,MAC1B,CAAC,MAAiC,EAAE,SAAS;AAAA,IAAA;AAE/C,UAAM,WACJ,iBAAiB,IAAK,MAAM,aAAa,IAA6B;AAExE,UAAM,WAAW,UAAU,OAAO,MAAM;AACxC,UAAM,cAAc,iBAAiB,OAAO;AAC5C,UAAM,cACJ,gBAAgB,UAAa,gBAAgB,OACzC,cACA,UAAU;AAEhB,UAAM,WAAiC;AAAA,MACrC,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,GAAI,gBAAgB,SAAY,EAAE,SAAS,YAAA,IAAgB,CAAA;AAAA,MAC3D,GAAI,UAAU,cAAc,SACxB,EAAE,WAAW,SAAS,cACtB,CAAA;AAAA,IAAC;AAGP,QAAI,iBAAiB,GAAG;AACtB,YAAM,aAAa,IAAI;AAAA,IACzB,OAAO;AACL,YAAM,KAAK,QAAQ;AAAA,IACrB;AAEA,WAAO,EAAE,GAAG,KAAK,MAAA;AAAA,EACnB,CAAC;AACH;AAaO,SAAS,6BACd,UACA,WACA,MACA,KACA,WACkB;AAClB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,QAAI,IAAI,OAAO,WAAW;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;AAC3B,UAAM,gBAAgB,MAAM;AAAA,MAC1B,CAAC,MAAiC,EAAE,SAAS;AAAA,IAAA;AAG/C,UAAM,cACJ,iBAAiB,IACZ,MAAM,aAAa,EAA2B,MAC/C;AACN,QAAI,cAAc,OAAO;AACzB,QAAI,gBAAgB,MAAM,SAAS,QAAW;AAC5C,UAAI;AACF,sBAAc,KAAK,UAAU,IAAI;AAAA,MACnC,QAAQ;AAAA,MAQR;AAAA,IACF;AAEA,UAAM,WAAiC;AAAA,MACrC,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,MACA,SAAS;AAAA,MACT,KAAK;AAAA,MACL,GAAI,cAAc,SAAY,EAAE,cAAc,CAAA;AAAA,IAAC;AAGjD,QAAI,iBAAiB,GAAG;AACtB,YAAM,aAAa,IAAI;AAAA,IACzB,OAAO;AACL,YAAM,KAAK,QAAQ;AAAA,IACrB;AAEA,WAAO,EAAE,GAAG,KAAK,MAAA;AAAA,EACnB,CAAC;AACH;AASO,SAAS,0BACd,UACA,WACA,cACkB;AAClB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,QAAI,IAAI,OAAO,WAAW;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;AAC3B,UAAM,gBAAgB,MAAM;AAAA,MAC1B,CAAC,MAAiC,EAAE,SAAS;AAAA,IAAA;AAG/C,QAAI,gBAAgB,GAAG;AACrB,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,KAAK;AAAA,QACL;AAAA,MAAA,CACD;AACD,aAAO,EAAE,GAAG,KAAK,MAAA;AAAA,IACnB;AAEA,UAAM,WAAW,MAAM,aAAa;AACpC,QAAI,SAAS,WAAW,YAAY;AAClC,aAAO;AAAA,IACT;AACA,UAAM,aAAa,IAAI;AAAA,MACrB,GAAG;AAAA,MACH,QAAQ;AAAA,MACR;AAAA,IAAA;AAEF,WAAO,EAAE,GAAG,KAAK,MAAA;AAAA,EACnB,CAAC;AACH;AAMO,SAAS,mBACd,UACA,WACA,QACA,SACA,WACkB;AAClB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,QAAI,IAAI,OAAO,WAAW;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,CAAC,GAAG,IAAI,KAAK;AAC3B,UAAM,oBAAoB,MAAM;AAAA,MAC9B,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,WAAW;AAAA,IAAA;AAG/C,UAAM,eAA6B;AAAA,MACjC,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,GAAI,aAAa,EAAE,UAAA;AAAA,IAAU;AAG/B,QAAI,qBAAqB,GAAG;AAE1B,YAAM,iBAAiB,IAAI;AAAA,IAC7B,OAAO;AAEL,YAAM,KAAK,YAAY;AAAA,IACzB;AAEA,WAAO,EAAE,GAAG,KAAK,MAAA;AAAA,EACnB,CAAC;AACH;"}
|
|
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 { parsePartialJSON } from './json-parser'\nimport type {\n ContentPart,\n StructuredOutputPart,\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 | Array<ContentPart>,\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 const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n state: 'approval-requested',\n approval: {\n id: approvalId,\n needsApproval: true,\n },\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 const index = parts.indexOf(toolCallPart)\n parts[index] = { ...toolCallPart, 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 const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n output: errorText ? { error: errorText } : output,\n state: state ?? (errorText ? 'error' : '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 const index = parts.indexOf(toolCallPart)\n parts[index] = {\n ...toolCallPart,\n approval: { ...toolCallPart.approval, approved },\n state: 'approval-responded',\n }\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Append a delta to the structured-output part on `messageId`, or create one\n * if absent. Progressive parse of the accumulated buffer fills `partial`.\n *\n * Callers must only invoke this while the part is still in flight — the\n * helper unconditionally writes `status: 'streaming'`, so feeding it a delta\n * after a `complete`/`error` terminal would regress the part. In practice the\n * processor gates calls via `structuredMessageIds`, which is dropped on\n * terminal events.\n *\n * If the progressive parse returns null/undefined (the buffer is not yet a\n * parseable JSON prefix), the previously-good `partial` is preserved so the\n * UI doesn't flicker back to empty for a single render.\n */\nexport function appendStructuredOutputDelta(\n messages: Array<UIMessage>,\n messageId: string,\n delta: 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 existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n const existing =\n existingIndex >= 0 ? (parts[existingIndex] as StructuredOutputPart) : null\n\n const nextRaw = (existing?.raw ?? '') + delta\n const progressive = parsePartialJSON(nextRaw)\n const nextPartial =\n progressive !== undefined && progressive !== null\n ? progressive\n : existing?.partial\n\n const nextPart: StructuredOutputPart = {\n type: 'structured-output',\n status: 'streaming',\n raw: nextRaw,\n ...(nextPartial !== undefined ? { partial: nextPartial } : {}),\n ...(existing?.reasoning !== undefined\n ? { reasoning: existing.reasoning }\n : {}),\n }\n\n if (existingIndex >= 0) {\n parts[existingIndex] = nextPart\n } else {\n parts.push(nextPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Snap the structured-output part on `messageId` to `complete` with the\n * validated `data`. Picks the freshest available `raw` so the wire\n * round-trip stays internally consistent:\n *\n * 1. Caller-supplied `raw` (the original streamed bytes from the model).\n * 2. The existing part's `raw` (deltas accumulated before this terminal).\n * 3. `JSON.stringify(data)` as a defensive fallback for terminal-only\n * completes that never shipped raw — keeps the part self-consistent\n * so downstream consumers never see a complete part with empty raw.\n */\nexport function completeStructuredOutputPart(\n messages: Array<UIMessage>,\n messageId: string,\n data: unknown,\n raw: string,\n reasoning?: 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 existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n\n const existingRaw =\n existingIndex >= 0\n ? (parts[existingIndex] as StructuredOutputPart).raw\n : ''\n let resolvedRaw = raw || existingRaw\n if (resolvedRaw === '' && data !== undefined) {\n try {\n resolvedRaw = JSON.stringify(data)\n } catch {\n // Unserializable (circular, BigInt, throwing toJSON). Leave raw\n // empty. Both downstream paths handle this: `ag-ui-wire.ts`\n // `collectText` skips complete parts with empty raw entirely, and\n // `uiMessageToModelMessages` falls back to a defensive\n // `safeJsonStringify(data)` which itself returns `''` for the same\n // unserializable inputs — so the turn is silently dropped from the\n // next request rather than shipping garbage or crashing the stream.\n }\n }\n\n const nextPart: StructuredOutputPart = {\n type: 'structured-output',\n status: 'complete',\n data,\n partial: data,\n raw: resolvedRaw,\n ...(reasoning !== undefined ? { reasoning } : {}),\n }\n\n if (existingIndex >= 0) {\n parts[existingIndex] = nextPart\n } else {\n parts.push(nextPart)\n }\n\n return { ...msg, parts }\n })\n}\n\n/**\n * Mark the structured-output part on `messageId` as errored. If no part\n * exists yet — RUN_ERROR fired after `structured-output.start` but before\n * any delta — create an empty errored placeholder so consumers have\n * something renderable. Existing complete parts are left alone (an error\n * after a successful complete should not retroactively un-complete it).\n */\nexport function errorStructuredOutputPart(\n messages: Array<UIMessage>,\n messageId: string,\n errorMessage: 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 existingIndex = parts.findIndex(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n\n if (existingIndex < 0) {\n parts.push({\n type: 'structured-output',\n status: 'error',\n raw: '',\n errorMessage,\n })\n return { ...msg, parts }\n }\n\n const existing = parts[existingIndex] as StructuredOutputPart\n if (existing.status === 'complete') {\n return msg\n }\n parts[existingIndex] = {\n ...existing,\n status: 'error',\n errorMessage,\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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"}
|
|
@@ -362,6 +362,12 @@ export declare class StreamProcessor {
|
|
|
362
362
|
* Mark a tool call as complete and emit event
|
|
363
363
|
*/
|
|
364
364
|
private completeToolCall;
|
|
365
|
+
/**
|
|
366
|
+
* Whether the rendered tool-call part for the given id has reached the
|
|
367
|
+
* terminal 'error' state. Used to prevent the completion safety net from
|
|
368
|
+
* downgrading a failed call back to 'input-complete'.
|
|
369
|
+
*/
|
|
370
|
+
private isToolCallPartErrored;
|
|
365
371
|
/**
|
|
366
372
|
* Emit pending text update for a specific message.
|
|
367
373
|
*
|
|
@@ -137,7 +137,7 @@ class StreamProcessor {
|
|
|
137
137
|
this.messages,
|
|
138
138
|
toolCallId,
|
|
139
139
|
output,
|
|
140
|
-
error ? "
|
|
140
|
+
error ? "error" : void 0,
|
|
141
141
|
error
|
|
142
142
|
);
|
|
143
143
|
const content = normalizeToolResult(output);
|
|
@@ -767,7 +767,7 @@ class StreamProcessor {
|
|
|
767
767
|
this.messages,
|
|
768
768
|
chunk.toolCallId,
|
|
769
769
|
output,
|
|
770
|
-
chunk.state === "output-error" ? "
|
|
770
|
+
chunk.state === "output-error" ? "error" : void 0
|
|
771
771
|
);
|
|
772
772
|
const resultState = chunk.state === "output-error" ? "error" : "complete";
|
|
773
773
|
this.messages = updateToolResultPart(
|
|
@@ -807,7 +807,7 @@ class StreamProcessor {
|
|
|
807
807
|
this.messages,
|
|
808
808
|
chunk.toolCallId,
|
|
809
809
|
output,
|
|
810
|
-
chunk.state === "output-error" ? "
|
|
810
|
+
chunk.state === "output-error" ? "error" : void 0
|
|
811
811
|
);
|
|
812
812
|
const resultState = chunk.state === "output-error" ? "error" : "complete";
|
|
813
813
|
this.messages = updateToolResultPart(
|
|
@@ -1137,6 +1137,9 @@ class StreamProcessor {
|
|
|
1137
1137
|
completeToolCall(messageId, _index, toolCall) {
|
|
1138
1138
|
toolCall.state = "input-complete";
|
|
1139
1139
|
toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments);
|
|
1140
|
+
if (this.isToolCallPartErrored(toolCall.id)) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1140
1143
|
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
1141
1144
|
id: toolCall.id,
|
|
1142
1145
|
name: toolCall.name,
|
|
@@ -1152,6 +1155,18 @@ class StreamProcessor {
|
|
|
1152
1155
|
toolCall.arguments
|
|
1153
1156
|
);
|
|
1154
1157
|
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Whether the rendered tool-call part for the given id has reached the
|
|
1160
|
+
* terminal 'error' state. Used to prevent the completion safety net from
|
|
1161
|
+
* downgrading a failed call back to 'input-complete'.
|
|
1162
|
+
*/
|
|
1163
|
+
isToolCallPartErrored(toolCallId) {
|
|
1164
|
+
return this.messages.some(
|
|
1165
|
+
(msg) => msg.parts.some(
|
|
1166
|
+
(part) => part.type === "tool-call" && part.id === toolCallId && part.state === "error"
|
|
1167
|
+
)
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1155
1170
|
/**
|
|
1156
1171
|
* Emit pending text update for a specific message.
|
|
1157
1172
|
*
|