@tanstack/ai 0.61.0 → 0.64.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.
Files changed (104) hide show
  1. package/README.md +1 -0
  2. package/dist/esm/activities/chat/agents/define-agent.d.ts +17 -5
  3. package/dist/esm/activities/chat/agents/define-agent.js.map +1 -1
  4. package/dist/esm/activities/chat/agents/spawn.d.ts +2 -0
  5. package/dist/esm/activities/chat/agents/spawn.js +10 -33
  6. package/dist/esm/activities/chat/agents/spawn.js.map +1 -1
  7. package/dist/esm/activities/chat/index.d.ts +2 -0
  8. package/dist/esm/activities/chat/index.js +166 -60
  9. package/dist/esm/activities/chat/index.js.map +1 -1
  10. package/dist/esm/activities/chat/messages.js +43 -22
  11. package/dist/esm/activities/chat/messages.js.map +1 -1
  12. package/dist/esm/activities/chat/middleware/types.d.ts +1 -0
  13. package/dist/esm/activities/chat/middleware/types.js.map +1 -1
  14. package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
  15. package/dist/esm/activities/chat/stream/message-updaters.js +6 -2
  16. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  17. package/dist/esm/activities/chat/stream/processor.d.ts +11 -9
  18. package/dist/esm/activities/chat/stream/processor.js +38 -18
  19. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  20. package/dist/esm/activities/chat/tools/schema-converter.d.ts +8 -0
  21. package/dist/esm/activities/chat/tools/schema-converter.js +6 -5
  22. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  23. package/dist/esm/activities/chat/tools/tool-calls.d.ts +20 -3
  24. package/dist/esm/activities/chat/tools/tool-calls.js +126 -36
  25. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  26. package/dist/esm/activities/chat/tools/tool-definition.d.ts +4 -0
  27. package/dist/esm/activities/chat/tools/tool-definition.js +4 -0
  28. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  29. package/dist/esm/activities/evaluate/adapter.d.ts +4 -0
  30. package/dist/esm/activities/evaluate/adapter.js.map +1 -1
  31. package/dist/esm/activities/evaluate/index.d.ts +4 -0
  32. package/dist/esm/activities/evaluate/index.js +3 -1
  33. package/dist/esm/activities/evaluate/index.js.map +1 -1
  34. package/dist/esm/activities/generateSpeech/index.d.ts +1 -1
  35. package/dist/esm/activities/generateSpeech/index.js +1 -1
  36. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  37. package/dist/esm/activities/generateVideo/adapter.d.ts +14 -6
  38. package/dist/esm/activities/generateVideo/adapter.js +6 -3
  39. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  40. package/dist/esm/activities/generateVideo/index.d.ts +5 -4
  41. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  42. package/dist/esm/activities/generateVideo/snap.d.ts +12 -3
  43. package/dist/esm/activities/generateVideo/snap.js +47 -8
  44. package/dist/esm/activities/generateVideo/snap.js.map +1 -1
  45. package/dist/esm/activities/generateVoice/index.d.ts +1 -1
  46. package/dist/esm/activities/generateVoice/index.js +1 -1
  47. package/dist/esm/activities/generateVoice/index.js.map +1 -1
  48. package/dist/esm/activities/index.d.ts +2 -2
  49. package/dist/esm/activities/index.js +2 -2
  50. package/dist/esm/adapter-internals.d.ts +1 -0
  51. package/dist/esm/adapter-internals.js +2 -1
  52. package/dist/esm/client.d.ts +1 -1
  53. package/dist/esm/client.js.map +1 -1
  54. package/dist/esm/index.d.ts +1 -1
  55. package/dist/esm/index.js +2 -2
  56. package/dist/esm/interrupt-resume.js +29 -4
  57. package/dist/esm/interrupt-resume.js.map +1 -1
  58. package/dist/esm/middlewares/otel.d.ts +5 -2
  59. package/dist/esm/middlewares/otel.js +114 -0
  60. package/dist/esm/middlewares/otel.js.map +1 -1
  61. package/dist/esm/types.d.ts +70 -4
  62. package/dist/esm/utilities/ag-ui-wire.js +8 -4
  63. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  64. package/dist/esm/utilities/merge-streams.d.ts +6 -0
  65. package/dist/esm/utilities/merge-streams.js +37 -0
  66. package/dist/esm/utilities/merge-streams.js.map +1 -0
  67. package/dist/esm/utilities/reasoning-encrypted-value.d.ts +8 -0
  68. package/dist/esm/utilities/reasoning-encrypted-value.js +11 -1
  69. package/dist/esm/utilities/reasoning-encrypted-value.js.map +1 -1
  70. package/dist/esm/utilities/tool-result.d.ts +2 -1
  71. package/dist/esm/utilities/tool-result.js +4 -1
  72. package/dist/esm/utilities/tool-result.js.map +1 -1
  73. package/package.json +3 -3
  74. package/skills/ai-core/chat-experience/SKILL.md +120 -0
  75. package/skills/ai-core/media-generation/SKILL.md +3 -3
  76. package/skills/ai-core/tool-calling/SKILL.md +103 -0
  77. package/src/activities/chat/agents/define-agent.ts +20 -3
  78. package/src/activities/chat/agents/spawn.ts +22 -43
  79. package/src/activities/chat/index.ts +248 -65
  80. package/src/activities/chat/messages.ts +54 -7
  81. package/src/activities/chat/middleware/types.ts +1 -0
  82. package/src/activities/chat/stream/message-updaters.ts +8 -0
  83. package/src/activities/chat/stream/processor.ts +57 -27
  84. package/src/activities/chat/tools/schema-converter.ts +17 -5
  85. package/src/activities/chat/tools/tool-calls.ts +215 -68
  86. package/src/activities/chat/tools/tool-definition.ts +8 -0
  87. package/src/activities/evaluate/adapter.ts +4 -0
  88. package/src/activities/evaluate/index.ts +6 -0
  89. package/src/activities/generateSpeech/index.ts +1 -1
  90. package/src/activities/generateVideo/adapter.ts +21 -7
  91. package/src/activities/generateVideo/index.ts +5 -4
  92. package/src/activities/generateVideo/snap.ts +64 -6
  93. package/src/activities/generateVoice/index.ts +1 -1
  94. package/src/activities/index.ts +2 -1
  95. package/src/adapter-internals.ts +1 -0
  96. package/src/client.ts +1 -0
  97. package/src/index.ts +1 -0
  98. package/src/interrupt-resume.ts +55 -4
  99. package/src/middlewares/otel.ts +161 -3
  100. package/src/types.ts +66 -5
  101. package/src/utilities/ag-ui-wire.ts +17 -2
  102. package/src/utilities/merge-streams.ts +34 -0
  103. package/src/utilities/reasoning-encrypted-value.ts +12 -0
  104. package/src/utilities/tool-result.ts +7 -1
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/activities/chat/middleware/types.ts"],"sourcesContent":["import type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from '@standard-schema/spec'\nimport type {\n AgentLoopState,\n EmitCustomEventOptions,\n Interrupt,\n JSONSchema,\n ModelMessage,\n UIMessage,\n RunAgentResumeItem,\n StreamChunk,\n TokenUsage,\n Tool,\n ToolCall,\n} from '../../../types'\nimport type { SystemPrompt } from '../../../system-prompts'\nimport type { ToolApprovalResolution } from '../../../interrupts'\nimport type {\n GenericInterruptRequest,\n InterruptDefinition,\n} from '../../../interrupt-definition'\nimport type {\n Capability,\n CapabilityHandle,\n CapabilityRegistry,\n} from './capabilities'\n\n/** A file change observed inside a sandbox during a chat run. */\nexport interface SandboxFileEvent {\n type: 'create' | 'change' | 'delete'\n /** Absolute path inside the sandbox (under the workspace root). */\n path: string\n timestamp: number\n}\n\n/** The file event a sandbox hook receives: the serializable {@link SandboxFileEvent}\n * plus lazy, git-backed content accessors. Accessors compute on call, so a hook\n * that only reads `path`/`type` pays nothing. Never present on the serialized\n * `sandbox.file` CUSTOM chunk. */\nexport interface SandboxFileHookEvent extends SandboxFileEvent {\n /** Content at the session baseline (`''` for a new file or non-git workspace). */\n before: () => Promise<string>\n /** Current content (`''` when the event is a delete). */\n after: () => Promise<string>\n /** Unified patch vs the session baseline (synthesized add-patch when non-git). */\n diff: () => Promise<string>\n}\n\n/**\n * Sandbox file-event hooks a chat middleware can declare. Fire server-side for\n * every file create/change/delete observed in the sandbox during the run.\n */\nexport interface ChatSandboxHooks<TContext = unknown> {\n onFile?: (\n ctx: ChatMiddlewareContext<TContext>,\n e: SandboxFileHookEvent,\n ) => void | Promise<void>\n onFileCreate?: (\n ctx: ChatMiddlewareContext<TContext>,\n e: SandboxFileHookEvent,\n ) => void | Promise<void>\n onFileChange?: (\n ctx: ChatMiddlewareContext<TContext>,\n e: SandboxFileHookEvent,\n ) => void | Promise<void>\n onFileDelete?: (\n ctx: ChatMiddlewareContext<TContext>,\n e: SandboxFileHookEvent,\n ) => void | Promise<void>\n}\n\n// ===========================\n// Middleware Context\n// ===========================\n\n/**\n * Phase of the chat middleware lifecycle.\n * - 'init': Initial config transform before the chat engine starts\n * - 'beforeModel': Before each adapter chatStream call (per agent iteration)\n * - 'afterModel': After each adapter chatStream call (per agent iteration)\n * - 'modelStream': During model streaming\n * - 'beforeTools': Before tool execution phase\n * - 'afterTools': After tool execution phase\n * - 'structuredOutput': During the final structured-output adapter call (set\n * for chunks from adapter.structuredOutputStream or the synthesized fallback)\n */\nexport type ChatMiddlewarePhase =\n | 'init'\n | 'beforeModel'\n | 'afterModel'\n | 'modelStream'\n | 'beforeTools'\n | 'afterTools'\n | 'structuredOutput'\n\nexport const INTERRUPT_BOUNDARY_PHASES = [\n 'beforeModel',\n 'afterModel',\n 'beforeTools',\n 'afterTools',\n] as const\n\nexport type InterruptBoundaryPhase = (typeof INTERRUPT_BOUNDARY_PHASES)[number]\n\nexport const INTERRUPT_TOOL_RESUMES = ['continue', 'cancel', 'stop'] as const\n\nexport type InterruptToolResume = (typeof INTERRUPT_TOOL_RESUMES)[number]\n\ntype AnyInterruptDefinition = InterruptDefinition<any, any, any, any>\n\ntype InterruptResponse<TDefinition> =\n TDefinition extends InterruptDefinition<any, any, infer TResponseSchema, any>\n ? TResponseSchema extends StandardSchemaV1<any, infer TResponse>\n ? TResponse\n : TResponseSchema extends StandardJSONSchemaV1<any, infer TResponse>\n ? TResponse\n : unknown\n : unknown\n\nexport type GenericInterruptResolution<\n TDefinition extends AnyInterruptDefinition,\n> = TDefinition extends AnyInterruptDefinition\n ?\n | {\n readonly request: GenericInterruptRequest<TDefinition>\n readonly status: 'resolved'\n readonly response: InterruptResponse<TDefinition>\n }\n | {\n readonly request: GenericInterruptRequest<TDefinition>\n readonly status: 'cancelled'\n readonly response?: never\n }\n : never\n\nexport interface InterruptResolutionCollection<\n TDefinitions extends AnyInterruptDefinition = AnyInterruptDefinition,\n> {\n for: <\n TDefinition extends ([TDefinitions] extends [never]\n ? AnyInterruptDefinition\n : TDefinitions),\n >(\n definition: TDefinition,\n ) => ReadonlyArray<GenericInterruptResolution<TDefinition>>\n all: {\n (): ReadonlyArray<GenericInterruptResolution<TDefinitions>>\n <const TSelected extends ReadonlyArray<TDefinitions>>(\n ...definitions: TSelected\n ): ReadonlyArray<GenericInterruptResolution<TSelected[number]>>\n }\n}\n\ntype BivariantInterruptResolutionHook<\n TContext,\n TDefinitions extends AnyInterruptDefinition,\n> = InterruptResolutionHookSignature<TContext, TDefinitions>['call']\n\ndeclare abstract class InterruptResolutionHookSignature<\n TContext,\n TDefinitions extends AnyInterruptDefinition,\n> {\n abstract call(\n ctx: ChatMiddlewareContext<TContext>,\n resolutions: InterruptResolutionCollection<TDefinitions>,\n ): InterruptResolutionResult | Promise<InterruptResolutionResult>\n}\n\nexport type InterruptBoundaryResult<\n TDefinitions extends AnyInterruptDefinition = AnyInterruptDefinition,\n> =\n | undefined\n | {\n readonly interrupts: ReadonlyArray<GenericInterruptRequest<TDefinitions>>\n }\n\nexport type InterruptResolutionResult = void | {\n readonly toolResume: InterruptToolResume\n}\n\n/**\n * Stable context object passed to all middleware hooks.\n * Created once per chat() invocation and shared across all hooks.\n */\nexport interface ChatMiddlewareContext<TContext = unknown> {\n /** Unique identifier for this chat request */\n requestId: string\n /** Unique identifier for this stream */\n streamId: string\n /** AG-UI run identifier for correlating client and server events */\n runId: string\n /** Interrupted or parent run correlated with this continuation. */\n parentRunId?: string\n /**\n * Set when this run is a subagent. The id on the child's `SUBAGENT_STARTED`\n * and on every chunk it streams. Absent on a top-level run.\n */\n subagentRunId?: string\n /**\n * AG-UI thread identifier — a stable per-conversation ID used to\n * correlate client and server devtools events. Resolves to the\n * caller-provided `threadId` (or legacy `conversationId`), or an\n * auto-generated value when neither is supplied.\n */\n threadId: string\n /**\n * @deprecated Use `threadId` instead. Retained as an alias of\n * `threadId` so middleware written before the AG-UI rename keeps\n * working unchanged. Will be removed in a future major release.\n */\n conversationId?: string\n /** Current lifecycle phase */\n phase: ChatMiddlewarePhase\n /** Current agent loop iteration (0-indexed) */\n iteration: number\n /** Running count of chunks yielded so far */\n chunkIndex: number\n /** Abort signal from the chat request */\n signal?: AbortSignal\n /** Abort the chat run with a reason */\n abort: (reason?: string) => void\n /**\n * Push a `CUSTOM` chunk onto the chat stream immediately.\n * The engine yields it as soon as it can (including while `onConfig`\n * is still awaiting work such as a summarize call). Durability then\n * flushes the event on its own, unless you pass `{ batch: true }`.\n */\n emitCustomEvent: (\n name: string,\n value: Record<string, any>,\n options?: EmitCustomEventOptions,\n ) => void\n /** Runtime context provided by chat() options */\n context: TContext\n /**\n * Defer a non-blocking side-effect promise.\n * Deferred promises do not block streaming and are awaited\n * after the terminal hook (onFinish/onAbort/onError).\n */\n defer: (promise: Promise<unknown>) => void\n\n // --- Provider / adapter info (immutable for the lifetime of the request) ---\n\n /**\n * Which activity this context describes — always `'chat'`. Present so the\n * chat context structurally satisfies the base `GenerationMiddlewareContext`,\n * letting an observe-only middleware authored against the base (e.g.\n * `otelMiddleware`) run on both chat and media activities.\n */\n activity: 'chat'\n /** Provider name (e.g., 'openai', 'anthropic') */\n provider: string\n /** Model identifier (e.g., 'gpt-5.5') */\n model: string\n /** Source of the chat invocation — always 'server' for server-side chat */\n source: 'client' | 'server'\n /** Whether the chat is streaming */\n streaming: boolean\n\n // --- Config-derived info (may update per-iteration via onConfig) ---\n\n /** System prompts configured for this chat */\n systemPrompts: Array<SystemPrompt>\n /** Names of configured tools, if any */\n toolNames?: Array<string>\n /** Flattened generation options (metadata) */\n options?: Record<string, unknown> | undefined\n /** Provider-specific model options */\n modelOptions?: Record<string, unknown> | undefined\n\n // --- Computed info ---\n\n /** Number of messages at the start of the request */\n messageCount: number\n /** Whether tools are configured */\n hasTools: boolean\n\n // --- Mutable per-iteration state ---\n\n /** Current assistant message ID (changes per iteration) */\n currentMessageId: string | null\n /** Accumulated text content for the current iteration */\n accumulatedContent: string\n\n // --- References ---\n\n /** Current messages array (read-only view) */\n messages: ReadonlyArray<ModelMessage>\n /** Generate a unique ID with the given prefix */\n createId: (prefix: string) => string\n /**\n * Capability bookkeeping for this request. Populated by middleware `setup`\n * hooks (via `provide` accessors) and read by later middleware (via `get`\n * accessors). Prefer the accessors returned by `createCapability` over using\n * this directly. Orthogonal to `context` (the user runtime context).\n */\n capabilities: CapabilityRegistry\n /**\n * Read a provided capability by its handle. Equivalent to the handle's own\n * `get` accessor (`getX(ctx)`); throws if the capability was never provided.\n */\n get: <TValue>(capability: Capability<TValue>) => TValue\n /**\n * Read a capability by its handle, returning `undefined` if it was never\n * provided (never throws).\n */\n getOptional: <TValue>(capability: Capability<TValue>) => TValue | undefined\n /**\n * Provide a capability value. Equivalent to the handle's own `provide`\n * accessor (`provideX(ctx, value)`). Typically called from `setup`.\n */\n provide: <TValue>(capability: Capability<TValue>, value: TValue) => void\n}\n\n// ===========================\n// Config passed to onConfig\n// ===========================\n\n/**\n * Chat configuration that middleware can observe or transform.\n * This is a subset of the chat engine's effective configuration\n * that middleware is allowed to modify.\n */\nexport interface ChatMiddlewareConfig {\n /** Canonical conversation history. Middleware and persistence read this. */\n messages: Array<ModelMessage>\n /** Provider-only context. Defaults to `messages` when it is not set. */\n providerMessages?: Array<ModelMessage> | undefined\n systemPrompts: Array<SystemPrompt>\n tools: Array<Tool>\n resume?: Array<RunAgentResumeItem> | undefined\n resumeToolState?: ChatResumeToolState | undefined\n metadata?: Record<string, unknown> | undefined\n modelOptions?: Record<string, unknown> | undefined\n}\n\n/**\n * Tool decisions reconstructed by server-side middleware from validated resume\n * entries. This lets empty-message interrupt resumes continue tool execution\n * without relying on client message history.\n */\nexport interface ChatResumeToolState {\n approvals?: ReadonlyMap<string, ToolApprovalResolution> | undefined\n clientToolResults?: ReadonlyMap<string, unknown> | undefined\n genericInterrupts?:\n | ReadonlyMap<string, ChatResumeGenericResolution>\n | undefined\n /** Durable generic requests reconstructed by server middleware. */\n genericInterruptRequests?:\n | ReadonlyMap<\n string,\n GenericInterruptRequest<InterruptDefinition<any, any, any, any>>\n >\n | undefined\n deniedToolResults?: ReadonlyMap<string, unknown> | undefined\n cancelledToolCallIds?: ReadonlySet<string> | undefined\n}\n\nexport type ChatResumeGenericResolution =\n | { interruptId: string; status: 'resolved'; payload: unknown }\n | { interruptId: string; status: 'cancelled'; payload?: never }\n\n/**\n * Config passed to onStructuredOutputConfig.\n *\n * Mirrors ChatMiddlewareConfig minus `tools` (the final structured-output call\n * is a single typed-response request, not an agentic loop — tools cannot be\n * forwarded to it), plus the `outputSchema` being sent to the provider.\n * Middleware may transform the schema (e.g., inject $defs, strip\n * vendor-incompatible keywords) by returning a partial that includes\n * `outputSchema`.\n */\nexport interface StructuredOutputMiddlewareConfig extends Omit<\n ChatMiddlewareConfig,\n 'tools'\n> {\n /** JSON Schema being sent to the provider for structured output. */\n outputSchema: JSONSchema\n}\n\n// ===========================\n// Tool Call Hook Context\n// ===========================\n\n/**\n * Context provided to tool call hooks (onBeforeToolCall / onAfterToolCall).\n */\nexport interface ToolCallHookContext {\n /** The tool call being executed */\n toolCall: ToolCall\n /** The resolved tool definition, if found */\n tool: Tool | undefined\n /** Parsed arguments for the tool call */\n args: unknown\n /** Name of the tool */\n toolName: string\n /** ID of the tool call */\n toolCallId: string\n}\n\n/**\n * Decision returned from onBeforeToolCall.\n * - undefined/void: continue with normal execution\n * - { type: 'transformArgs', args }: replace args used for execution\n * - { type: 'skip', result }: skip execution, use provided result\n * - { type: 'abort', reason }: abort the entire chat run\n */\nexport type BeforeToolCallDecision =\n | void\n | undefined\n | null\n | { type: 'transformArgs'; args: unknown }\n | { type: 'skip'; result: unknown }\n | { type: 'abort'; reason?: string }\n\n/**\n * Outcome information provided to onAfterToolCall.\n */\nexport interface AfterToolCallInfo {\n /** The tool call that was executed */\n toolCall: ToolCall\n /** The resolved tool definition */\n tool: Tool | undefined\n /** Name of the tool */\n toolName: string\n /** ID of the tool call */\n toolCallId: string\n /** Whether the execution succeeded */\n ok: boolean\n /** Duration of tool execution in milliseconds */\n duration: number\n /** The result (if ok) or error (if not ok) */\n result?: unknown\n error?: unknown\n}\n\n// ===========================\n// Iteration Info\n// ===========================\n\n/**\n * Information passed to onIteration at the start of each agent loop iteration.\n */\nexport interface IterationInfo {\n /** 0-based iteration index */\n iteration: number\n /** The assistant message ID created for this iteration */\n messageId: string\n}\n\n// ===========================\n// Tool Phase Complete Info\n// ===========================\n\n/**\n * Aggregate information passed to onToolPhaseComplete after all tool calls\n * in an iteration have been processed.\n */\nexport interface ToolPhaseCompleteInfo {\n /** Tool calls that were assigned to the assistant message */\n toolCalls: Array<ToolCall>\n /** Completed tool results */\n results: Array<{\n toolCallId: string\n toolName: string\n result: unknown\n duration?: number\n }>\n /** Tools that need user approval */\n needsApproval: Array<{\n toolCallId: string\n toolName: string\n input: unknown\n approvalId: string\n }>\n /** Tools that need client-side execution */\n needsClientExecution: Array<{\n toolCallId: string\n toolName: string\n input: unknown\n }>\n}\n\n// ===========================\n// Usage Info\n// ===========================\n\n/**\n * Token usage statistics passed to the onUsage hook.\n * Extracted from the RUN_FINISHED chunk when usage data is present.\n *\n * Includes optional provider-reported `cost`/`costDetails` (see {@link TokenUsage}).\n * Kept as an interface extending `TokenUsage` to preserve declaration merging for\n * this publicly exported type.\n */\nexport interface UsageInfo extends TokenUsage {}\n\n// ===========================\n// Terminal Hook Info\n// ===========================\n\n/**\n * Information passed to onFinish.\n */\nexport interface FinishInfo {\n /** The finish reason from the last model response */\n finishReason: string | null\n /** Total duration of the chat run in milliseconds */\n duration: number\n /** Final accumulated text content */\n content: string\n /** Final usage totals, if available (optionally including provider-reported cost) */\n usage?: TokenUsage | undefined\n}\n\n/**\n * Information passed to onAbort.\n */\nexport interface AbortInfo {\n /** The reason for the abort, if provided */\n reason?: string\n /** Duration until abort in milliseconds */\n duration: number\n /**\n * True only when the abort came from an explicit, out-of-band cancel (e.g. a\n * cancel endpoint setting `RunRecord.cancelRequested`), never from a mere\n * client disconnect.\n *\n * A disconnect and a user pressing \"stop\" are the SAME connection close on\n * the wire, so consumers must not infer intent from an abort alone. Middleware\n * that tears down expensive resources reads this to distinguish \"the viewer\n * left, keep going\" from \"the user wants this stopped\". Populated from the\n * abort reason: `true` exactly when the run was aborted with `RUN_CANCEL_REASON`\n * (matched with `===`, so an arbitrary error message can never be read as a\n * deliberate cancel), `false` for every other abort. The durable channel is\n * separate — middleware that must also catch a cancel recorded on a different\n * host reads `RunRecord.cancelRequested` in addition to this flag.\n */\n cancelRequested?: boolean\n}\n\n/**\n * Information passed to onError.\n */\nexport interface ErrorInfo {\n /** The error that caused the failure */\n error: unknown\n /** Duration until error in milliseconds */\n duration: number\n}\n\n/**\n * Saves subagent runs while a router owns the turn.\n * `withPersistence` sets this. `chat()` calls it. Apps do not.\n */\nexport interface RoutedSubagentPersistence {\n start: (input: {\n threadId: string\n runId: string\n messages: ReadonlyArray<UIMessage | ModelMessage>\n /**\n * The run's resume entries: answers to earlier child interrupts, plus any\n * the parent answers itself.\n */\n resume?: ReadonlyArray<RunAgentResumeItem>\n }) => Promise<void>\n chunk: (input: {\n threadId: string\n runId: string\n chunk: StreamChunk\n }) => Promise<void>\n finish: (input: { threadId: string; runId: string }) => Promise<void>\n /** The run stopped because a child waits for outside input. */\n suspend?: (input: {\n threadId: string\n runId: string\n interrupts: ReadonlyArray<Interrupt>\n }) => Promise<void>\n abort: (input: {\n threadId: string\n runId: string\n error?: unknown\n }) => Promise<void>\n}\n\n// ===========================\n// Middleware Interface\n// ===========================\n\n/**\n * Chat middleware interface.\n *\n * All hooks are optional. Middleware is composed in array order:\n * - `onConfig`: config piped through middlewares in order (first transform influences later)\n * - `onChunk`: each output chunk is fed into the next middleware in order\n *\n * @example Logging middleware\n * ```ts\n * const loggingMiddleware: ChatMiddleware = {\n * name: 'logging',\n * onStart(ctx) { console.log('Chat started', ctx.requestId) },\n * onChunk(ctx, chunk) { console.log('Chunk:', chunk.type) },\n * onFinish(ctx, info) { console.log('Done:', info.duration, 'ms') },\n * }\n * ```\n *\n * @example Redaction middleware\n * ```ts\n * const redactionMiddleware: ChatMiddleware = {\n * name: 'redaction',\n * onChunk(ctx, chunk) {\n * if (chunk.type === 'TEXT_MESSAGE_CONTENT') {\n * return { ...chunk, delta: redact(chunk.delta) }\n * }\n * },\n * }\n * ```\n */\nexport interface ChatMiddleware<\n TContext = unknown,\n TInterruptDefinitions extends AnyInterruptDefinition = never,\n> {\n /** Optional name for debugging and identification */\n name?: string\n\n /**\n * Present when this middleware stores subagent runs.\n * The router calls it. An app does not set it.\n */\n routedSubagentPersistence?: RoutedSubagentPersistence\n\n /**\n * Called at a lifecycle boundary. Return interrupt requests to pause the run.\n * Requests from every middleware in the same boundary form one batch.\n */\n onInterruptBoundary?: (\n ctx: ChatMiddlewareContext<TContext> & { phase: InterruptBoundaryPhase },\n ) =>\n | InterruptBoundaryResult<TInterruptDefinitions>\n | Promise<InterruptBoundaryResult<TInterruptDefinitions>>\n\n /**\n * Called on a continuation run after the client answers registered interrupts.\n * Return `toolResume` to decide whether pending tools continue, cancel, or stop.\n */\n onInterruptResolution?: BivariantInterruptResolutionHook<\n TContext,\n TInterruptDefinitions\n >\n\n /**\n * Capabilities this middleware requires. `chat()` validates that some\n * middleware (or the adapter) provides each one; unsatisfied requirements are\n * a compile-time error (array coverage / builder) and a runtime error before\n * the adapter runs.\n */\n requires?: ReadonlyArray<CapabilityHandle>\n\n /**\n * Capabilities this middleware provides. Each declared capability MUST be\n * provided (via its `provide` accessor) inside `setup`, or `chat()` throws\n * after the setup phase.\n */\n provides?: ReadonlyArray<CapabilityHandle>\n\n /**\n * Capabilities this middleware uses if present but does not require.\n * Non-gating: never causes a validation error. Read with\n * `getX(ctx, { optional: true })`.\n */\n optionalRequires?: ReadonlyArray<CapabilityHandle>\n\n /**\n * Provisioning hook. Runs FIRST — before `onConfig` (init) — across all\n * middleware in array order. Use it to call `provide` accessors so later\n * middleware (`onConfig` onward) can consume the capabilities. Receives the\n * stable context; does NOT receive the mutable config.\n */\n setup?: (ctx: ChatMiddlewareContext<TContext>) => void | Promise<void>\n\n /**\n * Called to observe or transform the chat configuration.\n * Called at init and at the beginning of each agent iteration.\n *\n * Return a partial config to merge with the current config, or void to pass through.\n * Only the fields you return are overwritten — everything else is preserved.\n */\n onConfig?: (\n ctx: ChatMiddlewareContext<TContext>,\n config: ChatMiddlewareConfig,\n ) =>\n | void\n | null\n | Partial<ChatMiddlewareConfig>\n | Promise<void | null | Partial<ChatMiddlewareConfig>>\n\n /**\n * Called at the start of the final structured-output call (when the chat\n * was invoked with outputSchema). Pipes through middleware in order, like\n * onConfig, but with access to the JSON Schema being sent to the provider.\n *\n * Return a partial to shallow-merge into the current config, or void to\n * pass through.\n *\n * Fires BEFORE onConfig at the structured-output boundary. onConfig also\n * re-fires at the same boundary with ctx.phase === 'structuredOutput',\n * receiving the post-onStructuredOutputConfig view of the config (minus\n * outputSchema). Use onConfig for general-purpose transforms that apply\n * to every adapter call; use this hook when you need to transform the\n * outputSchema or apply structured-output-specific behavior.\n */\n onStructuredOutputConfig?: (\n ctx: ChatMiddlewareContext<TContext>,\n config: StructuredOutputMiddlewareConfig,\n ) =>\n | void\n | null\n | Partial<StructuredOutputMiddlewareConfig>\n | Promise<void | null | Partial<StructuredOutputMiddlewareConfig>>\n\n /**\n * Called when the chat run starts (after initial onConfig).\n */\n onStart?: (ctx: ChatMiddlewareContext<TContext>) => void | Promise<void>\n\n /**\n * Called at the start of each agent loop iteration, after a new assistant message ID\n * is created. Use this to observe iteration boundaries.\n */\n onIteration?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: IterationInfo,\n ) => void | Promise<void>\n\n /**\n * Called when the engine is deciding whether to start another agent-loop\n * iteration (after a tool phase or between model turns).\n *\n * Return `false` to stop further iterations. Return `true`, `void`, or\n * `undefined` to allow continuation. Combined with AND semantics across\n * middleware and with `agentLoopStrategy` — any `false` stops the loop.\n *\n * Does not abort the run: the stream finishes normally with the current\n * messages. Use `ctx.abort()` only when you need a hard abort.\n *\n * Receives the same {@link AgentLoopState} passed to strategies\n * (`iterationCount`, `toolCallCount`, `lastTurnToolCallCount`, etc.).\n */\n onShouldContinue?: (\n ctx: ChatMiddlewareContext<TContext>,\n state: AgentLoopState,\n ) => boolean | void | Promise<boolean | void>\n\n /**\n * Called for every chunk yielded by chat().\n * Can observe, transform, expand, or drop chunks.\n *\n * @returns void (pass through), chunk (replace), chunk[] (expand), null (drop)\n */\n onChunk?: (\n ctx: ChatMiddlewareContext<TContext>,\n chunk: StreamChunk,\n ) =>\n | void\n | StreamChunk\n | Array<StreamChunk>\n | null\n | Promise<void | StreamChunk | Array<StreamChunk> | null>\n\n /**\n * Called before a tool is executed.\n * Can observe, transform args, skip execution, or abort the run.\n */\n onBeforeToolCall?: (\n ctx: ChatMiddlewareContext<TContext>,\n hookCtx: ToolCallHookContext,\n ) => BeforeToolCallDecision | Promise<BeforeToolCallDecision>\n\n /**\n * Called after a tool execution completes (success or failure).\n */\n onAfterToolCall?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: AfterToolCallInfo,\n ) => void | Promise<void>\n\n /**\n * Called after all tool calls in an iteration have been processed.\n * Provides aggregate data about tool execution results, approvals, and client tools.\n */\n onToolPhaseComplete?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: ToolPhaseCompleteInfo,\n ) => void | Promise<void>\n\n /**\n * Called when usage data is available from a RUN_FINISHED chunk.\n * Called once per model iteration that reports usage.\n */\n onUsage?: (\n ctx: ChatMiddlewareContext<TContext>,\n usage: UsageInfo,\n ) => void | Promise<void>\n\n /**\n * Called when the chat run completes normally.\n * Exactly one of onFinish/onAbort/onError will be called per run.\n */\n onFinish?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: FinishInfo,\n ) => void | Promise<void>\n\n /**\n * Called when the chat run is aborted.\n * Exactly one of onFinish/onAbort/onError will be called per run.\n */\n onAbort?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: AbortInfo,\n ) => void | Promise<void>\n\n /**\n * Called when the chat run encounters an unhandled error.\n * Exactly one of onFinish/onAbort/onError will be called per run.\n */\n onError?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: ErrorInfo,\n ) => void | Promise<void>\n\n /**\n * Sandbox file-event hooks. Fire when a sandbox provided by `withSandbox` is\n * active during the run and a file is created/changed/deleted. Server-side.\n */\n sandbox?: ChatSandboxHooks<TContext>\n}\n\n/** A `ChatMiddleware` with a permissive context — for use as a constraint. */\n/** A permissive middleware constraint that retains the definition parameter. */\nexport type AnyChatMiddleware = ChatMiddleware<any, any>\n"],"mappings":";AAiGA,IAAa,4BAA4B;CACvC;CACA;CACA;CACA;AACF;AAIA,IAAa,yBAAyB;CAAC;CAAY;CAAU;AAAM"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/activities/chat/middleware/types.ts"],"sourcesContent":["import type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from '@standard-schema/spec'\nimport type {\n AgentLoopState,\n EmitCustomEventOptions,\n Interrupt,\n JSONSchema,\n ModelMessage,\n UIMessage,\n RunAgentResumeItem,\n StreamChunk,\n TokenUsage,\n Tool,\n ToolCall,\n} from '../../../types'\nimport type { SystemPrompt } from '../../../system-prompts'\nimport type { ToolApprovalResolution } from '../../../interrupts'\nimport type {\n GenericInterruptRequest,\n InterruptDefinition,\n} from '../../../interrupt-definition'\nimport type {\n Capability,\n CapabilityHandle,\n CapabilityRegistry,\n} from './capabilities'\n\n/** A file change observed inside a sandbox during a chat run. */\nexport interface SandboxFileEvent {\n type: 'create' | 'change' | 'delete'\n /** Absolute path inside the sandbox (under the workspace root). */\n path: string\n timestamp: number\n}\n\n/** The file event a sandbox hook receives: the serializable {@link SandboxFileEvent}\n * plus lazy, git-backed content accessors. Accessors compute on call, so a hook\n * that only reads `path`/`type` pays nothing. Never present on the serialized\n * `sandbox.file` CUSTOM chunk. */\nexport interface SandboxFileHookEvent extends SandboxFileEvent {\n /** Content at the session baseline (`''` for a new file or non-git workspace). */\n before: () => Promise<string>\n /** Current content (`''` when the event is a delete). */\n after: () => Promise<string>\n /** Unified patch vs the session baseline (synthesized add-patch when non-git). */\n diff: () => Promise<string>\n}\n\n/**\n * Sandbox file-event hooks a chat middleware can declare. Fire server-side for\n * every file create/change/delete observed in the sandbox during the run.\n */\nexport interface ChatSandboxHooks<TContext = unknown> {\n onFile?: (\n ctx: ChatMiddlewareContext<TContext>,\n e: SandboxFileHookEvent,\n ) => void | Promise<void>\n onFileCreate?: (\n ctx: ChatMiddlewareContext<TContext>,\n e: SandboxFileHookEvent,\n ) => void | Promise<void>\n onFileChange?: (\n ctx: ChatMiddlewareContext<TContext>,\n e: SandboxFileHookEvent,\n ) => void | Promise<void>\n onFileDelete?: (\n ctx: ChatMiddlewareContext<TContext>,\n e: SandboxFileHookEvent,\n ) => void | Promise<void>\n}\n\n// ===========================\n// Middleware Context\n// ===========================\n\n/**\n * Phase of the chat middleware lifecycle.\n * - 'init': Initial config transform before the chat engine starts\n * - 'beforeModel': Before each adapter chatStream call (per agent iteration)\n * - 'afterModel': After each adapter chatStream call (per agent iteration)\n * - 'modelStream': During model streaming\n * - 'beforeTools': Before tool execution phase\n * - 'afterTools': After tool execution phase\n * - 'structuredOutput': During the final structured-output adapter call (set\n * for chunks from adapter.structuredOutputStream or the synthesized fallback)\n */\nexport type ChatMiddlewarePhase =\n | 'init'\n | 'beforeModel'\n | 'afterModel'\n | 'modelStream'\n | 'beforeTools'\n | 'afterTools'\n | 'structuredOutput'\n\nexport const INTERRUPT_BOUNDARY_PHASES = [\n 'beforeModel',\n 'afterModel',\n 'beforeTools',\n 'afterTools',\n] as const\n\nexport type InterruptBoundaryPhase = (typeof INTERRUPT_BOUNDARY_PHASES)[number]\n\nexport const INTERRUPT_TOOL_RESUMES = ['continue', 'cancel', 'stop'] as const\n\nexport type InterruptToolResume = (typeof INTERRUPT_TOOL_RESUMES)[number]\n\ntype AnyInterruptDefinition = InterruptDefinition<any, any, any, any>\n\ntype InterruptResponse<TDefinition> =\n TDefinition extends InterruptDefinition<any, any, infer TResponseSchema, any>\n ? TResponseSchema extends StandardSchemaV1<any, infer TResponse>\n ? TResponse\n : TResponseSchema extends StandardJSONSchemaV1<any, infer TResponse>\n ? TResponse\n : unknown\n : unknown\n\nexport type GenericInterruptResolution<\n TDefinition extends AnyInterruptDefinition,\n> = TDefinition extends AnyInterruptDefinition\n ?\n | {\n readonly request: GenericInterruptRequest<TDefinition>\n readonly status: 'resolved'\n readonly response: InterruptResponse<TDefinition>\n }\n | {\n readonly request: GenericInterruptRequest<TDefinition>\n readonly status: 'cancelled'\n readonly response?: never\n }\n : never\n\nexport interface InterruptResolutionCollection<\n TDefinitions extends AnyInterruptDefinition = AnyInterruptDefinition,\n> {\n for: <\n TDefinition extends ([TDefinitions] extends [never]\n ? AnyInterruptDefinition\n : TDefinitions),\n >(\n definition: TDefinition,\n ) => ReadonlyArray<GenericInterruptResolution<TDefinition>>\n all: {\n (): ReadonlyArray<GenericInterruptResolution<TDefinitions>>\n <const TSelected extends ReadonlyArray<TDefinitions>>(\n ...definitions: TSelected\n ): ReadonlyArray<GenericInterruptResolution<TSelected[number]>>\n }\n}\n\ntype BivariantInterruptResolutionHook<\n TContext,\n TDefinitions extends AnyInterruptDefinition,\n> = InterruptResolutionHookSignature<TContext, TDefinitions>['call']\n\ndeclare abstract class InterruptResolutionHookSignature<\n TContext,\n TDefinitions extends AnyInterruptDefinition,\n> {\n abstract call(\n ctx: ChatMiddlewareContext<TContext>,\n resolutions: InterruptResolutionCollection<TDefinitions>,\n ): InterruptResolutionResult | Promise<InterruptResolutionResult>\n}\n\nexport type InterruptBoundaryResult<\n TDefinitions extends AnyInterruptDefinition = AnyInterruptDefinition,\n> =\n | undefined\n | {\n readonly interrupts: ReadonlyArray<GenericInterruptRequest<TDefinitions>>\n }\n\nexport type InterruptResolutionResult = void | {\n readonly toolResume: InterruptToolResume\n}\n\n/**\n * Stable context object passed to all middleware hooks.\n * Created once per chat() invocation and shared across all hooks.\n */\nexport interface ChatMiddlewareContext<TContext = unknown> {\n /** Unique identifier for this chat request */\n requestId: string\n /** Unique identifier for this stream */\n streamId: string\n /** AG-UI run identifier for correlating client and server events */\n runId: string\n /** Interrupted or parent run correlated with this continuation. */\n parentRunId?: string\n /**\n * Set when this run is a subagent. The id on the child's `SUBAGENT_STARTED`\n * and on every chunk it streams. Absent on a top-level run.\n */\n subagentRunId?: string\n /**\n * AG-UI thread identifier — a stable per-conversation ID used to\n * correlate client and server devtools events. Resolves to the\n * caller-provided `threadId` (or legacy `conversationId`), or an\n * auto-generated value when neither is supplied.\n */\n threadId: string\n /**\n * @deprecated Use `threadId` instead. Retained as an alias of\n * `threadId` so middleware written before the AG-UI rename keeps\n * working unchanged. Will be removed in a future major release.\n */\n conversationId?: string\n /** Current lifecycle phase */\n phase: ChatMiddlewarePhase\n /** Current agent loop iteration (0-indexed) */\n iteration: number\n /** Running count of chunks yielded so far */\n chunkIndex: number\n /** Abort signal from the chat request */\n signal?: AbortSignal\n /** Abort the chat run with a reason */\n abort: (reason?: string) => void\n /**\n * Push a `CUSTOM` chunk onto the chat stream immediately.\n * The engine yields it as soon as it can (including while `onConfig`\n * is still awaiting work such as a summarize call). Durability then\n * flushes the event on its own, unless you pass `{ batch: true }`.\n */\n emitCustomEvent: (\n name: string,\n value: Record<string, any>,\n options?: EmitCustomEventOptions,\n ) => void\n /** Runtime context provided by chat() options */\n context: TContext\n /**\n * Defer a non-blocking side-effect promise.\n * Deferred promises do not block streaming and are awaited\n * after the terminal hook (onFinish/onAbort/onError).\n */\n defer: (promise: Promise<unknown>) => void\n\n // --- Provider / adapter info (immutable for the lifetime of the request) ---\n\n /**\n * Which activity this context describes — always `'chat'`. Present so the\n * chat context structurally satisfies the base `GenerationMiddlewareContext`,\n * letting an observe-only middleware authored against the base (e.g.\n * `otelMiddleware`) run on both chat and media activities.\n */\n activity: 'chat'\n /** Provider name (e.g., 'openai', 'anthropic') */\n provider: string\n /** Model identifier (e.g., 'gpt-5.5') */\n model: string\n /** Source of the chat invocation — always 'server' for server-side chat */\n source: 'client' | 'server'\n /** Whether the chat is streaming */\n streaming: boolean\n\n // --- Config-derived info (may update per-iteration via onConfig) ---\n\n /** System prompts configured for this chat */\n systemPrompts: Array<SystemPrompt>\n /** Names of configured tools, if any */\n toolNames?: Array<string>\n /** Flattened generation options (metadata) */\n options?: Record<string, unknown> | undefined\n /** Provider-specific model options */\n modelOptions?: Record<string, unknown> | undefined\n\n // --- Computed info ---\n\n /** Number of messages at the start of the request */\n messageCount: number\n /** Whether tools are configured */\n hasTools: boolean\n\n // --- Mutable per-iteration state ---\n\n /** Current assistant message ID (changes per iteration) */\n currentMessageId: string | null\n /** Accumulated text content for the current iteration */\n accumulatedContent: string\n\n // --- References ---\n\n /** Current messages array (read-only view) */\n messages: ReadonlyArray<ModelMessage>\n /** Generate a unique ID with the given prefix */\n createId: (prefix: string) => string\n /**\n * Capability bookkeeping for this request. Populated by middleware `setup`\n * hooks (via `provide` accessors) and read by later middleware (via `get`\n * accessors). Prefer the accessors returned by `createCapability` over using\n * this directly. Orthogonal to `context` (the user runtime context).\n */\n capabilities: CapabilityRegistry\n /**\n * Read a provided capability by its handle. Equivalent to the handle's own\n * `get` accessor (`getX(ctx)`); throws if the capability was never provided.\n */\n get: <TValue>(capability: Capability<TValue>) => TValue\n /**\n * Read a capability by its handle, returning `undefined` if it was never\n * provided (never throws).\n */\n getOptional: <TValue>(capability: Capability<TValue>) => TValue | undefined\n /**\n * Provide a capability value. Equivalent to the handle's own `provide`\n * accessor (`provideX(ctx, value)`). Typically called from `setup`.\n */\n provide: <TValue>(capability: Capability<TValue>, value: TValue) => void\n}\n\n// ===========================\n// Config passed to onConfig\n// ===========================\n\n/**\n * Chat configuration that middleware can observe or transform.\n * This is a subset of the chat engine's effective configuration\n * that middleware is allowed to modify.\n */\nexport interface ChatMiddlewareConfig {\n /** Canonical conversation history. Middleware and persistence read this. */\n messages: Array<ModelMessage>\n /** Provider-only context. Defaults to `messages` when it is not set. */\n providerMessages?: Array<ModelMessage> | undefined\n systemPrompts: Array<SystemPrompt>\n tools: Array<Tool>\n resume?: Array<RunAgentResumeItem> | undefined\n resumeToolState?: ChatResumeToolState | undefined\n metadata?: Record<string, unknown> | undefined\n modelOptions?: Record<string, unknown> | undefined\n}\n\n/**\n * Tool decisions reconstructed by server-side middleware from validated resume\n * entries. This lets empty-message interrupt resumes continue tool execution\n * without relying on client message history.\n */\nexport interface ChatResumeToolState {\n approvals?: ReadonlyMap<string, ToolApprovalResolution> | undefined\n clientToolResults?: ReadonlyMap<string, unknown> | undefined\n clientToolErrors?: ReadonlyMap<string, string> | undefined\n genericInterrupts?:\n | ReadonlyMap<string, ChatResumeGenericResolution>\n | undefined\n /** Durable generic requests reconstructed by server middleware. */\n genericInterruptRequests?:\n | ReadonlyMap<\n string,\n GenericInterruptRequest<InterruptDefinition<any, any, any, any>>\n >\n | undefined\n deniedToolResults?: ReadonlyMap<string, unknown> | undefined\n cancelledToolCallIds?: ReadonlySet<string> | undefined\n}\n\nexport type ChatResumeGenericResolution =\n | { interruptId: string; status: 'resolved'; payload: unknown }\n | { interruptId: string; status: 'cancelled'; payload?: never }\n\n/**\n * Config passed to onStructuredOutputConfig.\n *\n * Mirrors ChatMiddlewareConfig minus `tools` (the final structured-output call\n * is a single typed-response request, not an agentic loop — tools cannot be\n * forwarded to it), plus the `outputSchema` being sent to the provider.\n * Middleware may transform the schema (e.g., inject $defs, strip\n * vendor-incompatible keywords) by returning a partial that includes\n * `outputSchema`.\n */\nexport interface StructuredOutputMiddlewareConfig extends Omit<\n ChatMiddlewareConfig,\n 'tools'\n> {\n /** JSON Schema being sent to the provider for structured output. */\n outputSchema: JSONSchema\n}\n\n// ===========================\n// Tool Call Hook Context\n// ===========================\n\n/**\n * Context provided to tool call hooks (onBeforeToolCall / onAfterToolCall).\n */\nexport interface ToolCallHookContext {\n /** The tool call being executed */\n toolCall: ToolCall\n /** The resolved tool definition, if found */\n tool: Tool | undefined\n /** Parsed arguments for the tool call */\n args: unknown\n /** Name of the tool */\n toolName: string\n /** ID of the tool call */\n toolCallId: string\n}\n\n/**\n * Decision returned from onBeforeToolCall.\n * - undefined/void: continue with normal execution\n * - { type: 'transformArgs', args }: replace args used for execution\n * - { type: 'skip', result }: skip execution, use provided result\n * - { type: 'abort', reason }: abort the entire chat run\n */\nexport type BeforeToolCallDecision =\n | void\n | undefined\n | null\n | { type: 'transformArgs'; args: unknown }\n | { type: 'skip'; result: unknown }\n | { type: 'abort'; reason?: string }\n\n/**\n * Outcome information provided to onAfterToolCall.\n */\nexport interface AfterToolCallInfo {\n /** The tool call that was executed */\n toolCall: ToolCall\n /** The resolved tool definition */\n tool: Tool | undefined\n /** Name of the tool */\n toolName: string\n /** ID of the tool call */\n toolCallId: string\n /** Whether the execution succeeded */\n ok: boolean\n /** Duration of tool execution in milliseconds */\n duration: number\n /** The result (if ok) or error (if not ok) */\n result?: unknown\n error?: unknown\n}\n\n// ===========================\n// Iteration Info\n// ===========================\n\n/**\n * Information passed to onIteration at the start of each agent loop iteration.\n */\nexport interface IterationInfo {\n /** 0-based iteration index */\n iteration: number\n /** The assistant message ID created for this iteration */\n messageId: string\n}\n\n// ===========================\n// Tool Phase Complete Info\n// ===========================\n\n/**\n * Aggregate information passed to onToolPhaseComplete after all tool calls\n * in an iteration have been processed.\n */\nexport interface ToolPhaseCompleteInfo {\n /** Tool calls that were assigned to the assistant message */\n toolCalls: Array<ToolCall>\n /** Completed tool results */\n results: Array<{\n toolCallId: string\n toolName: string\n result: unknown\n duration?: number\n }>\n /** Tools that need user approval */\n needsApproval: Array<{\n toolCallId: string\n toolName: string\n input: unknown\n approvalId: string\n }>\n /** Tools that need client-side execution */\n needsClientExecution: Array<{\n toolCallId: string\n toolName: string\n input: unknown\n }>\n}\n\n// ===========================\n// Usage Info\n// ===========================\n\n/**\n * Token usage statistics passed to the onUsage hook.\n * Extracted from the RUN_FINISHED chunk when usage data is present.\n *\n * Includes optional provider-reported `cost`/`costDetails` (see {@link TokenUsage}).\n * Kept as an interface extending `TokenUsage` to preserve declaration merging for\n * this publicly exported type.\n */\nexport interface UsageInfo extends TokenUsage {}\n\n// ===========================\n// Terminal Hook Info\n// ===========================\n\n/**\n * Information passed to onFinish.\n */\nexport interface FinishInfo {\n /** The finish reason from the last model response */\n finishReason: string | null\n /** Total duration of the chat run in milliseconds */\n duration: number\n /** Final accumulated text content */\n content: string\n /** Final usage totals, if available (optionally including provider-reported cost) */\n usage?: TokenUsage | undefined\n}\n\n/**\n * Information passed to onAbort.\n */\nexport interface AbortInfo {\n /** The reason for the abort, if provided */\n reason?: string\n /** Duration until abort in milliseconds */\n duration: number\n /**\n * True only when the abort came from an explicit, out-of-band cancel (e.g. a\n * cancel endpoint setting `RunRecord.cancelRequested`), never from a mere\n * client disconnect.\n *\n * A disconnect and a user pressing \"stop\" are the SAME connection close on\n * the wire, so consumers must not infer intent from an abort alone. Middleware\n * that tears down expensive resources reads this to distinguish \"the viewer\n * left, keep going\" from \"the user wants this stopped\". Populated from the\n * abort reason: `true` exactly when the run was aborted with `RUN_CANCEL_REASON`\n * (matched with `===`, so an arbitrary error message can never be read as a\n * deliberate cancel), `false` for every other abort. The durable channel is\n * separate — middleware that must also catch a cancel recorded on a different\n * host reads `RunRecord.cancelRequested` in addition to this flag.\n */\n cancelRequested?: boolean\n}\n\n/**\n * Information passed to onError.\n */\nexport interface ErrorInfo {\n /** The error that caused the failure */\n error: unknown\n /** Duration until error in milliseconds */\n duration: number\n}\n\n/**\n * Saves subagent runs while a router owns the turn.\n * `withPersistence` sets this. `chat()` calls it. Apps do not.\n */\nexport interface RoutedSubagentPersistence {\n start: (input: {\n threadId: string\n runId: string\n messages: ReadonlyArray<UIMessage | ModelMessage>\n /**\n * The run's resume entries: answers to earlier child interrupts, plus any\n * the parent answers itself.\n */\n resume?: ReadonlyArray<RunAgentResumeItem>\n }) => Promise<void>\n chunk: (input: {\n threadId: string\n runId: string\n chunk: StreamChunk\n }) => Promise<void>\n finish: (input: { threadId: string; runId: string }) => Promise<void>\n /** The run stopped because a child waits for outside input. */\n suspend?: (input: {\n threadId: string\n runId: string\n interrupts: ReadonlyArray<Interrupt>\n }) => Promise<void>\n abort: (input: {\n threadId: string\n runId: string\n error?: unknown\n }) => Promise<void>\n}\n\n// ===========================\n// Middleware Interface\n// ===========================\n\n/**\n * Chat middleware interface.\n *\n * All hooks are optional. Middleware is composed in array order:\n * - `onConfig`: config piped through middlewares in order (first transform influences later)\n * - `onChunk`: each output chunk is fed into the next middleware in order\n *\n * @example Logging middleware\n * ```ts\n * const loggingMiddleware: ChatMiddleware = {\n * name: 'logging',\n * onStart(ctx) { console.log('Chat started', ctx.requestId) },\n * onChunk(ctx, chunk) { console.log('Chunk:', chunk.type) },\n * onFinish(ctx, info) { console.log('Done:', info.duration, 'ms') },\n * }\n * ```\n *\n * @example Redaction middleware\n * ```ts\n * const redactionMiddleware: ChatMiddleware = {\n * name: 'redaction',\n * onChunk(ctx, chunk) {\n * if (chunk.type === 'TEXT_MESSAGE_CONTENT') {\n * return { ...chunk, delta: redact(chunk.delta) }\n * }\n * },\n * }\n * ```\n */\nexport interface ChatMiddleware<\n TContext = unknown,\n TInterruptDefinitions extends AnyInterruptDefinition = never,\n> {\n /** Optional name for debugging and identification */\n name?: string\n\n /**\n * Present when this middleware stores subagent runs.\n * The router calls it. An app does not set it.\n */\n routedSubagentPersistence?: RoutedSubagentPersistence\n\n /**\n * Called at a lifecycle boundary. Return interrupt requests to pause the run.\n * Requests from every middleware in the same boundary form one batch.\n */\n onInterruptBoundary?: (\n ctx: ChatMiddlewareContext<TContext> & { phase: InterruptBoundaryPhase },\n ) =>\n | InterruptBoundaryResult<TInterruptDefinitions>\n | Promise<InterruptBoundaryResult<TInterruptDefinitions>>\n\n /**\n * Called on a continuation run after the client answers registered interrupts.\n * Return `toolResume` to decide whether pending tools continue, cancel, or stop.\n */\n onInterruptResolution?: BivariantInterruptResolutionHook<\n TContext,\n TInterruptDefinitions\n >\n\n /**\n * Capabilities this middleware requires. `chat()` validates that some\n * middleware (or the adapter) provides each one; unsatisfied requirements are\n * a compile-time error (array coverage / builder) and a runtime error before\n * the adapter runs.\n */\n requires?: ReadonlyArray<CapabilityHandle>\n\n /**\n * Capabilities this middleware provides. Each declared capability MUST be\n * provided (via its `provide` accessor) inside `setup`, or `chat()` throws\n * after the setup phase.\n */\n provides?: ReadonlyArray<CapabilityHandle>\n\n /**\n * Capabilities this middleware uses if present but does not require.\n * Non-gating: never causes a validation error. Read with\n * `getX(ctx, { optional: true })`.\n */\n optionalRequires?: ReadonlyArray<CapabilityHandle>\n\n /**\n * Provisioning hook. Runs FIRST — before `onConfig` (init) — across all\n * middleware in array order. Use it to call `provide` accessors so later\n * middleware (`onConfig` onward) can consume the capabilities. Receives the\n * stable context; does NOT receive the mutable config.\n */\n setup?: (ctx: ChatMiddlewareContext<TContext>) => void | Promise<void>\n\n /**\n * Called to observe or transform the chat configuration.\n * Called at init and at the beginning of each agent iteration.\n *\n * Return a partial config to merge with the current config, or void to pass through.\n * Only the fields you return are overwritten — everything else is preserved.\n */\n onConfig?: (\n ctx: ChatMiddlewareContext<TContext>,\n config: ChatMiddlewareConfig,\n ) =>\n | void\n | null\n | Partial<ChatMiddlewareConfig>\n | Promise<void | null | Partial<ChatMiddlewareConfig>>\n\n /**\n * Called at the start of the final structured-output call (when the chat\n * was invoked with outputSchema). Pipes through middleware in order, like\n * onConfig, but with access to the JSON Schema being sent to the provider.\n *\n * Return a partial to shallow-merge into the current config, or void to\n * pass through.\n *\n * Fires BEFORE onConfig at the structured-output boundary. onConfig also\n * re-fires at the same boundary with ctx.phase === 'structuredOutput',\n * receiving the post-onStructuredOutputConfig view of the config (minus\n * outputSchema). Use onConfig for general-purpose transforms that apply\n * to every adapter call; use this hook when you need to transform the\n * outputSchema or apply structured-output-specific behavior.\n */\n onStructuredOutputConfig?: (\n ctx: ChatMiddlewareContext<TContext>,\n config: StructuredOutputMiddlewareConfig,\n ) =>\n | void\n | null\n | Partial<StructuredOutputMiddlewareConfig>\n | Promise<void | null | Partial<StructuredOutputMiddlewareConfig>>\n\n /**\n * Called when the chat run starts (after initial onConfig).\n */\n onStart?: (ctx: ChatMiddlewareContext<TContext>) => void | Promise<void>\n\n /**\n * Called at the start of each agent loop iteration, after a new assistant message ID\n * is created. Use this to observe iteration boundaries.\n */\n onIteration?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: IterationInfo,\n ) => void | Promise<void>\n\n /**\n * Called when the engine is deciding whether to start another agent-loop\n * iteration (after a tool phase or between model turns).\n *\n * Return `false` to stop further iterations. Return `true`, `void`, or\n * `undefined` to allow continuation. Combined with AND semantics across\n * middleware and with `agentLoopStrategy` — any `false` stops the loop.\n *\n * Does not abort the run: the stream finishes normally with the current\n * messages. Use `ctx.abort()` only when you need a hard abort.\n *\n * Receives the same {@link AgentLoopState} passed to strategies\n * (`iterationCount`, `toolCallCount`, `lastTurnToolCallCount`, etc.).\n */\n onShouldContinue?: (\n ctx: ChatMiddlewareContext<TContext>,\n state: AgentLoopState,\n ) => boolean | void | Promise<boolean | void>\n\n /**\n * Called for every chunk yielded by chat().\n * Can observe, transform, expand, or drop chunks.\n *\n * @returns void (pass through), chunk (replace), chunk[] (expand), null (drop)\n */\n onChunk?: (\n ctx: ChatMiddlewareContext<TContext>,\n chunk: StreamChunk,\n ) =>\n | void\n | StreamChunk\n | Array<StreamChunk>\n | null\n | Promise<void | StreamChunk | Array<StreamChunk> | null>\n\n /**\n * Called before a tool is executed.\n * Can observe, transform args, skip execution, or abort the run.\n */\n onBeforeToolCall?: (\n ctx: ChatMiddlewareContext<TContext>,\n hookCtx: ToolCallHookContext,\n ) => BeforeToolCallDecision | Promise<BeforeToolCallDecision>\n\n /**\n * Called after a tool execution completes (success or failure).\n */\n onAfterToolCall?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: AfterToolCallInfo,\n ) => void | Promise<void>\n\n /**\n * Called after all tool calls in an iteration have been processed.\n * Provides aggregate data about tool execution results, approvals, and client tools.\n */\n onToolPhaseComplete?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: ToolPhaseCompleteInfo,\n ) => void | Promise<void>\n\n /**\n * Called when usage data is available from a RUN_FINISHED chunk.\n * Called once per model iteration that reports usage.\n */\n onUsage?: (\n ctx: ChatMiddlewareContext<TContext>,\n usage: UsageInfo,\n ) => void | Promise<void>\n\n /**\n * Called when the chat run completes normally.\n * Exactly one of onFinish/onAbort/onError will be called per run.\n */\n onFinish?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: FinishInfo,\n ) => void | Promise<void>\n\n /**\n * Called when the chat run is aborted.\n * Exactly one of onFinish/onAbort/onError will be called per run.\n */\n onAbort?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: AbortInfo,\n ) => void | Promise<void>\n\n /**\n * Called when the chat run encounters an unhandled error.\n * Exactly one of onFinish/onAbort/onError will be called per run.\n */\n onError?: (\n ctx: ChatMiddlewareContext<TContext>,\n info: ErrorInfo,\n ) => void | Promise<void>\n\n /**\n * Sandbox file-event hooks. Fire when a sandbox provided by `withSandbox` is\n * active during the run and a file is created/changed/deleted. Server-side.\n */\n sandbox?: ChatSandboxHooks<TContext>\n}\n\n/** A `ChatMiddleware` with a permissive context — for use as a constraint. */\n/** A permissive middleware constraint that retains the definition parameter. */\nexport type AnyChatMiddleware = ChatMiddleware<any, any>\n"],"mappings":";AAiGA,IAAa,4BAA4B;CACvC;CACA;CACA;CACA;AACF;AAIA,IAAa,yBAAyB;CAAC;CAAY;CAAU;AAAM"}
@@ -1,4 +1,4 @@
1
- import { ContentPart, UIMessage } from '../../../types.js';
1
+ import { ContentPart, ToolResultOutcome, UIMessage } from '../../../types.js';
2
2
  import { ToolCallState, ToolResultState } from './types.js';
3
3
  /**
4
4
  * Update or add a text part to a message.
@@ -22,7 +22,7 @@ export declare function updateToolCallPart(messages: Array<UIMessage>, messageId
22
22
  /**
23
23
  * Update or add a tool result part to a message.
24
24
  */
25
- export declare function updateToolResultPart(messages: Array<UIMessage>, messageId: string, toolCallId: string, content: string | Array<ContentPart>, state: ToolResultState, error?: string): Array<UIMessage>;
25
+ export declare function updateToolResultPart(messages: Array<UIMessage>, messageId: string, toolCallId: string, content: string | Array<ContentPart>, state: ToolResultState, error?: string, outcome?: ToolResultOutcome): Array<UIMessage>;
26
26
  /**
27
27
  * Update a tool call part with approval request metadata.
28
28
  */
@@ -1,3 +1,4 @@
1
+ import { isRedactedThinkingId } from "../../../utilities/reasoning-encrypted-value.js";
1
2
  import { parsePartialJSON } from "./json-parser.js";
2
3
  //#region src/activities/chat/stream/message-updaters.ts
3
4
  /**
@@ -63,7 +64,7 @@ function updateToolCallPart(messages, messageId, toolCall) {
63
64
  /**
64
65
  * Update or add a tool result part to a message.
65
66
  */
66
- function updateToolResultPart(messages, messageId, toolCallId, content, state, error) {
67
+ function updateToolResultPart(messages, messageId, toolCallId, content, state, error, outcome) {
67
68
  return messages.map((msg) => {
68
69
  if (msg.id !== messageId) return msg;
69
70
  const parts = [...msg.parts];
@@ -73,6 +74,7 @@ function updateToolResultPart(messages, messageId, toolCallId, content, state, e
73
74
  toolCallId,
74
75
  content,
75
76
  state,
77
+ ...outcome !== void 0 && { outcome },
76
78
  ...error && { error }
77
79
  };
78
80
  if (resultPartIndex >= 0) parts[resultPartIndex] = toolResultPart;
@@ -283,11 +285,13 @@ function updateThinkingPart(messages, messageId, stepId, content, signature) {
283
285
  if (candidate?.type === "thinking") adopted = candidate;
284
286
  }
285
287
  const nextSignature = signature ?? adopted?.signature;
288
+ const redacted = isRedactedThinkingId(stepId) || adopted?.redacted === true;
286
289
  const thinkingPart = {
287
290
  type: "thinking",
288
291
  content,
289
292
  stepId,
290
- ...nextSignature && { signature: nextSignature }
293
+ ...nextSignature && { signature: nextSignature },
294
+ ...redacted && { redacted: true }
291
295
  };
292
296
  if (thinkingPartIndex >= 0) parts[thinkingPartIndex] = thinkingPart;
293
297
  else parts.push(thinkingPart);
@@ -1 +1 @@
1
- {"version":3,"file":"message-updaters.js","names":[],"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 /** Parsed input — set when the arguments are complete. */\n input?: unknown\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 // Same for the parsed input: it's supplied once at completion, so\n // subsequent arg-less updates (approval, etc.) must not drop it.\n const input = toolCall.input ?? existing?.input\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, output and parsed input from the existing part\n ...(existing?.approval && { approval: { ...existing.approval } }),\n ...(existing?.output !== undefined && { output: existing.output }),\n ...(input !== undefined && { input }),\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 let thinkingPartIndex = parts.findIndex(\n (p) => p.type === 'thinking' && p.stepId === stepId,\n )\n\n // A hydrated message carries its thinking without a stepId: the stored form\n // has no field for one, so `modelMessageToUIMessage` cannot put it back.\n // When a run is rejoined mid-stream the replayed reasoning is keyed by\n // stepId, matches nothing, and gets appended -- leaving a second thinking\n // part sitting after the answer text. Adopt the first stepId-less thinking\n // part instead, so the replay lands on the part it belongs to. Live\n // streaming always writes a stepId, so the only parts this can match are\n // hydrated ones.\n let adopted: ThinkingPart | undefined\n if (thinkingPartIndex < 0) {\n thinkingPartIndex = parts.findIndex(\n (p) => p.type === 'thinking' && p.stepId === undefined,\n )\n const candidate = parts[thinkingPartIndex]\n if (candidate?.type === 'thinking') adopted = candidate\n }\n\n // Keep the signature the hydrated part already had when this update does\n // not carry one; losing it would strip the provider's encrypted reasoning\n // from a message that is about to be sent back.\n const nextSignature = signature ?? adopted?.signature\n\n const thinkingPart: ThinkingPart = {\n type: 'thinking',\n content,\n stepId,\n ...(nextSignature && { signature: nextSignature }),\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"],"mappings":"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"}
1
+ {"version":3,"file":"message-updaters.js","names":[],"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 { isRedactedThinkingId } from '../../../utilities/reasoning-encrypted-value'\nimport { parsePartialJSON } from './json-parser'\nimport type {\n ContentPart,\n StructuredOutputPart,\n ThinkingPart,\n ToolCallPart,\n ToolResultOutcome,\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 /** Parsed input — set when the arguments are complete. */\n input?: unknown\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 // Same for the parsed input: it's supplied once at completion, so\n // subsequent arg-less updates (approval, etc.) must not drop it.\n const input = toolCall.input ?? existing?.input\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, output and parsed input from the existing part\n ...(existing?.approval && { approval: { ...existing.approval } }),\n ...(existing?.output !== undefined && { output: existing.output }),\n ...(input !== undefined && { input }),\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 outcome?: ToolResultOutcome,\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 ...(outcome !== undefined && { outcome }),\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 let thinkingPartIndex = parts.findIndex(\n (p) => p.type === 'thinking' && p.stepId === stepId,\n )\n\n // A hydrated message carries its thinking without a stepId: the stored form\n // has no field for one, so `modelMessageToUIMessage` cannot put it back.\n // When a run is rejoined mid-stream the replayed reasoning is keyed by\n // stepId, matches nothing, and gets appended -- leaving a second thinking\n // part sitting after the answer text. Adopt the first stepId-less thinking\n // part instead, so the replay lands on the part it belongs to. Live\n // streaming always writes a stepId, so the only parts this can match are\n // hydrated ones.\n let adopted: ThinkingPart | undefined\n if (thinkingPartIndex < 0) {\n thinkingPartIndex = parts.findIndex(\n (p) => p.type === 'thinking' && p.stepId === undefined,\n )\n const candidate = parts[thinkingPartIndex]\n if (candidate?.type === 'thinking') adopted = candidate\n }\n\n // Keep the signature the hydrated part already had when this update does\n // not carry one; losing it would strip the provider's encrypted reasoning\n // from a message that is about to be sent back.\n const nextSignature = signature ?? adopted?.signature\n // A hydrated part has no stepId to carry the redacted marker, so it keeps\n // its own flag.\n const redacted = isRedactedThinkingId(stepId) || adopted?.redacted === true\n\n const thinkingPart: ThinkingPart = {\n type: 'thinking',\n content,\n stepId,\n ...(nextSignature && { signature: nextSignature }),\n ...(redacted && { redacted: true }),\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"],"mappings":"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"}
@@ -369,11 +369,13 @@ export declare class StreamProcessor {
369
369
  * Handle TOOL_CALL_END event — arguments are finalized (input-complete).
370
370
  * Tool output arrives on TOOL_CALL_RESULT, not on this event.
371
371
  *
372
- * If TOOL_CALL_END carries parsed `input`, use it as the canonical arguments:
373
- * back-fill the accumulated string when no TOOL_CALL_ARGS deltas were seen
374
- * (adapters that deliver the whole input on END — e.g. Anthropic
375
- * server_tool_use / web_search — issue #839) and override the rendered part's
376
- * `input` with the canonical value.
372
+ * If TOOL_CALL_END carries parsed `input`, it is the canonical arguments:
373
+ * write it into the accumulated string and override the rendered part's
374
+ * `input` with it. That covers adapters that deliver the whole input on END
375
+ * (e.g. Anthropic server_tool_use / web_search — issue #839) and adapters
376
+ * that stream the wire arguments and then normalize them (OpenAI strict-mode
377
+ * null widening, undone by the adapter after #939), so `arguments` and
378
+ * `input` never disagree on the persisted part.
377
379
  *
378
380
  * @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
379
381
  */
@@ -486,11 +488,11 @@ export declare class StreamProcessor {
486
488
  private completeToolCall;
487
489
  private isToolCallPartAwaitingUserAction;
488
490
  /**
489
- * Whether the rendered tool-call part for the given id has reached the
490
- * terminal 'error' state. Used to prevent the completion safety net from
491
- * downgrading a failed call back to 'input-complete'.
491
+ * Whether the rendered tool-call part for the given id has reached a
492
+ * terminal 'error' or 'complete' state. Used to prevent the completion
493
+ * safety net from downgrading a finished call back to 'input-complete'.
492
494
  */
493
- private isToolCallPartErrored;
495
+ private isToolCallPartTerminal;
494
496
  /**
495
497
  * Emit pending text update for a specific message.
496
498
  *
@@ -3,7 +3,7 @@ import { mergeMetadata, tanstackMetadata } from "../../../utilities/merge-metada
3
3
  import { runErrorEventToError } from "../../../utilities/errors.js";
4
4
  import { INTERRUPT_BINDING_METADATA_KEY } from "../../../interrupt-resume.js";
5
5
  import { isAssistantSegmentOf, isProviderExecutedToolCall } from "../../../utilities/provider-executed.js";
6
- import { normalizeToolResult, toolResultErrorText } from "../../../utilities/tool-result.js";
6
+ import { isContentPartArray, isToolResultOutcome, normalizeToolResult, toolResultErrorText } from "../../../utilities/tool-result.js";
7
7
  import { splitSubagentWire } from "../../../utilities/subagent-wire.js";
8
8
  import { aguiContentToContentParts, aguiSnapshotMessageToUIMessage, coerceCreatedAt, generateMessageId, uiMessageToModelMessages } from "../messages.js";
9
9
  import { getChunkRunId } from "../../../utilities/chunk-ids.js";
@@ -40,6 +40,22 @@ function interruptBatchHasGeneric(interrupts) {
40
40
  });
41
41
  }
42
42
  /**
43
+ * The canonical arguments string for a `TOOL_CALL_END.input`, or `undefined`
44
+ * when JSON cannot carry it: `JSON.stringify` returns `undefined` (despite
45
+ * its declared type) for a top-level function or symbol and throws on BigInt
46
+ * and circular references. `null` is not tool arguments either, so it also
47
+ * keeps the streamed value instead of writing `arguments = "null"`.
48
+ */
49
+ function serializeToolInput(input) {
50
+ if (input === void 0 || input === null) return void 0;
51
+ try {
52
+ const serialized = JSON.stringify(input);
53
+ return typeof serialized === "string" ? serialized : void 0;
54
+ } catch {
55
+ return;
56
+ }
57
+ }
58
+ /**
43
59
  * StreamProcessor - State machine for processing AI response streams
44
60
  *
45
61
  * Manages the full UIMessage[] conversation and emits events on changes.
@@ -1110,11 +1126,13 @@ var StreamProcessor = class StreamProcessor {
1110
1126
  * Handle TOOL_CALL_END event — arguments are finalized (input-complete).
1111
1127
  * Tool output arrives on TOOL_CALL_RESULT, not on this event.
1112
1128
  *
1113
- * If TOOL_CALL_END carries parsed `input`, use it as the canonical arguments:
1114
- * back-fill the accumulated string when no TOOL_CALL_ARGS deltas were seen
1115
- * (adapters that deliver the whole input on END — e.g. Anthropic
1116
- * server_tool_use / web_search — issue #839) and override the rendered part's
1117
- * `input` with the canonical value.
1129
+ * If TOOL_CALL_END carries parsed `input`, it is the canonical arguments:
1130
+ * write it into the accumulated string and override the rendered part's
1131
+ * `input` with it. That covers adapters that deliver the whole input on END
1132
+ * (e.g. Anthropic server_tool_use / web_search — issue #839) and adapters
1133
+ * that stream the wire arguments and then normalize them (OpenAI strict-mode
1134
+ * null widening, undone by the adapter after #939), so `arguments` and
1135
+ * `input` never disagree on the persisted part.
1118
1136
  *
1119
1137
  * @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
1120
1138
  */
@@ -1126,12 +1144,11 @@ var StreamProcessor = class StreamProcessor {
1126
1144
  const input = chunk.input !== void 0 ? chunk.input : tanstackMetadata(chunk)?.input;
1127
1145
  const existingToolCall = msgState.toolCalls.get(chunk.toolCallId);
1128
1146
  if (existingToolCall && existingToolCall.state !== "input-complete") {
1129
- if (input !== void 0 && !existingToolCall.arguments) try {
1130
- existingToolCall.arguments = JSON.stringify(input);
1131
- } catch {}
1147
+ const serializedInput = serializeToolInput(input);
1148
+ if (serializedInput !== void 0) existingToolCall.arguments = serializedInput;
1132
1149
  const index = msgState.toolCallOrder.indexOf(chunk.toolCallId);
1133
1150
  this.completeToolCall(messageId, index, existingToolCall);
1134
- if (input !== void 0) {
1151
+ if (serializedInput !== void 0) {
1135
1152
  existingToolCall.parsedArguments = input;
1136
1153
  this.messages = updateToolCallPart(this.messages, messageId, {
1137
1154
  id: existingToolCall.id,
@@ -1155,7 +1172,10 @@ var StreamProcessor = class StreamProcessor {
1155
1172
  handleToolCallResultEvent(chunk) {
1156
1173
  const messageId = this.toolCallToMessage.get(chunk.toolCallId) ?? this.messages.find((m) => m.parts.some((p) => p.type === "tool-call" && p.id === chunk.toolCallId))?.id;
1157
1174
  if (!messageId) return;
1158
- const isOutputError = chunk.state === "output-error" || tanstackMetadata(chunk)?.state === "output-error";
1175
+ const extra = chunk;
1176
+ const rawToolResultOutcome = tanstackMetadata(chunk)?.toolResultOutcome;
1177
+ const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome) ? rawToolResultOutcome : void 0;
1178
+ const isOutputError = extra.state === "output-error" || tanstackMetadata(chunk)?.state === "output-error" || toolResultOutcome !== void 0;
1159
1179
  let output;
1160
1180
  try {
1161
1181
  output = typeof chunk.content === "string" ? JSON.parse(chunk.content) : chunk.content;
@@ -1164,7 +1184,7 @@ var StreamProcessor = class StreamProcessor {
1164
1184
  }
1165
1185
  this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output, isOutputError ? "error" : void 0);
1166
1186
  const resultState = isOutputError ? "error" : "complete";
1167
- this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, aguiContentToContentParts(chunk.content), resultState, resultState === "error" ? toolResultErrorText(output) : void 0);
1187
+ this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, isContentPartArray(output) ? output : aguiContentToContentParts(chunk.content), resultState, resultState === "error" ? toolResultErrorText(output) : void 0, toolResultOutcome);
1168
1188
  this.emitMessagesChange();
1169
1189
  }
1170
1190
  /**
@@ -1530,7 +1550,7 @@ var StreamProcessor = class StreamProcessor {
1530
1550
  } catch {
1531
1551
  toolCall.parsedArguments = void 0;
1532
1552
  }
1533
- if (this.isToolCallPartErrored(toolCall.id)) return;
1553
+ if (this.isToolCallPartTerminal(toolCall.id)) return;
1534
1554
  if (this.isToolCallPartAwaitingUserAction(toolCall.id)) return;
1535
1555
  this.messages = updateToolCallPart(this.messages, messageId, {
1536
1556
  id: toolCall.id,
@@ -1547,12 +1567,12 @@ var StreamProcessor = class StreamProcessor {
1547
1567
  return this.messages.some((msg) => msg.parts?.some((part) => part.type === "tool-call" && part.id === toolCallId && (part.state === "approval-requested" || part.state === "approval-responded")));
1548
1568
  }
1549
1569
  /**
1550
- * Whether the rendered tool-call part for the given id has reached the
1551
- * terminal 'error' state. Used to prevent the completion safety net from
1552
- * downgrading a failed call back to 'input-complete'.
1570
+ * Whether the rendered tool-call part for the given id has reached a
1571
+ * terminal 'error' or 'complete' state. Used to prevent the completion
1572
+ * safety net from downgrading a finished call back to 'input-complete'.
1553
1573
  */
1554
- isToolCallPartErrored(toolCallId) {
1555
- return this.messages.some((msg) => msg.parts?.some((part) => part.type === "tool-call" && part.id === toolCallId && part.state === "error"));
1574
+ isToolCallPartTerminal(toolCallId) {
1575
+ return this.messages.some((msg) => msg.parts?.some((part) => part.type === "tool-call" && part.id === toolCallId && (part.state === "error" || part.state === "complete")));
1556
1576
  }
1557
1577
  /**
1558
1578
  * Emit pending text update for a specific message.