@tanstack/ai 0.55.0 → 0.58.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 (67) hide show
  1. package/README.md +42 -16
  2. package/dist/esm/activities/chat/index.js +8 -6
  3. package/dist/esm/activities/chat/index.js.map +1 -1
  4. package/dist/esm/activities/chat/middleware/types.d.ts +4 -3
  5. package/dist/esm/activities/chat/middleware/types.js.map +1 -1
  6. package/dist/esm/activities/chat/tools/tool-calls.d.ts +2 -2
  7. package/dist/esm/activities/chat/tools/tool-calls.js +3 -2
  8. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  9. package/dist/esm/activities/evaluate/adapter.d.ts +160 -0
  10. package/dist/esm/activities/evaluate/adapter.js +23 -0
  11. package/dist/esm/activities/evaluate/adapter.js.map +1 -0
  12. package/dist/esm/activities/evaluate/index.d.ts +255 -0
  13. package/dist/esm/activities/evaluate/index.js +317 -0
  14. package/dist/esm/activities/evaluate/index.js.map +1 -0
  15. package/dist/esm/activities/generateSpeech/adapter.d.ts +39 -1
  16. package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
  17. package/dist/esm/activities/generateSpeech/index.d.ts +55 -5
  18. package/dist/esm/activities/generateSpeech/index.js +53 -3
  19. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  20. package/dist/esm/activities/generateVoice/adapter.d.ts +62 -0
  21. package/dist/esm/activities/generateVoice/adapter.js +23 -0
  22. package/dist/esm/activities/generateVoice/adapter.js.map +1 -0
  23. package/dist/esm/activities/generateVoice/index.d.ts +133 -0
  24. package/dist/esm/activities/generateVoice/index.js +184 -0
  25. package/dist/esm/activities/generateVoice/index.js.map +1 -0
  26. package/dist/esm/activities/index.d.ts +10 -4
  27. package/dist/esm/activities/index.js +14 -10
  28. package/dist/esm/activities/middleware/types.d.ts +1 -1
  29. package/dist/esm/client.d.ts +3 -2
  30. package/dist/esm/client.js +21 -3
  31. package/dist/esm/client.js.map +1 -1
  32. package/dist/esm/index.d.ts +4 -2
  33. package/dist/esm/index.js +6 -3
  34. package/dist/esm/middlewares/otel.js +2 -0
  35. package/dist/esm/middlewares/otel.js.map +1 -1
  36. package/dist/esm/realtime/index.d.ts +1 -1
  37. package/dist/esm/realtime/index.js +1 -1
  38. package/dist/esm/realtime/index.js.map +1 -1
  39. package/dist/esm/stream-to-response.js +12 -6
  40. package/dist/esm/stream-to-response.js.map +1 -1
  41. package/dist/esm/strip-to-spec-middleware.js +2 -1
  42. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  43. package/dist/esm/types.d.ts +243 -3
  44. package/dist/esm/utilities/durability-batch.d.ts +8 -0
  45. package/dist/esm/utilities/durability-batch.js +45 -0
  46. package/dist/esm/utilities/durability-batch.js.map +1 -0
  47. package/package.json +3 -3
  48. package/skills/ai-core/media-generation/SKILL.md +132 -6
  49. package/src/activities/chat/index.ts +11 -5
  50. package/src/activities/chat/middleware/types.ts +8 -2
  51. package/src/activities/chat/tools/tool-calls.ts +24 -5
  52. package/src/activities/evaluate/adapter.ts +212 -0
  53. package/src/activities/evaluate/index.ts +614 -0
  54. package/src/activities/generateSpeech/adapter.ts +47 -1
  55. package/src/activities/generateSpeech/index.ts +149 -8
  56. package/src/activities/generateVoice/adapter.ts +89 -0
  57. package/src/activities/generateVoice/index.ts +371 -0
  58. package/src/activities/index.ts +69 -0
  59. package/src/activities/middleware/types.ts +2 -0
  60. package/src/client.ts +35 -8
  61. package/src/index.ts +21 -0
  62. package/src/middlewares/otel.ts +2 -0
  63. package/src/realtime/index.ts +1 -1
  64. package/src/stream-to-response.ts +16 -6
  65. package/src/strip-to-spec-middleware.ts +2 -1
  66. package/src/types.ts +269 -3
  67. package/src/utilities/durability-batch.ts +48 -0
@@ -1,5 +1,5 @@
1
1
  import { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec';
2
- import { AgentLoopState, JSONSchema, ModelMessage, RunAgentResumeItem, StreamChunk, TokenUsage, Tool, ToolCall } from '../../../types.js';
2
+ import { AgentLoopState, EmitCustomEventOptions, JSONSchema, ModelMessage, RunAgentResumeItem, StreamChunk, TokenUsage, Tool, ToolCall } from '../../../types.js';
3
3
  import { SystemPrompt } from '../../../system-prompts.js';
4
4
  import { ToolApprovalResolution } from '../../../interrupts.js';
5
5
  import { GenericInterruptRequest, InterruptDefinition } from '../../../interrupt-definition.js';
@@ -116,9 +116,10 @@ export interface ChatMiddlewareContext<TContext = unknown> {
116
116
  /**
117
117
  * Push a `CUSTOM` chunk onto the chat stream immediately.
118
118
  * The engine yields it as soon as it can (including while `onConfig`
119
- * is still awaiting work such as a summarize call).
119
+ * is still awaiting work such as a summarize call). Durability then
120
+ * flushes the event on its own, unless you pass `{ batch: true }`.
120
121
  */
121
- emitCustomEvent: (name: string, value: Record<string, any>) => void;
122
+ emitCustomEvent: (name: string, value: Record<string, any>, options?: EmitCustomEventOptions) => void;
122
123
  /** Runtime context provided by chat() options */
123
124
  context: TContext;
124
125
  /**
@@ -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 JSONSchema,\n ModelMessage,\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 * 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).\n */\n emitCustomEvent: (name: string, value: Record<string, any>) => 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// 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 * 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":";AA8FA,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 JSONSchema,\n ModelMessage,\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 * 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// 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 * 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":";AA+FA,IAAa,4BAA4B;CACvC;CACA;CACA;CACA;AACF;AAIA,IAAa,yBAAyB;CAAC;CAAY;CAAU;AAAM"}
@@ -1,6 +1,6 @@
1
1
  import { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk.js';
2
2
  import { ToolApprovalResolution } from '../../../interrupts.js';
3
- import { AnyTool, CustomEvent, ModelMessage, RunFinishedEvent, Tool, ToolCall, ToolCallArgsEvent, ToolCallEndEvent, ToolCallStartEvent, ToolExecutionContext } from '../../../types.js';
3
+ import { AnyTool, CustomEvent, EmitCustomEventOptions, ModelMessage, RunFinishedEvent, Tool, ToolCall, ToolCallArgsEvent, ToolCallEndEvent, ToolCallStartEvent, ToolExecutionContext } from '../../../types.js';
4
4
  import { AfterToolCallInfo, BeforeToolCallDecision } from '../middleware/types.js';
5
5
  import { ContextFromTool, DefinedContext, MergeContext, UnionToIntersection } from '../runtime-context-types.js';
6
6
  /**
@@ -158,5 +158,5 @@ export declare function executeServerTool<TContext = unknown>(toolCall: ToolCall
158
158
  * @param clientResults - Map of client-side execution results (toolCallId -> result)
159
159
  * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)
160
160
  */
161
- export declare function executeToolCalls<TContext = unknown>(toolCalls: Array<ToolCall>, tools: ReadonlyArray<AnyTool>, approvals?: Map<string, ToolApprovalResolution>, clientResults?: Map<string, any>, createCustomEventChunk?: (eventName: string, value: Record<string, any>) => CustomEvent, middlewareHooks?: ToolExecutionMiddlewareHooks, userContext?: TContext, abortSignal?: AbortSignal, resumeState?: ToolResumeExecutionState): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void>;
161
+ export declare function executeToolCalls<TContext = unknown>(toolCalls: Array<ToolCall>, tools: ReadonlyArray<AnyTool>, approvals?: Map<string, ToolApprovalResolution>, clientResults?: Map<string, any>, createCustomEventChunk?: (eventName: string, value: Record<string, any>, options?: EmitCustomEventOptions) => CustomEvent, middlewareHooks?: ToolExecutionMiddlewareHooks, userContext?: TContext, abortSignal?: AbortSignal, resumeState?: ToolResumeExecutionState): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void>;
162
162
  export {};
@@ -101,6 +101,7 @@ var ToolCallManager = class {
101
101
  * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)
102
102
  */
103
103
  addToolCallStartEvent(event) {
104
+ for (const toolCall of this.toolCallsMap.values()) if (toolCall.id === event.toolCallId) return;
104
105
  const index = event.index ?? this.toolCallsMap.size;
105
106
  const name = event.toolCallName ?? event.toolName;
106
107
  this.toolCallsMap.set(index, {
@@ -466,11 +467,11 @@ async function* executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ n
466
467
  toolCallId: toolCall.id,
467
468
  context: userContext,
468
469
  abortSignal,
469
- emitCustomEvent: (eventName, value) => {
470
+ emitCustomEvent: (eventName, value, options) => {
470
471
  if (createCustomEventChunk) pendingEvents.push(createCustomEventChunk(eventName, {
471
472
  ...value,
472
473
  toolCallId: toolCall.id
473
- }));
474
+ }, options));
474
475
  }
475
476
  };
476
477
  if (!tool.execute) {
@@ -1 +1 @@
1
- {"version":3,"file":"tool-calls.js","names":[],"sources":["../../../../../src/activities/chat/tools/tool-calls.ts"],"sourcesContent":["import { normalizeToolResult } from '../../../utilities/tool-result'\nimport { tanstackMetadata } from '../../../utilities/merge-metadata'\nimport type { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk'\nimport { isStandardSchema, parseWithStandardSchema } from './schema-converter'\nimport type { ToolApprovalResolution } from '../../../interrupts'\nimport type {\n AnyTool,\n ContentPart,\n CustomEvent,\n ModelMessage,\n RunFinishedEvent,\n Tool,\n ToolCall,\n ToolCallArgsEvent,\n ToolCallEndEvent,\n ToolCallStartEvent,\n ToolExecutionContext,\n ToolOutputState,\n} from '../../../types'\nimport type {\n AfterToolCallInfo,\n BeforeToolCallDecision,\n} from '../middleware/types'\nimport type { McpResourceReadResult } from '../mcp/types'\nimport type {\n ContextFromTool,\n DefinedContext,\n MergeContext,\n UnionToIntersection,\n} from '../runtime-context-types'\n\nfunction safeJsonParse(value: string): unknown {\n try {\n return JSON.parse(value)\n } catch {\n return value\n }\n}\n\n/**\n * MCP Apps metadata attached to a server tool at discovery (see\n * `@tanstack/ai-mcp` discovery + `MCPManager.discover()`).\n *\n * - `uiResourceUri` / `serverId` are stamped by ai-mcp at tool discovery.\n * - `readResource` is bound by `MCPManager.discover()` (the one site that has\n * both the tool and its originating source) so the resource can be eagerly\n * read at the emit site. Under `chat()`-managed MCP lifecycle\n * (`connection:'close'`), the MCP source is not disposed until the run\n * drains, so `readResource` is still live at this emit point. Note: a caller\n * who closes the MCP source early (outside `chat()`'s managed lifecycle)\n * degrades fail-soft — `readResource` may reject, the widget is absent, but\n * the tool result still flows to the model.\n * `@tanstack/ai` never imports `@tanstack/ai-mcp`; this travels structurally\n * on the tool.\n */\ninterface McpToolAppMeta {\n uiResourceUri?: string\n serverId?: string\n /** Server-native (unprefixed) MCP tool name — used as the renderer's toolName. */\n serverToolName?: string\n readResource?: (uri: string) => Promise<McpResourceReadResult>\n}\n\nfunction readMcpAppMeta(tool: AnyTool): McpToolAppMeta | undefined {\n const meta = (tool.metadata as { mcp?: McpToolAppMeta } | undefined)?.mcp\n return meta\n}\n\n/**\n * Eagerly read a tool's linked `ui://` resource (MCP Apps) and emit a\n * `ui-resource` CUSTOM event so the client can render the widget. The model\n * still receives the normal text tool-result; the widget rides alongside and\n * never enters model input.\n *\n * Fail-soft: any read error logs a warning and emits nothing — it never throws,\n * so the normal tool-result still flows and a broken widget cannot break the run.\n */\nasync function emitUiResourceIfLinked<TContext>(\n tool: AnyTool,\n context: ToolExecutionContext<TContext>,\n): Promise<void> {\n const mcp = readMcpAppMeta(tool)\n const uiUri = mcp?.uiResourceUri\n if (!uiUri || !mcp.readResource) return\n\n // The try covers ONLY the fallible read — keep `emitCustomEvent` out of it so\n // an exception from the emit path can't be mislabeled as a read failure.\n let matched: McpResourceReadResult['contents'][number] | undefined\n try {\n const res = await mcp.readResource(uiUri)\n // Emit ONLY the content whose uri matches the requested `uiUri`. A source\n // can return unrelated contents; falling back to `contents[0]` would risk\n // rendering a widget that doesn't correspond to the linked resource. This\n // is a display widget — a mismatched resource is worse than none, so if no\n // content matches we fail-soft (warn + return) rather than emit.\n matched = res.contents.find((c) => c.uri === uiUri)\n } catch (err) {\n // fail-soft — the text tool-result already flows; a broken widget must\n // not break the run.\n console.warn(`[mcp-apps] failed to read ui resource ${uiUri}:`, err)\n return\n }\n if (!matched) {\n console.warn(\n `[mcp-apps] ui resource ${uiUri} returned no content matching that uri; not emitting`,\n )\n return\n }\n // NOTE: `toolCallId` is intentionally NOT set here — it is stamped onto\n // every emitted event by the `executeToolCalls` context wrapper, so the\n // UIResourceEvent.value.toolCallId / UIResourcePart.toolCallId contract is\n // still satisfied downstream.\n context.emitCustomEvent('ui-resource', {\n resource: {\n uri: matched.uri,\n mimeType: matched.mimeType ?? 'text/html',\n text: matched.text,\n blob: matched.blob,\n },\n serverId: mcp.serverId,\n toolName: mcp.serverToolName ?? tool.name,\n meta: undefined,\n })\n}\n\n/**\n * Optional middleware hooks for tool execution.\n * When provided, these callbacks are invoked before/after each tool execution.\n */\nexport interface ToolExecutionMiddlewareHooks {\n onBeforeToolCall?: (\n toolCall: ToolCall,\n tool: Tool | undefined,\n args: unknown,\n ) => Promise<BeforeToolCallDecision>\n onAfterToolCall?: (info: AfterToolCallInfo) => Promise<void>\n}\n\n/**\n * Error thrown when middleware decides to abort the chat run during tool execution.\n */\nexport class MiddlewareAbortError extends Error {\n constructor(reason: string) {\n super(reason)\n this.name = 'MiddlewareAbortError'\n }\n}\n\n// The leaf context-inference primitives (ContextFromTool, MergeContext,\n// UnionToIntersection, DefinedContext) are shared with the chat activity\n// options layer — see ../runtime-context-types.\ntype RequiredContextFromToolUnion<T> = T extends unknown\n ? undefined extends ContextFromTool<T>\n ? never\n : ContextFromTool<T>\n : never\n\ntype ContextFromToolUnion<T> = [\n UnionToIntersection<DefinedContext<ContextFromTool<T>>>,\n] extends [never]\n ? unknown\n : [RequiredContextFromToolUnion<T>] extends [never]\n ? UnionToIntersection<DefinedContext<ContextFromTool<T>>> | undefined\n : UnionToIntersection<DefinedContext<ContextFromTool<T>>>\n\ntype ContextFromTools<TTools> = TTools extends readonly [\n infer THead,\n ...infer TTail,\n]\n ? MergeContext<ContextFromTool<THead>, ContextFromTools<TTail>>\n : TTools extends ReadonlyArray<infer TTool>\n ? ContextFromToolUnion<TTool>\n : unknown\n\ntype ExecuteToolsContextArgs<TContext> = undefined extends TContext\n ? [userContext?: TContext]\n : [userContext: TContext]\n\n/**\n * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.\n *\n * Responsibilities:\n * - Accumulates streaming tool call events (ID, name, arguments)\n * - Validates tool calls (filters out incomplete ones)\n * - Executes tool `execute` functions with parsed arguments\n * - Emits `TOOL_CALL_END` events for client visibility\n * - Returns tool result messages for conversation history\n *\n * This class is used internally by the AI.chat() method to handle the automatic\n * tool execution loop. It can also be used independently for custom tool execution logic.\n *\n * @example\n * ```typescript\n * const manager = new ToolCallManager(tools);\n *\n * // During streaming, accumulate tool calls\n * for await (const chunk of stream) {\n * if (chunk.type === 'TOOL_CALL_START') {\n * manager.addToolCallStartEvent(chunk);\n * } else if (chunk.type === 'TOOL_CALL_ARGS') {\n * manager.addToolCallArgsEvent(chunk);\n * }\n * }\n *\n * // After stream completes, execute tools\n * if (manager.hasToolCalls()) {\n * const toolResults = yield* manager.executeTools(finishEvent);\n * messages = [...messages, ...toolResults];\n * manager.clear();\n * }\n * ```\n */\nexport class ToolCallManager<\n TToolsOrContext = ReadonlyArray<AnyTool>,\n TContext = TToolsOrContext extends ReadonlyArray<AnyTool>\n ? ContextFromTools<TToolsOrContext>\n : TToolsOrContext,\n> {\n private readonly toolCallsMap = new Map<number, ToolCall>()\n private readonly tools: TToolsOrContext extends ReadonlyArray<AnyTool>\n ? TToolsOrContext\n : ReadonlyArray<AnyTool>\n\n constructor(\n tools: TToolsOrContext extends ReadonlyArray<AnyTool>\n ? TToolsOrContext\n : ReadonlyArray<AnyTool>,\n ) {\n this.tools = tools\n }\n\n /**\n * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)\n */\n addToolCallStartEvent(event: ToolCallStartEvent): void {\n const index = (event as AdapterYieldChunk).index ?? this.toolCallsMap.size\n const name = event.toolCallName ?? event.toolName\n this.toolCallsMap.set(index, {\n id: event.toolCallId,\n type: 'function',\n function: {\n name,\n arguments: '',\n },\n ...(event.metadata !== undefined && { metadata: event.metadata }),\n })\n }\n\n /**\n * Add a TOOL_CALL_ARGS event to accumulate arguments (AG-UI)\n */\n addToolCallArgsEvent(event: ToolCallArgsEvent): void {\n const extra = event as AdapterYieldChunk\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n if (typeof extra.args === 'string' && extra.args !== '') {\n toolCall.function.arguments = extra.args\n } else {\n toolCall.function.arguments += event.delta\n }\n break\n }\n }\n }\n\n /**\n * Complete a tool call with its final input\n * Called when TOOL_CALL_END is received\n */\n completeToolCall(event: ToolCallEndEvent): void {\n for (const toolCall of this.toolCallsMap.values()) {\n if (toolCall.id !== event.toolCallId) continue\n if (event.input === undefined) return\n const normalized =\n event.input && typeof event.input === 'object' ? event.input : {}\n toolCall.function.arguments = JSON.stringify(normalized)\n return\n }\n }\n\n /**\n * Check if there are any complete tool calls to execute\n */\n hasToolCalls(): boolean {\n return this.getToolCalls().length > 0\n }\n\n /**\n * Get all complete tool calls (filtered for valid ID and name)\n */\n getToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCallsMap.values()).filter(\n (tc) => tc.id && tc.function.name && tc.function.name.trim().length > 0,\n )\n }\n\n /**\n * Execute all tool calls and return tool result messages\n * Yields TOOL_CALL_END events for streaming\n * @param finishEvent - RUN_FINISHED event from the stream\n */\n async *executeTools(\n finishEvent: RunFinishedEvent,\n ...contextArgs: ExecuteToolsContextArgs<TContext>\n ): AsyncGenerator<AdapterYieldChunk, Array<ModelMessage>, void> {\n const toolCallsArray = this.getToolCalls()\n const toolResults: Array<ModelMessage> = []\n const hasRuntimeContext = contextArgs.length > 0\n const userContext = contextArgs[0]\n\n for (const toolCall of toolCallsArray) {\n const tool = this.tools.find((t) => t.name === toolCall.function.name)\n\n let toolResultContent: string | Array<ContentPart>\n let toolResultState: ToolOutputState | undefined\n // Holds the parsed/validated execution output before serialization.\n // Stays `undefined` when the tool has no `execute` (client-only\n // tools) or when execution throws.\n let toolOutput: unknown\n if (tool?.execute) {\n try {\n // Parse arguments (normalize null/non-object to {} for empty tool_use blocks)\n let args: unknown\n try {\n const argsString = toolCall.function.arguments.trim() || '{}'\n const parsed = JSON.parse(argsString)\n args = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n throw new Error(\n `Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`,\n )\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n args = parseWithStandardSchema(tool.inputSchema, args)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Input validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n // Execute the tool\n const executionContext = {\n toolCallId: toolCall.id,\n context: userContext,\n emitCustomEvent: () => {},\n } as ToolExecutionContext<TContext>\n let result = hasRuntimeContext\n ? await tool.execute(args, executionContext)\n : await tool.execute(args)\n\n // Validate output against outputSchema if provided (for Standard\n // Schema compliant schemas). Unlike the previous implementation we\n // intentionally validate `undefined`/`null` results too, so a tool\n // whose schema forbids them surfaces a validation error instead of\n // silently passing — the schema itself decides whether they're valid.\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n try {\n result = parseWithStandardSchema(tool.outputSchema, result)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n toolOutput = result\n toolResultContent = normalizeToolResult(result)\n } catch (error: unknown) {\n // If tool execution fails, add error message\n const message =\n error instanceof Error ? error.message : 'Unknown error'\n toolResultContent = `Error executing tool: ${message}`\n toolResultState = 'output-error'\n }\n } else {\n // Tool doesn't have execute function, add placeholder\n toolResultContent = `Tool ${toolCall.function.name} does not have an execute function`\n }\n\n // Emit TOOL_CALL_END event\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolCallName: toolCall.function.name,\n toolName: toolCall.function.name,\n model: (() => {\n const model = tanstackMetadata(finishEvent)?.model\n return typeof model === 'string' ? model : undefined\n })(),\n timestamp: Date.now(),\n // Typed parsed output (undefined for failed exec / client-only tools).\n ...(toolOutput !== undefined ? { output: toolOutput } : {}),\n result: toolResultContent,\n ...(toolResultState !== undefined && { state: toolResultState }),\n }\n\n // Add tool result message\n toolResults.push({\n role: 'tool',\n content: toolResultContent,\n toolCallId: toolCall.id,\n })\n }\n\n return toolResults\n }\n\n /**\n * Clear the tool calls map for the next iteration\n */\n clear(): void {\n this.toolCallsMap.clear()\n }\n}\n\nexport interface ToolResult {\n toolCallId: string\n toolName: string\n result: any\n state?: 'output-available' | 'output-error'\n /** Duration of tool execution in milliseconds (only for server-executed tools) */\n duration?: number\n /**\n * Parsed tool input (after JSON parse + optional Standard Schema validation).\n * Parsed tool input after JSON parse + optional Standard Schema validation.\n */\n input?: unknown\n /**\n * Parsed tool output before wire serialization. Surfaced on engine-emitted\n * `TOOL_CALL_END` events so consumers can read typed `output` without\n * re-parsing `result`. Undefined on error paths and when execution is skipped.\n */\n output?: unknown\n}\n\nexport interface ApprovalRequest {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n}\n\nexport interface ClientToolRequest {\n toolCallId: string\n toolName: string\n input: any\n}\n\nexport interface ToolResumeExecutionState {\n deniedToolResults?: ReadonlyMap<string, unknown>\n cancelledToolCallIds?: ReadonlySet<string>\n}\n\nfunction approvalResolution(\n approvals: ReadonlyMap<string, ToolApprovalResolution>,\n toolCallId: string,\n): ToolApprovalResolution | undefined {\n return approvals.get(toolCallId) ?? approvals.get(`approval_${toolCallId}`)\n}\n\nfunction isApproved(resolution: ToolApprovalResolution): boolean {\n return typeof resolution === 'boolean' ? resolution : resolution.approved\n}\n\nfunction editedApprovalArgs(\n resolution: ToolApprovalResolution,\n): unknown | undefined {\n return typeof resolution === 'object' && resolution.approved\n ? resolution.editedArgs\n : undefined\n}\n\nfunction deniedApprovalResult(resolution: ToolApprovalResolution): unknown {\n return typeof resolution === 'object' && !resolution.approved\n ? (resolution.payload ?? { error: 'User declined tool execution' })\n : { error: 'User declined tool execution' }\n}\n\ninterface ExecuteToolCallsResult {\n /** Tool results ready to send to LLM */\n results: Array<ToolResult>\n /** Tools that need user approval before execution */\n needsApproval: Array<ApprovalRequest>\n /** Tools that need client-side execution */\n needsClientExecution: Array<ClientToolRequest>\n}\n\n/**\n * Helper that runs a tool execution promise while polling for pending custom events.\n * Yields any custom events that are emitted during execution, then returns the\n * execution result.\n */\nasync function* executeWithEventPolling<T>(\n executionPromise: Promise<T>,\n pendingEvents: Array<CustomEvent>,\n): AsyncGenerator<CustomEvent, T, void> {\n // Use an object to track mutable state across the async boundary\n const state = { done: false, result: undefined as T }\n const executionWithFlag = executionPromise.then((r) => {\n state.done = true\n state.result = r\n return r\n })\n\n while (!state.done) {\n // Wait for either the execution to complete or a short timeout\n await Promise.race([\n executionWithFlag,\n new Promise((resolve) => setTimeout(resolve, 10)),\n ])\n\n // Flush any pending events\n let event: CustomEvent | undefined\n while ((event = pendingEvents.shift()) !== undefined) {\n yield event\n }\n }\n\n // Final flush in case events were emitted right at completion\n let event: CustomEvent | undefined\n while ((event = pendingEvents.shift()) !== undefined) {\n yield event\n }\n\n return state.result\n}\n\n/**\n * Apply a middleware onBeforeToolCall decision.\n * Returns the (possibly transformed) input if execution should proceed,\n * or undefined if the tool call was skipped (result already pushed).\n * Throws MiddlewareAbortError if the decision is 'abort'.\n */\nasync function applyBeforeToolCallDecision(\n toolCall: ToolCall,\n tool: Tool,\n input: unknown,\n toolName: string,\n middlewareHooks: ToolExecutionMiddlewareHooks,\n results: Array<ToolResult>,\n): Promise<{ proceed: true; input: unknown } | { proceed: false }> {\n if (!middlewareHooks.onBeforeToolCall) {\n return { proceed: true, input }\n }\n\n const decision = await middlewareHooks.onBeforeToolCall(toolCall, tool, input)\n if (!decision) {\n return { proceed: true, input }\n }\n\n if (decision.type === 'abort') {\n throw new MiddlewareAbortError(decision.reason || 'Aborted by middleware')\n }\n\n if (decision.type === 'skip') {\n const skipResult = decision.result\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof skipResult === 'string'\n ? safeJsonParse(skipResult)\n : (skipResult ?? null),\n duration: 0,\n })\n if (middlewareHooks.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration: 0,\n result: skipResult,\n })\n }\n return { proceed: false }\n }\n\n return { proceed: true, input: decision.args }\n}\n\n/**\n * Execute a server-side tool with event polling, output validation, and middleware hooks.\n * Yields CustomEvent chunks during execution and pushes the result to the results array.\n */\nexport async function* executeServerTool<TContext = unknown>(\n toolCall: ToolCall,\n tool: AnyTool,\n toolName: string,\n input: unknown,\n context: ToolExecutionContext<TContext>,\n pendingEvents: Array<CustomEvent>,\n results: Array<ToolResult>,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n): AsyncGenerator<CustomEvent, void, void> {\n const startTime = Date.now()\n try {\n if (!tool.execute) {\n throw new Error(`Tool ${toolName} has no execute() implementation`)\n }\n const executionPromise = Promise.resolve(tool.execute(input, context))\n let result = yield* executeWithEventPolling(executionPromise, pendingEvents)\n const duration = Date.now() - startTime\n\n // MCP Apps: if this tool links a ui:// resource, eagerly read it and queue\n // a `ui-resource` CUSTOM event. The MCP source stays live until the run\n // drains (MCPManager's `connection:'close'` policy disposes on completion),\n // so `readResource` is callable here. Fail-soft: a read error warns and\n // emits nothing — the text result still flows.\n await emitUiResourceIfLinked(tool, context)\n\n // Flush remaining events (including any queued ui-resource event)\n let pendingEvent: CustomEvent | undefined\n while ((pendingEvent = pendingEvents.shift()) !== undefined) {\n yield pendingEvent\n }\n\n // Validate output against outputSchema if provided. Validates\n // `undefined`/`null` too — the schema decides whether they're valid.\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n const finalResult =\n typeof result === 'string' ? safeJsonParse(result) : (result ?? null)\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: finalResult,\n input,\n output: finalResult,\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration,\n result: finalResult,\n })\n }\n } catch (error: unknown) {\n const duration = Date.now() - startTime\n\n // Flush remaining events\n let pendingEvent: CustomEvent | undefined\n while ((pendingEvent = pendingEvents.shift()) !== undefined) {\n yield pendingEvent\n }\n\n if (error instanceof MiddlewareAbortError) {\n throw error\n }\n\n const message = error instanceof Error ? error.message : 'Unknown error'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: message },\n input,\n state: 'output-error',\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: false,\n duration,\n error,\n })\n }\n }\n}\n\nfunction buildClientToolResult(\n toolCallId: string,\n toolName: string,\n tool: AnyTool,\n rawResult: unknown,\n input?: unknown,\n): ToolResult {\n try {\n let result = rawResult\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n const parsed =\n typeof result === 'string' ? safeJsonParse(result) : (result ?? null)\n return {\n toolCallId,\n toolName,\n result: parsed,\n input,\n output: parsed,\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Validation failed'\n return {\n toolCallId,\n toolName,\n result: { error: message },\n input,\n state: 'output-error',\n }\n }\n}\n\n/**\n * Execute tool calls based on their configuration.\n * Yields CustomEvent chunks during tool execution for real-time progress updates.\n *\n * Handles three cases:\n * 1. Client tools (no execute) - request client to execute\n * 2. Server tools with approval - check approval before executing\n * 3. Normal server tools - execute immediately\n *\n * @param toolCalls - Tool calls from the LLM\n * @param tools - Available tools with their configurations\n * @param approvals - Map keyed by toolCallId (or `approval_${toolCallId}`) → ToolApprovalResolution\n * @param clientResults - Map of client-side execution results (toolCallId -> result)\n * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)\n */\nexport async function* executeToolCalls<TContext = unknown>(\n toolCalls: Array<ToolCall>,\n tools: ReadonlyArray<AnyTool>,\n approvals: Map<string, ToolApprovalResolution> = new Map(),\n clientResults: Map<string, any> = new Map(),\n createCustomEventChunk?: (\n eventName: string,\n value: Record<string, any>,\n ) => CustomEvent,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n userContext?: TContext,\n abortSignal?: AbortSignal,\n resumeState?: ToolResumeExecutionState,\n): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\n\n // Create tool lookup map\n const toolMap = new Map<string, AnyTool>()\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n // Batch gating: when any tool in the batch still needs an approval decision,\n // defer all execution so side effects don't happen before the user decides.\n const hasPendingApprovals = toolCalls.some((tc) => {\n const t = toolMap.get(tc.function.name)\n return (\n t?.needsApproval &&\n approvalResolution(approvals, tc.id) === undefined &&\n !resumeState?.cancelledToolCallIds?.has(tc.id)\n )\n })\n\n for (const toolCall of toolCalls) {\n const tool = toolMap.get(toolCall.function.name)\n const toolName = toolCall.function.name\n\n if (!tool) {\n // Unknown tool - return error\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: `Unknown tool: ${toolName}` },\n state: 'output-error',\n })\n continue\n }\n\n // Skip non-pending tools while approvals are outstanding\n if (hasPendingApprovals) {\n const isPendingApproval =\n tool.needsApproval &&\n approvalResolution(approvals, toolCall.id) === undefined\n const isPlainClientRequest = !tool.needsApproval && !tool.execute\n if (!isPendingApproval && !isPlainClientRequest) {\n continue\n }\n }\n\n if (resumeState?.cancelledToolCallIds?.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'Tool execution cancelled' },\n state: 'output-error',\n })\n continue\n }\n\n // Parse arguments\n let input: unknown = {}\n const argsStr = toolCall.function.arguments.trim() || '{}'\n if (argsStr) {\n try {\n const parsed = JSON.parse(argsStr)\n // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)\n input = parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Failed to parse tool arguments as JSON: ${argsStr}`,\n },\n input,\n state: 'output-error',\n })\n continue\n }\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n input = parseWithStandardSchema(tool.inputSchema, input)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Input validation failed for tool ${tool.name}: ${message}`,\n },\n // raw parse may have failed validation — still attach best-effort input\n input,\n state: 'output-error',\n })\n continue\n }\n }\n\n // Create a ToolExecutionContext for this tool call with event emission\n const pendingEvents: Array<CustomEvent> = []\n const context = {\n toolCallId: toolCall.id,\n context: userContext,\n abortSignal,\n emitCustomEvent: (eventName: string, value: Record<string, any>) => {\n if (createCustomEventChunk) {\n pendingEvents.push(\n createCustomEventChunk(eventName, {\n ...value,\n toolCallId: toolCall.id,\n }),\n )\n }\n },\n } as ToolExecutionContext<TContext>\n\n // CASE 1: Client-side tool (no execute function)\n if (!tool.execute) {\n // Check if tool needs approval\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n const resolution = approvalResolution(approvals, toolCall.id)\n\n // Check if approval decision exists\n if (resolution !== undefined) {\n const approved = isApproved(resolution)\n\n if (approved) {\n input = editedApprovalArgs(resolution) ?? input\n // Approved - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push(\n buildClientToolResult(\n toolCall.id,\n toolName,\n tool,\n clientResults.get(toolCall.id),\n input,\n ),\n )\n } else {\n // Approved but not executed yet - request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n resumeState?.deniedToolResults?.get(toolCall.id) ??\n deniedApprovalResult(resolution),\n input,\n state: 'output-error',\n })\n }\n } else {\n // Need approval first\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n input,\n approvalId,\n })\n }\n } else {\n // No approval needed - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push(\n buildClientToolResult(\n toolCall.id,\n toolName,\n tool,\n clientResults.get(toolCall.id),\n input,\n ),\n )\n } else {\n // Request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n }\n continue\n }\n\n // CASE 2: Server tool with approval required\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n const resolution = approvalResolution(approvals, toolCall.id)\n\n // Check if approval decision exists\n if (resolution !== undefined) {\n const approved = isApproved(resolution)\n\n if (approved) {\n input = editedApprovalArgs(resolution) ?? input\n // Apply middleware before-hook for approved tools\n if (middlewareHooks) {\n const decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n if (!decision.proceed) continue\n input = decision.input\n }\n\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n )\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n resumeState?.deniedToolResults?.get(toolCall.id) ??\n deniedApprovalResult(resolution),\n input,\n state: 'output-error',\n })\n }\n } else {\n // Need approval\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n approvalId,\n })\n }\n continue\n }\n\n // CASE 3: Normal server tool - execute immediately\n if (middlewareHooks) {\n const decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n if (!decision.proceed) continue\n input = decision.input\n }\n\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n )\n }\n\n return { results, needsApproval, needsClientExecution }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tool-calls.js","names":[],"sources":["../../../../../src/activities/chat/tools/tool-calls.ts"],"sourcesContent":["import { normalizeToolResult } from '../../../utilities/tool-result'\nimport { tanstackMetadata } from '../../../utilities/merge-metadata'\nimport type { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk'\nimport { isStandardSchema, parseWithStandardSchema } from './schema-converter'\nimport type { ToolApprovalResolution } from '../../../interrupts'\nimport type {\n AnyTool,\n ContentPart,\n CustomEvent,\n EmitCustomEventOptions,\n ModelMessage,\n RunFinishedEvent,\n Tool,\n ToolCall,\n ToolCallArgsEvent,\n ToolCallEndEvent,\n ToolCallStartEvent,\n ToolExecutionContext,\n ToolOutputState,\n} from '../../../types'\nimport type {\n AfterToolCallInfo,\n BeforeToolCallDecision,\n} from '../middleware/types'\nimport type { McpResourceReadResult } from '../mcp/types'\nimport type {\n ContextFromTool,\n DefinedContext,\n MergeContext,\n UnionToIntersection,\n} from '../runtime-context-types'\n\nfunction safeJsonParse(value: string): unknown {\n try {\n return JSON.parse(value)\n } catch {\n return value\n }\n}\n\n/**\n * MCP Apps metadata attached to a server tool at discovery (see\n * `@tanstack/ai-mcp` discovery + `MCPManager.discover()`).\n *\n * - `uiResourceUri` / `serverId` are stamped by ai-mcp at tool discovery.\n * - `readResource` is bound by `MCPManager.discover()` (the one site that has\n * both the tool and its originating source) so the resource can be eagerly\n * read at the emit site. Under `chat()`-managed MCP lifecycle\n * (`connection:'close'`), the MCP source is not disposed until the run\n * drains, so `readResource` is still live at this emit point. Note: a caller\n * who closes the MCP source early (outside `chat()`'s managed lifecycle)\n * degrades fail-soft — `readResource` may reject, the widget is absent, but\n * the tool result still flows to the model.\n * `@tanstack/ai` never imports `@tanstack/ai-mcp`; this travels structurally\n * on the tool.\n */\ninterface McpToolAppMeta {\n uiResourceUri?: string\n serverId?: string\n /** Server-native (unprefixed) MCP tool name — used as the renderer's toolName. */\n serverToolName?: string\n readResource?: (uri: string) => Promise<McpResourceReadResult>\n}\n\nfunction readMcpAppMeta(tool: AnyTool): McpToolAppMeta | undefined {\n const meta = (tool.metadata as { mcp?: McpToolAppMeta } | undefined)?.mcp\n return meta\n}\n\n/**\n * Eagerly read a tool's linked `ui://` resource (MCP Apps) and emit a\n * `ui-resource` CUSTOM event so the client can render the widget. The model\n * still receives the normal text tool-result; the widget rides alongside and\n * never enters model input.\n *\n * Fail-soft: any read error logs a warning and emits nothing — it never throws,\n * so the normal tool-result still flows and a broken widget cannot break the run.\n */\nasync function emitUiResourceIfLinked<TContext>(\n tool: AnyTool,\n context: ToolExecutionContext<TContext>,\n): Promise<void> {\n const mcp = readMcpAppMeta(tool)\n const uiUri = mcp?.uiResourceUri\n if (!uiUri || !mcp.readResource) return\n\n // The try covers ONLY the fallible read — keep `emitCustomEvent` out of it so\n // an exception from the emit path can't be mislabeled as a read failure.\n let matched: McpResourceReadResult['contents'][number] | undefined\n try {\n const res = await mcp.readResource(uiUri)\n // Emit ONLY the content whose uri matches the requested `uiUri`. A source\n // can return unrelated contents; falling back to `contents[0]` would risk\n // rendering a widget that doesn't correspond to the linked resource. This\n // is a display widget — a mismatched resource is worse than none, so if no\n // content matches we fail-soft (warn + return) rather than emit.\n matched = res.contents.find((c) => c.uri === uiUri)\n } catch (err) {\n // fail-soft — the text tool-result already flows; a broken widget must\n // not break the run.\n console.warn(`[mcp-apps] failed to read ui resource ${uiUri}:`, err)\n return\n }\n if (!matched) {\n console.warn(\n `[mcp-apps] ui resource ${uiUri} returned no content matching that uri; not emitting`,\n )\n return\n }\n // NOTE: `toolCallId` is intentionally NOT set here — it is stamped onto\n // every emitted event by the `executeToolCalls` context wrapper, so the\n // UIResourceEvent.value.toolCallId / UIResourcePart.toolCallId contract is\n // still satisfied downstream.\n context.emitCustomEvent('ui-resource', {\n resource: {\n uri: matched.uri,\n mimeType: matched.mimeType ?? 'text/html',\n text: matched.text,\n blob: matched.blob,\n },\n serverId: mcp.serverId,\n toolName: mcp.serverToolName ?? tool.name,\n meta: undefined,\n })\n}\n\n/**\n * Optional middleware hooks for tool execution.\n * When provided, these callbacks are invoked before/after each tool execution.\n */\nexport interface ToolExecutionMiddlewareHooks {\n onBeforeToolCall?: (\n toolCall: ToolCall,\n tool: Tool | undefined,\n args: unknown,\n ) => Promise<BeforeToolCallDecision>\n onAfterToolCall?: (info: AfterToolCallInfo) => Promise<void>\n}\n\n/**\n * Error thrown when middleware decides to abort the chat run during tool execution.\n */\nexport class MiddlewareAbortError extends Error {\n constructor(reason: string) {\n super(reason)\n this.name = 'MiddlewareAbortError'\n }\n}\n\n// The leaf context-inference primitives (ContextFromTool, MergeContext,\n// UnionToIntersection, DefinedContext) are shared with the chat activity\n// options layer — see ../runtime-context-types.\ntype RequiredContextFromToolUnion<T> = T extends unknown\n ? undefined extends ContextFromTool<T>\n ? never\n : ContextFromTool<T>\n : never\n\ntype ContextFromToolUnion<T> = [\n UnionToIntersection<DefinedContext<ContextFromTool<T>>>,\n] extends [never]\n ? unknown\n : [RequiredContextFromToolUnion<T>] extends [never]\n ? UnionToIntersection<DefinedContext<ContextFromTool<T>>> | undefined\n : UnionToIntersection<DefinedContext<ContextFromTool<T>>>\n\ntype ContextFromTools<TTools> = TTools extends readonly [\n infer THead,\n ...infer TTail,\n]\n ? MergeContext<ContextFromTool<THead>, ContextFromTools<TTail>>\n : TTools extends ReadonlyArray<infer TTool>\n ? ContextFromToolUnion<TTool>\n : unknown\n\ntype ExecuteToolsContextArgs<TContext> = undefined extends TContext\n ? [userContext?: TContext]\n : [userContext: TContext]\n\n/**\n * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.\n *\n * Responsibilities:\n * - Accumulates streaming tool call events (ID, name, arguments)\n * - Validates tool calls (filters out incomplete ones)\n * - Executes tool `execute` functions with parsed arguments\n * - Emits `TOOL_CALL_END` events for client visibility\n * - Returns tool result messages for conversation history\n *\n * This class is used internally by the AI.chat() method to handle the automatic\n * tool execution loop. It can also be used independently for custom tool execution logic.\n *\n * @example\n * ```typescript\n * const manager = new ToolCallManager(tools);\n *\n * // During streaming, accumulate tool calls\n * for await (const chunk of stream) {\n * if (chunk.type === 'TOOL_CALL_START') {\n * manager.addToolCallStartEvent(chunk);\n * } else if (chunk.type === 'TOOL_CALL_ARGS') {\n * manager.addToolCallArgsEvent(chunk);\n * }\n * }\n *\n * // After stream completes, execute tools\n * if (manager.hasToolCalls()) {\n * const toolResults = yield* manager.executeTools(finishEvent);\n * messages = [...messages, ...toolResults];\n * manager.clear();\n * }\n * ```\n */\nexport class ToolCallManager<\n TToolsOrContext = ReadonlyArray<AnyTool>,\n TContext = TToolsOrContext extends ReadonlyArray<AnyTool>\n ? ContextFromTools<TToolsOrContext>\n : TToolsOrContext,\n> {\n private readonly toolCallsMap = new Map<number, ToolCall>()\n private readonly tools: TToolsOrContext extends ReadonlyArray<AnyTool>\n ? TToolsOrContext\n : ReadonlyArray<AnyTool>\n\n constructor(\n tools: TToolsOrContext extends ReadonlyArray<AnyTool>\n ? TToolsOrContext\n : ReadonlyArray<AnyTool>,\n ) {\n this.tools = tools\n }\n\n /**\n * Add a TOOL_CALL_START event to begin tracking a tool call (AG-UI)\n */\n addToolCallStartEvent(event: ToolCallStartEvent): void {\n // AG-UI's TOOL_CALL_START carries no index, and a non-first-party or\n // malformed producer can send a second START for a toolCallId that is\n // already tracked. Without this guard, a repeat with the same index\n // overwrites the slot (wiping any TOOL_CALL_ARGS already accumulated),\n // and a repeat with a missing/different index inserts a duplicate row\n // that getToolCalls() returns twice, running the tool twice.\n for (const toolCall of this.toolCallsMap.values()) {\n if (toolCall.id === event.toolCallId) return\n }\n const index = (event as AdapterYieldChunk).index ?? this.toolCallsMap.size\n const name = event.toolCallName ?? event.toolName\n this.toolCallsMap.set(index, {\n id: event.toolCallId,\n type: 'function',\n function: {\n name,\n arguments: '',\n },\n ...(event.metadata !== undefined && { metadata: event.metadata }),\n })\n }\n\n /**\n * Add a TOOL_CALL_ARGS event to accumulate arguments (AG-UI)\n */\n addToolCallArgsEvent(event: ToolCallArgsEvent): void {\n const extra = event as AdapterYieldChunk\n for (const [, toolCall] of this.toolCallsMap.entries()) {\n if (toolCall.id === event.toolCallId) {\n if (typeof extra.args === 'string' && extra.args !== '') {\n toolCall.function.arguments = extra.args\n } else {\n toolCall.function.arguments += event.delta\n }\n break\n }\n }\n }\n\n /**\n * Complete a tool call with its final input\n * Called when TOOL_CALL_END is received\n */\n completeToolCall(event: ToolCallEndEvent): void {\n for (const toolCall of this.toolCallsMap.values()) {\n if (toolCall.id !== event.toolCallId) continue\n if (event.input === undefined) return\n const normalized =\n event.input && typeof event.input === 'object' ? event.input : {}\n toolCall.function.arguments = JSON.stringify(normalized)\n return\n }\n }\n\n /**\n * Check if there are any complete tool calls to execute\n */\n hasToolCalls(): boolean {\n return this.getToolCalls().length > 0\n }\n\n /**\n * Get all complete tool calls (filtered for valid ID and name)\n */\n getToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCallsMap.values()).filter(\n (tc) => tc.id && tc.function.name && tc.function.name.trim().length > 0,\n )\n }\n\n /**\n * Execute all tool calls and return tool result messages\n * Yields TOOL_CALL_END events for streaming\n * @param finishEvent - RUN_FINISHED event from the stream\n */\n async *executeTools(\n finishEvent: RunFinishedEvent,\n ...contextArgs: ExecuteToolsContextArgs<TContext>\n ): AsyncGenerator<AdapterYieldChunk, Array<ModelMessage>, void> {\n const toolCallsArray = this.getToolCalls()\n const toolResults: Array<ModelMessage> = []\n const hasRuntimeContext = contextArgs.length > 0\n const userContext = contextArgs[0]\n\n for (const toolCall of toolCallsArray) {\n const tool = this.tools.find((t) => t.name === toolCall.function.name)\n\n let toolResultContent: string | Array<ContentPart>\n let toolResultState: ToolOutputState | undefined\n // Holds the parsed/validated execution output before serialization.\n // Stays `undefined` when the tool has no `execute` (client-only\n // tools) or when execution throws.\n let toolOutput: unknown\n if (tool?.execute) {\n try {\n // Parse arguments (normalize null/non-object to {} for empty tool_use blocks)\n let args: unknown\n try {\n const argsString = toolCall.function.arguments.trim() || '{}'\n const parsed = JSON.parse(argsString)\n args = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n throw new Error(\n `Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`,\n )\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n args = parseWithStandardSchema(tool.inputSchema, args)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Input validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n // Execute the tool\n const executionContext = {\n toolCallId: toolCall.id,\n context: userContext,\n emitCustomEvent: () => {},\n } as ToolExecutionContext<TContext>\n let result = hasRuntimeContext\n ? await tool.execute(args, executionContext)\n : await tool.execute(args)\n\n // Validate output against outputSchema if provided (for Standard\n // Schema compliant schemas). Unlike the previous implementation we\n // intentionally validate `undefined`/`null` results too, so a tool\n // whose schema forbids them surfaces a validation error instead of\n // silently passing — the schema itself decides whether they're valid.\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n try {\n result = parseWithStandardSchema(tool.outputSchema, result)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n toolOutput = result\n toolResultContent = normalizeToolResult(result)\n } catch (error: unknown) {\n // If tool execution fails, add error message\n const message =\n error instanceof Error ? error.message : 'Unknown error'\n toolResultContent = `Error executing tool: ${message}`\n toolResultState = 'output-error'\n }\n } else {\n // Tool doesn't have execute function, add placeholder\n toolResultContent = `Tool ${toolCall.function.name} does not have an execute function`\n }\n\n // Emit TOOL_CALL_END event\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolCallName: toolCall.function.name,\n toolName: toolCall.function.name,\n model: (() => {\n const model = tanstackMetadata(finishEvent)?.model\n return typeof model === 'string' ? model : undefined\n })(),\n timestamp: Date.now(),\n // Typed parsed output (undefined for failed exec / client-only tools).\n ...(toolOutput !== undefined ? { output: toolOutput } : {}),\n result: toolResultContent,\n ...(toolResultState !== undefined && { state: toolResultState }),\n }\n\n // Add tool result message\n toolResults.push({\n role: 'tool',\n content: toolResultContent,\n toolCallId: toolCall.id,\n })\n }\n\n return toolResults\n }\n\n /**\n * Clear the tool calls map for the next iteration\n */\n clear(): void {\n this.toolCallsMap.clear()\n }\n}\n\nexport interface ToolResult {\n toolCallId: string\n toolName: string\n result: any\n state?: 'output-available' | 'output-error'\n /** Duration of tool execution in milliseconds (only for server-executed tools) */\n duration?: number\n /**\n * Parsed tool input (after JSON parse + optional Standard Schema validation).\n * Parsed tool input after JSON parse + optional Standard Schema validation.\n */\n input?: unknown\n /**\n * Parsed tool output before wire serialization. Surfaced on engine-emitted\n * `TOOL_CALL_END` events so consumers can read typed `output` without\n * re-parsing `result`. Undefined on error paths and when execution is skipped.\n */\n output?: unknown\n}\n\nexport interface ApprovalRequest {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n}\n\nexport interface ClientToolRequest {\n toolCallId: string\n toolName: string\n input: any\n}\n\nexport interface ToolResumeExecutionState {\n deniedToolResults?: ReadonlyMap<string, unknown>\n cancelledToolCallIds?: ReadonlySet<string>\n}\n\nfunction approvalResolution(\n approvals: ReadonlyMap<string, ToolApprovalResolution>,\n toolCallId: string,\n): ToolApprovalResolution | undefined {\n return approvals.get(toolCallId) ?? approvals.get(`approval_${toolCallId}`)\n}\n\nfunction isApproved(resolution: ToolApprovalResolution): boolean {\n return typeof resolution === 'boolean' ? resolution : resolution.approved\n}\n\nfunction editedApprovalArgs(\n resolution: ToolApprovalResolution,\n): unknown | undefined {\n return typeof resolution === 'object' && resolution.approved\n ? resolution.editedArgs\n : undefined\n}\n\nfunction deniedApprovalResult(resolution: ToolApprovalResolution): unknown {\n return typeof resolution === 'object' && !resolution.approved\n ? (resolution.payload ?? { error: 'User declined tool execution' })\n : { error: 'User declined tool execution' }\n}\n\ninterface ExecuteToolCallsResult {\n /** Tool results ready to send to LLM */\n results: Array<ToolResult>\n /** Tools that need user approval before execution */\n needsApproval: Array<ApprovalRequest>\n /** Tools that need client-side execution */\n needsClientExecution: Array<ClientToolRequest>\n}\n\n/**\n * Helper that runs a tool execution promise while polling for pending custom events.\n * Yields any custom events that are emitted during execution, then returns the\n * execution result.\n */\nasync function* executeWithEventPolling<T>(\n executionPromise: Promise<T>,\n pendingEvents: Array<CustomEvent>,\n): AsyncGenerator<CustomEvent, T, void> {\n // Use an object to track mutable state across the async boundary\n const state = { done: false, result: undefined as T }\n const executionWithFlag = executionPromise.then((r) => {\n state.done = true\n state.result = r\n return r\n })\n\n while (!state.done) {\n // Wait for either the execution to complete or a short timeout\n await Promise.race([\n executionWithFlag,\n new Promise((resolve) => setTimeout(resolve, 10)),\n ])\n\n // Flush any pending events\n let event: CustomEvent | undefined\n while ((event = pendingEvents.shift()) !== undefined) {\n yield event\n }\n }\n\n // Final flush in case events were emitted right at completion\n let event: CustomEvent | undefined\n while ((event = pendingEvents.shift()) !== undefined) {\n yield event\n }\n\n return state.result\n}\n\n/**\n * Apply a middleware onBeforeToolCall decision.\n * Returns the (possibly transformed) input if execution should proceed,\n * or undefined if the tool call was skipped (result already pushed).\n * Throws MiddlewareAbortError if the decision is 'abort'.\n */\nasync function applyBeforeToolCallDecision(\n toolCall: ToolCall,\n tool: Tool,\n input: unknown,\n toolName: string,\n middlewareHooks: ToolExecutionMiddlewareHooks,\n results: Array<ToolResult>,\n): Promise<{ proceed: true; input: unknown } | { proceed: false }> {\n if (!middlewareHooks.onBeforeToolCall) {\n return { proceed: true, input }\n }\n\n const decision = await middlewareHooks.onBeforeToolCall(toolCall, tool, input)\n if (!decision) {\n return { proceed: true, input }\n }\n\n if (decision.type === 'abort') {\n throw new MiddlewareAbortError(decision.reason || 'Aborted by middleware')\n }\n\n if (decision.type === 'skip') {\n const skipResult = decision.result\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof skipResult === 'string'\n ? safeJsonParse(skipResult)\n : (skipResult ?? null),\n duration: 0,\n })\n if (middlewareHooks.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration: 0,\n result: skipResult,\n })\n }\n return { proceed: false }\n }\n\n return { proceed: true, input: decision.args }\n}\n\n/**\n * Execute a server-side tool with event polling, output validation, and middleware hooks.\n * Yields CustomEvent chunks during execution and pushes the result to the results array.\n */\nexport async function* executeServerTool<TContext = unknown>(\n toolCall: ToolCall,\n tool: AnyTool,\n toolName: string,\n input: unknown,\n context: ToolExecutionContext<TContext>,\n pendingEvents: Array<CustomEvent>,\n results: Array<ToolResult>,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n): AsyncGenerator<CustomEvent, void, void> {\n const startTime = Date.now()\n try {\n if (!tool.execute) {\n throw new Error(`Tool ${toolName} has no execute() implementation`)\n }\n const executionPromise = Promise.resolve(tool.execute(input, context))\n let result = yield* executeWithEventPolling(executionPromise, pendingEvents)\n const duration = Date.now() - startTime\n\n // MCP Apps: if this tool links a ui:// resource, eagerly read it and queue\n // a `ui-resource` CUSTOM event. The MCP source stays live until the run\n // drains (MCPManager's `connection:'close'` policy disposes on completion),\n // so `readResource` is callable here. Fail-soft: a read error warns and\n // emits nothing — the text result still flows.\n await emitUiResourceIfLinked(tool, context)\n\n // Flush remaining events (including any queued ui-resource event)\n let pendingEvent: CustomEvent | undefined\n while ((pendingEvent = pendingEvents.shift()) !== undefined) {\n yield pendingEvent\n }\n\n // Validate output against outputSchema if provided. Validates\n // `undefined`/`null` too — the schema decides whether they're valid.\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n const finalResult =\n typeof result === 'string' ? safeJsonParse(result) : (result ?? null)\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: finalResult,\n input,\n output: finalResult,\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: true,\n duration,\n result: finalResult,\n })\n }\n } catch (error: unknown) {\n const duration = Date.now() - startTime\n\n // Flush remaining events\n let pendingEvent: CustomEvent | undefined\n while ((pendingEvent = pendingEvents.shift()) !== undefined) {\n yield pendingEvent\n }\n\n if (error instanceof MiddlewareAbortError) {\n throw error\n }\n\n const message = error instanceof Error ? error.message : 'Unknown error'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: message },\n input,\n state: 'output-error',\n duration,\n })\n\n if (middlewareHooks?.onAfterToolCall) {\n await middlewareHooks.onAfterToolCall({\n toolCall,\n tool,\n toolName,\n toolCallId: toolCall.id,\n ok: false,\n duration,\n error,\n })\n }\n }\n}\n\nfunction buildClientToolResult(\n toolCallId: string,\n toolName: string,\n tool: AnyTool,\n rawResult: unknown,\n input?: unknown,\n): ToolResult {\n try {\n let result = rawResult\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n result = parseWithStandardSchema(tool.outputSchema, result)\n }\n\n const parsed =\n typeof result === 'string' ? safeJsonParse(result) : (result ?? null)\n return {\n toolCallId,\n toolName,\n result: parsed,\n input,\n output: parsed,\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Validation failed'\n return {\n toolCallId,\n toolName,\n result: { error: message },\n input,\n state: 'output-error',\n }\n }\n}\n\n/**\n * Execute tool calls based on their configuration.\n * Yields CustomEvent chunks during tool execution for real-time progress updates.\n *\n * Handles three cases:\n * 1. Client tools (no execute) - request client to execute\n * 2. Server tools with approval - check approval before executing\n * 3. Normal server tools - execute immediately\n *\n * @param toolCalls - Tool calls from the LLM\n * @param tools - Available tools with their configurations\n * @param approvals - Map keyed by toolCallId (or `approval_${toolCallId}`) → ToolApprovalResolution\n * @param clientResults - Map of client-side execution results (toolCallId -> result)\n * @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)\n */\nexport async function* executeToolCalls<TContext = unknown>(\n toolCalls: Array<ToolCall>,\n tools: ReadonlyArray<AnyTool>,\n approvals: Map<string, ToolApprovalResolution> = new Map(),\n clientResults: Map<string, any> = new Map(),\n createCustomEventChunk?: (\n eventName: string,\n value: Record<string, any>,\n options?: EmitCustomEventOptions,\n ) => CustomEvent,\n middlewareHooks?: ToolExecutionMiddlewareHooks,\n userContext?: TContext,\n abortSignal?: AbortSignal,\n resumeState?: ToolResumeExecutionState,\n): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\n\n // Create tool lookup map\n const toolMap = new Map<string, AnyTool>()\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n // Batch gating: when any tool in the batch still needs an approval decision,\n // defer all execution so side effects don't happen before the user decides.\n const hasPendingApprovals = toolCalls.some((tc) => {\n const t = toolMap.get(tc.function.name)\n return (\n t?.needsApproval &&\n approvalResolution(approvals, tc.id) === undefined &&\n !resumeState?.cancelledToolCallIds?.has(tc.id)\n )\n })\n\n for (const toolCall of toolCalls) {\n const tool = toolMap.get(toolCall.function.name)\n const toolName = toolCall.function.name\n\n if (!tool) {\n // Unknown tool - return error\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: `Unknown tool: ${toolName}` },\n state: 'output-error',\n })\n continue\n }\n\n // Skip non-pending tools while approvals are outstanding\n if (hasPendingApprovals) {\n const isPendingApproval =\n tool.needsApproval &&\n approvalResolution(approvals, toolCall.id) === undefined\n const isPlainClientRequest = !tool.needsApproval && !tool.execute\n if (!isPendingApproval && !isPlainClientRequest) {\n continue\n }\n }\n\n if (resumeState?.cancelledToolCallIds?.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'Tool execution cancelled' },\n state: 'output-error',\n })\n continue\n }\n\n // Parse arguments\n let input: unknown = {}\n const argsStr = toolCall.function.arguments.trim() || '{}'\n if (argsStr) {\n try {\n const parsed = JSON.parse(argsStr)\n // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)\n input = parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Failed to parse tool arguments as JSON: ${argsStr}`,\n },\n input,\n state: 'output-error',\n })\n continue\n }\n }\n\n // Validate input against inputSchema (for Standard Schema compliant schemas)\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n input = parseWithStandardSchema(tool.inputSchema, input)\n } catch (validationError: unknown) {\n const message =\n validationError instanceof Error\n ? validationError.message\n : 'Validation failed'\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Input validation failed for tool ${tool.name}: ${message}`,\n },\n // raw parse may have failed validation — still attach best-effort input\n input,\n state: 'output-error',\n })\n continue\n }\n }\n\n // Create a ToolExecutionContext for this tool call with event emission\n const pendingEvents: Array<CustomEvent> = []\n const context = {\n toolCallId: toolCall.id,\n context: userContext,\n abortSignal,\n emitCustomEvent: (\n eventName: string,\n value: Record<string, any>,\n options?: EmitCustomEventOptions,\n ) => {\n if (createCustomEventChunk) {\n pendingEvents.push(\n createCustomEventChunk(\n eventName,\n {\n ...value,\n toolCallId: toolCall.id,\n },\n options,\n ),\n )\n }\n },\n } as ToolExecutionContext<TContext>\n\n // CASE 1: Client-side tool (no execute function)\n if (!tool.execute) {\n // Check if tool needs approval\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n const resolution = approvalResolution(approvals, toolCall.id)\n\n // Check if approval decision exists\n if (resolution !== undefined) {\n const approved = isApproved(resolution)\n\n if (approved) {\n input = editedApprovalArgs(resolution) ?? input\n // Approved - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push(\n buildClientToolResult(\n toolCall.id,\n toolName,\n tool,\n clientResults.get(toolCall.id),\n input,\n ),\n )\n } else {\n // Approved but not executed yet - request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n resumeState?.deniedToolResults?.get(toolCall.id) ??\n deniedApprovalResult(resolution),\n input,\n state: 'output-error',\n })\n }\n } else {\n // Need approval first\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n input,\n approvalId,\n })\n }\n } else {\n // No approval needed - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push(\n buildClientToolResult(\n toolCall.id,\n toolName,\n tool,\n clientResults.get(toolCall.id),\n input,\n ),\n )\n } else {\n // Request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n }\n continue\n }\n\n // CASE 2: Server tool with approval required\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n const resolution = approvalResolution(approvals, toolCall.id)\n\n // Check if approval decision exists\n if (resolution !== undefined) {\n const approved = isApproved(resolution)\n\n if (approved) {\n input = editedApprovalArgs(resolution) ?? input\n // Apply middleware before-hook for approved tools\n if (middlewareHooks) {\n const decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n if (!decision.proceed) continue\n input = decision.input\n }\n\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n )\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n resumeState?.deniedToolResults?.get(toolCall.id) ??\n deniedApprovalResult(resolution),\n input,\n state: 'output-error',\n })\n }\n } else {\n // Need approval\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n approvalId,\n })\n }\n continue\n }\n\n // CASE 3: Normal server tool - execute immediately\n if (middlewareHooks) {\n const decision = await applyBeforeToolCallDecision(\n toolCall,\n tool,\n input,\n toolName,\n middlewareHooks,\n results,\n )\n if (!decision.proceed) continue\n input = decision.input\n }\n\n yield* executeServerTool(\n toolCall,\n tool,\n toolName,\n input,\n context,\n pendingEvents,\n results,\n middlewareHooks,\n )\n }\n\n return { results, needsApproval, needsClientExecution }\n}\n"],"mappings":";;;;AAgCA,SAAS,cAAc,OAAwB;CAC7C,IAAI;EACF,OAAO,KAAK,MAAM,KAAK;CACzB,QAAQ;EACN,OAAO;CACT;AACF;AA0BA,SAAS,eAAe,MAA2C;CAEjE,OADc,KAAK,UAAmD;AAExE;;;;;;;;;;AAWA,eAAe,uBACb,MACA,SACe;CACf,MAAM,MAAM,eAAe,IAAI;CAC/B,MAAM,QAAQ,KAAK;CACnB,IAAI,CAAC,SAAS,CAAC,IAAI,cAAc;CAIjC,IAAI;CACJ,IAAI;EAOF,WAAU,MANQ,IAAI,aAAa,KAAK,EAAA,CAM1B,SAAS,MAAM,MAAM,EAAE,QAAQ,KAAK;CACpD,SAAS,KAAK;EAGZ,QAAQ,KAAK,yCAAyC,MAAM,IAAI,GAAG;EACnE;CACF;CACA,IAAI,CAAC,SAAS;EACZ,QAAQ,KACN,0BAA0B,MAAM,qDAClC;EACA;CACF;CAKA,QAAQ,gBAAgB,eAAe;EACrC,UAAU;GACR,KAAK,QAAQ;GACb,UAAU,QAAQ,YAAY;GAC9B,MAAM,QAAQ;GACd,MAAM,QAAQ;EAChB;EACA,UAAU,IAAI;EACd,UAAU,IAAI,kBAAkB,KAAK;EACrC,MAAM,KAAA;CACR,CAAC;AACH;;;;AAkBA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,YAAY,QAAgB;EAC1B,MAAM,MAAM;EACZ,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkEA,IAAa,kBAAb,MAKE;CACA,+BAAgC,IAAI,IAAsB;CAC1D;CAIA,YACE,OAGA;EACA,KAAK,QAAQ;CACf;;;;CAKA,sBAAsB,OAAiC;EAOrD,KAAK,MAAM,YAAY,KAAK,aAAa,OAAO,GAC9C,IAAI,SAAS,OAAO,MAAM,YAAY;EAExC,MAAM,QAAS,MAA4B,SAAS,KAAK,aAAa;EACtE,MAAM,OAAO,MAAM,gBAAgB,MAAM;EACzC,KAAK,aAAa,IAAI,OAAO;GAC3B,IAAI,MAAM;GACV,MAAM;GACN,UAAU;IACR;IACA,WAAW;GACb;GACA,GAAI,MAAM,aAAa,KAAA,KAAa,EAAE,UAAU,MAAM,SAAS;EACjE,CAAC;CACH;;;;CAKA,qBAAqB,OAAgC;EACnD,MAAM,QAAQ;EACd,KAAK,MAAM,GAAG,aAAa,KAAK,aAAa,QAAQ,GACnD,IAAI,SAAS,OAAO,MAAM,YAAY;GACpC,IAAI,OAAO,MAAM,SAAS,YAAY,MAAM,SAAS,IACnD,SAAS,SAAS,YAAY,MAAM;QAEpC,SAAS,SAAS,aAAa,MAAM;GAEvC;EACF;CAEJ;;;;;CAMA,iBAAiB,OAA+B;EAC9C,KAAK,MAAM,YAAY,KAAK,aAAa,OAAO,GAAG;GACjD,IAAI,SAAS,OAAO,MAAM,YAAY;GACtC,IAAI,MAAM,UAAU,KAAA,GAAW;GAC/B,MAAM,aACJ,MAAM,SAAS,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ,CAAC;GAClE,SAAS,SAAS,YAAY,KAAK,UAAU,UAAU;GACvD;EACF;CACF;;;;CAKA,eAAwB;EACtB,OAAO,KAAK,aAAa,CAAC,CAAC,SAAS;CACtC;;;;CAKA,eAAgC;EAC9B,OAAO,MAAM,KAAK,KAAK,aAAa,OAAO,CAAC,CAAC,CAAC,QAC3C,OAAO,GAAG,MAAM,GAAG,SAAS,QAAQ,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,SAAS,CACxE;CACF;;;;;;CAOA,OAAO,aACL,aACA,GAAG,aAC2D;EAC9D,MAAM,iBAAiB,KAAK,aAAa;EACzC,MAAM,cAAmC,CAAC;EAC1C,MAAM,oBAAoB,YAAY,SAAS;EAC/C,MAAM,cAAc,YAAY;EAEhC,KAAK,MAAM,YAAY,gBAAgB;GACrC,MAAM,OAAO,KAAK,MAAM,MAAM,MAAM,EAAE,SAAS,SAAS,SAAS,IAAI;GAErE,IAAI;GACJ,IAAI;GAIJ,IAAI;GACJ,IAAI,MAAM,SACR,IAAI;IAEF,IAAI;IACJ,IAAI;KACF,MAAM,aAAa,SAAS,SAAS,UAAU,KAAK,KAAK;KACzD,MAAM,SAAS,KAAK,MAAM,UAAU;KACpC,OAAO,UAAU,OAAO,WAAW,WAAW,SAAS,CAAC;IAC1D,SAAS,YAAY;KACnB,MAAM,IAAI,MACR,2CAA2C,SAAS,SAAS,WAC/D;IACF;IAGA,IAAI,KAAK,eAAe,iBAAiB,KAAK,WAAW,GACvD,IAAI;KACF,OAAO,wBAAwB,KAAK,aAAa,IAAI;IACvD,SAAS,iBAA0B;KACjC,MAAM,UACJ,2BAA2B,QACvB,gBAAgB,UAChB;KACN,MAAM,IAAI,MACR,oCAAoC,KAAK,KAAK,IAAI,SACpD;IACF;IAIF,MAAM,mBAAmB;KACvB,YAAY,SAAS;KACrB,SAAS;KACT,uBAAuB,CAAC;IAC1B;IACA,IAAI,SAAS,oBACT,MAAM,KAAK,QAAQ,MAAM,gBAAgB,IACzC,MAAM,KAAK,QAAQ,IAAI;IAO3B,IAAI,KAAK,gBAAgB,iBAAiB,KAAK,YAAY,GACzD,IAAI;KACF,SAAS,wBAAwB,KAAK,cAAc,MAAM;IAC5D,SAAS,iBAA0B;KACjC,MAAM,UACJ,2BAA2B,QACvB,gBAAgB,UAChB;KACN,MAAM,IAAI,MACR,qCAAqC,KAAK,KAAK,IAAI,SACrD;IACF;IAGF,aAAa;IACb,oBAAoB,oBAAoB,MAAM;GAChD,SAAS,OAAgB;IAIvB,oBAAoB,yBADlB,iBAAiB,QAAQ,MAAM,UAAU;IAE3C,kBAAkB;GACpB;QAGA,oBAAoB,QAAQ,SAAS,SAAS,KAAK;GAIrD,MAAM;IACJ,MAAM;IACN,YAAY,SAAS;IACrB,cAAc,SAAS,SAAS;IAChC,UAAU,SAAS,SAAS;IAC5B,cAAc;KACZ,MAAM,QAAQ,iBAAiB,WAAW,CAAC,EAAE;KAC7C,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;IAC7C,EAAA,CAAG;IACH,WAAW,KAAK,IAAI;IAEpB,GAAI,eAAe,KAAA,IAAY,EAAE,QAAQ,WAAW,IAAI,CAAC;IACzD,QAAQ;IACR,GAAI,oBAAoB,KAAA,KAAa,EAAE,OAAO,gBAAgB;GAChE;GAGA,YAAY,KAAK;IACf,MAAM;IACN,SAAS;IACT,YAAY,SAAS;GACvB,CAAC;EACH;EAEA,OAAO;CACT;;;;CAKA,QAAc;EACZ,KAAK,aAAa,MAAM;CAC1B;AACF;AAwCA,SAAS,mBACP,WACA,YACoC;CACpC,OAAO,UAAU,IAAI,UAAU,KAAK,UAAU,IAAI,YAAY,YAAY;AAC5E;AAEA,SAAS,WAAW,YAA6C;CAC/D,OAAO,OAAO,eAAe,YAAY,aAAa,WAAW;AACnE;AAEA,SAAS,mBACP,YACqB;CACrB,OAAO,OAAO,eAAe,YAAY,WAAW,WAChD,WAAW,aACX,KAAA;AACN;AAEA,SAAS,qBAAqB,YAA6C;CACzE,OAAO,OAAO,eAAe,YAAY,CAAC,WAAW,WAChD,WAAW,WAAW,EAAE,OAAO,+BAA+B,IAC/D,EAAE,OAAO,+BAA+B;AAC9C;;;;;;AAgBA,gBAAgB,wBACd,kBACA,eACsC;CAEtC,MAAM,QAAQ;EAAE,MAAM;EAAO,QAAQ,KAAA;CAAe;CACpD,MAAM,oBAAoB,iBAAiB,MAAM,MAAM;EACrD,MAAM,OAAO;EACb,MAAM,SAAS;EACf,OAAO;CACT,CAAC;CAED,OAAO,CAAC,MAAM,MAAM;EAElB,MAAM,QAAQ,KAAK,CACjB,mBACA,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC,CAClD,CAAC;EAGD,IAAI;EACJ,QAAQ,QAAQ,cAAc,MAAM,OAAO,KAAA,GACzC,MAAM;CAEV;CAGA,IAAI;CACJ,QAAQ,QAAQ,cAAc,MAAM,OAAO,KAAA,GACzC,MAAM;CAGR,OAAO,MAAM;AACf;;;;;;;AAQA,eAAe,4BACb,UACA,MACA,OACA,UACA,iBACA,SACiE;CACjE,IAAI,CAAC,gBAAgB,kBACnB,OAAO;EAAE,SAAS;EAAM;CAAM;CAGhC,MAAM,WAAW,MAAM,gBAAgB,iBAAiB,UAAU,MAAM,KAAK;CAC7E,IAAI,CAAC,UACH,OAAO;EAAE,SAAS;EAAM;CAAM;CAGhC,IAAI,SAAS,SAAS,SACpB,MAAM,IAAI,qBAAqB,SAAS,UAAU,uBAAuB;CAG3E,IAAI,SAAS,SAAS,QAAQ;EAC5B,MAAM,aAAa,SAAS;EAC5B,QAAQ,KAAK;GACX,YAAY,SAAS;GACrB;GACA,QACE,OAAO,eAAe,WAClB,cAAc,UAAU,IACvB,cAAc;GACrB,UAAU;EACZ,CAAC;EACD,IAAI,gBAAgB,iBAClB,MAAM,gBAAgB,gBAAgB;GACpC;GACA;GACA;GACA,YAAY,SAAS;GACrB,IAAI;GACJ,UAAU;GACV,QAAQ;EACV,CAAC;EAEH,OAAO,EAAE,SAAS,MAAM;CAC1B;CAEA,OAAO;EAAE,SAAS;EAAM,OAAO,SAAS;CAAK;AAC/C;;;;;AAMA,gBAAuB,kBACrB,UACA,MACA,UACA,OACA,SACA,eACA,SACA,iBACyC;CACzC,MAAM,YAAY,KAAK,IAAI;CAC3B,IAAI;EACF,IAAI,CAAC,KAAK,SACR,MAAM,IAAI,MAAM,QAAQ,SAAS,iCAAiC;EAGpE,IAAI,SAAS,OAAO,wBADK,QAAQ,QAAQ,KAAK,QAAQ,OAAO,OAAO,CACxB,GAAkB,aAAa;EAC3E,MAAM,WAAW,KAAK,IAAI,IAAI;EAO9B,MAAM,uBAAuB,MAAM,OAAO;EAG1C,IAAI;EACJ,QAAQ,eAAe,cAAc,MAAM,OAAO,KAAA,GAChD,MAAM;EAKR,IAAI,KAAK,gBAAgB,iBAAiB,KAAK,YAAY,GACzD,SAAS,wBAAwB,KAAK,cAAc,MAAM;EAG5D,MAAM,cACJ,OAAO,WAAW,WAAW,cAAc,MAAM,IAAK,UAAU;EAElE,QAAQ,KAAK;GACX,YAAY,SAAS;GACrB;GACA,QAAQ;GACR;GACA,QAAQ;GACR;EACF,CAAC;EAED,IAAI,iBAAiB,iBACnB,MAAM,gBAAgB,gBAAgB;GACpC;GACA;GACA;GACA,YAAY,SAAS;GACrB,IAAI;GACJ;GACA,QAAQ;EACV,CAAC;CAEL,SAAS,OAAgB;EACvB,MAAM,WAAW,KAAK,IAAI,IAAI;EAG9B,IAAI;EACJ,QAAQ,eAAe,cAAc,MAAM,OAAO,KAAA,GAChD,MAAM;EAGR,IAAI,iBAAiB,sBACnB,MAAM;EAGR,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;EACzD,QAAQ,KAAK;GACX,YAAY,SAAS;GACrB;GACA,QAAQ,EAAE,OAAO,QAAQ;GACzB;GACA,OAAO;GACP;EACF,CAAC;EAED,IAAI,iBAAiB,iBACnB,MAAM,gBAAgB,gBAAgB;GACpC;GACA;GACA;GACA,YAAY,SAAS;GACrB,IAAI;GACJ;GACA;EACF,CAAC;CAEL;AACF;AAEA,SAAS,sBACP,YACA,UACA,MACA,WACA,OACY;CACZ,IAAI;EACF,IAAI,SAAS;EACb,IAAI,KAAK,gBAAgB,iBAAiB,KAAK,YAAY,GACzD,SAAS,wBAAwB,KAAK,cAAc,MAAM;EAG5D,MAAM,SACJ,OAAO,WAAW,WAAW,cAAc,MAAM,IAAK,UAAU;EAClE,OAAO;GACL;GACA;GACA,QAAQ;GACR;GACA,QAAQ;EACV;CACF,SAAS,OAAgB;EAEvB,OAAO;GACL;GACA;GACA,QAAQ,EAAE,OAJI,iBAAiB,QAAQ,MAAM,UAAU,oBAI9B;GACzB;GACA,OAAO;EACT;CACF;AACF;;;;;;;;;;;;;;;;AAiBA,gBAAuB,iBACrB,WACA,OACA,4BAAiD,IAAI,IAAI,GACzD,gCAAkC,IAAI,IAAI,GAC1C,wBAKA,iBACA,aACA,aACA,aAC2D;CAC3D,MAAM,UAA6B,CAAC;CACpC,MAAM,gBAAwC,CAAC;CAC/C,MAAM,uBAAiD,CAAC;CAGxD,MAAM,0BAAU,IAAI,IAAqB;CACzC,KAAK,MAAM,QAAQ,OACjB,QAAQ,IAAI,KAAK,MAAM,IAAI;CAK7B,MAAM,sBAAsB,UAAU,MAAM,OAAO;EAEjD,OADU,QAAQ,IAAI,GAAG,SAAS,IAEhC,CAAA,EAAG,iBACH,mBAAmB,WAAW,GAAG,EAAE,MAAM,KAAA,KACzC,CAAC,aAAa,sBAAsB,IAAI,GAAG,EAAE;CAEjD,CAAC;CAED,KAAK,MAAM,YAAY,WAAW;EAChC,MAAM,OAAO,QAAQ,IAAI,SAAS,SAAS,IAAI;EAC/C,MAAM,WAAW,SAAS,SAAS;EAEnC,IAAI,CAAC,MAAM;GAET,QAAQ,KAAK;IACX,YAAY,SAAS;IACrB;IACA,QAAQ,EAAE,OAAO,iBAAiB,WAAW;IAC7C,OAAO;GACT,CAAC;GACD;EACF;EAGA,IAAI,qBAAqB;GACvB,MAAM,oBACJ,KAAK,iBACL,mBAAmB,WAAW,SAAS,EAAE,MAAM,KAAA;GACjD,MAAM,uBAAuB,CAAC,KAAK,iBAAiB,CAAC,KAAK;GAC1D,IAAI,CAAC,qBAAqB,CAAC,sBACzB;EAEJ;EAEA,IAAI,aAAa,sBAAsB,IAAI,SAAS,EAAE,GAAG;GACvD,QAAQ,KAAK;IACX,YAAY,SAAS;IACrB;IACA,QAAQ,EAAE,OAAO,2BAA2B;IAC5C,OAAO;GACT,CAAC;GACD;EACF;EAGA,IAAI,QAAiB,CAAC;EACtB,MAAM,UAAU,SAAS,SAAS,UAAU,KAAK,KAAK;EACtD,IAAI,SACF,IAAI;GACF,MAAM,SAAS,KAAK,MAAM,OAAO;GAEjC,QAAQ,UAAU,OAAO,WAAW,WAAW,SAAS,CAAC;EAC3D,QAAQ;GACN,QAAQ,KAAK;IACX,YAAY,SAAS;IACrB;IACA,QAAQ,EACN,OAAO,2CAA2C,UACpD;IACA;IACA,OAAO;GACT,CAAC;GACD;EACF;EAIF,IAAI,KAAK,eAAe,iBAAiB,KAAK,WAAW,GACvD,IAAI;GACF,QAAQ,wBAAwB,KAAK,aAAa,KAAK;EACzD,SAAS,iBAA0B;GACjC,MAAM,UACJ,2BAA2B,QACvB,gBAAgB,UAChB;GACN,QAAQ,KAAK;IACX,YAAY,SAAS;IACrB;IACA,QAAQ,EACN,OAAO,oCAAoC,KAAK,KAAK,IAAI,UAC3D;IAEA;IACA,OAAO;GACT,CAAC;GACD;EACF;EAIF,MAAM,gBAAoC,CAAC;EAC3C,MAAM,UAAU;GACd,YAAY,SAAS;GACrB,SAAS;GACT;GACA,kBACE,WACA,OACA,YACG;IACH,IAAI,wBACF,cAAc,KACZ,uBACE,WACA;KACE,GAAG;KACH,YAAY,SAAS;IACvB,GACA,OACF,CACF;GAEJ;EACF;EAGA,IAAI,CAAC,KAAK,SAAS;GAEjB,IAAI,KAAK,eAAe;IACtB,MAAM,aAAa,YAAY,SAAS;IACxC,MAAM,aAAa,mBAAmB,WAAW,SAAS,EAAE;IAG5D,IAAI,eAAe,KAAA,GAAW;KAG5B,IAFiB,WAAW,UAExB,GAAU;MACZ,QAAQ,mBAAmB,UAAU,KAAK;MAE1C,IAAI,cAAc,IAAI,SAAS,EAAE,GAC/B,QAAQ,KACN,sBACE,SAAS,IACT,UACA,MACA,cAAc,IAAI,SAAS,EAAE,GAC7B,KACF,CACF;WAGA,qBAAqB,KAAK;OACxB,YAAY,SAAS;OACrB;OACA;MACF,CAAC;KAEL,OAEE,QAAQ,KAAK;MACX,YAAY,SAAS;MACrB;MACA,QACE,aAAa,mBAAmB,IAAI,SAAS,EAAE,KAC/C,qBAAqB,UAAU;MACjC;MACA,OAAO;KACT,CAAC;IAEL,OAEE,cAAc,KAAK;KACjB,YAAY,SAAS;KACrB,UAAU,SAAS,SAAS;KAC5B;KACA;IACF,CAAC;GAEL,OAEE,IAAI,cAAc,IAAI,SAAS,EAAE,GAC/B,QAAQ,KACN,sBACE,SAAS,IACT,UACA,MACA,cAAc,IAAI,SAAS,EAAE,GAC7B,KACF,CACF;QAGA,qBAAqB,KAAK;IACxB,YAAY,SAAS;IACrB;IACA;GACF,CAAC;GAGL;EACF;EAGA,IAAI,KAAK,eAAe;GACtB,MAAM,aAAa,YAAY,SAAS;GACxC,MAAM,aAAa,mBAAmB,WAAW,SAAS,EAAE;GAG5D,IAAI,eAAe,KAAA,GAAW;IAG5B,IAFiB,WAAW,UAExB,GAAU;KACZ,QAAQ,mBAAmB,UAAU,KAAK;KAE1C,IAAI,iBAAiB;MACnB,MAAM,WAAW,MAAM,4BACrB,UACA,MACA,OACA,UACA,iBACA,OACF;MACA,IAAI,CAAC,SAAS,SAAS;MACvB,QAAQ,SAAS;KACnB;KAEA,OAAO,kBACL,UACA,MACA,UACA,OACA,SACA,eACA,SACA,eACF;IACF,OAEE,QAAQ,KAAK;KACX,YAAY,SAAS;KACrB;KACA,QACE,aAAa,mBAAmB,IAAI,SAAS,EAAE,KAC/C,qBAAqB,UAAU;KACjC;KACA,OAAO;IACT,CAAC;GAEL,OAEE,cAAc,KAAK;IACjB,YAAY,SAAS;IACrB;IACA;IACA;GACF,CAAC;GAEH;EACF;EAGA,IAAI,iBAAiB;GACnB,MAAM,WAAW,MAAM,4BACrB,UACA,MACA,OACA,UACA,iBACA,OACF;GACA,IAAI,CAAC,SAAS,SAAS;GACvB,QAAQ,SAAS;EACnB;EAEA,OAAO,kBACL,UACA,MACA,UACA,OACA,SACA,eACA,SACA,eACF;CACF;CAEA,OAAO;EAAE;EAAS;EAAe;CAAqB;AACxD"}
@@ -0,0 +1,160 @@
1
+ import { InternalLogger } from '../../logger/internal-logger.js';
2
+ import { TokenUsage } from '../../types.js';
3
+ /**
4
+ * Configuration for evaluate adapter instances.
5
+ */
6
+ export interface EvaluateAdapterConfig {
7
+ apiKey?: string;
8
+ baseUrl?: string;
9
+ timeout?: number;
10
+ headers?: Record<string, string>;
11
+ }
12
+ /**
13
+ * Shared JSON value for `state` and question `instructions`.
14
+ * A JSON array is one value, not a batch.
15
+ */
16
+ export type EvaluateJsonValue = string | object | Array<unknown>;
17
+ /** Content the model judges. A JSON array is one state, not a batch. */
18
+ export type EvaluateState = EvaluateJsonValue;
19
+ /** Question text. Matches TypeSafe: string, object, or array. */
20
+ export type EvaluateInstructions = EvaluateJsonValue;
21
+ /**
22
+ * TypeSafe choice question on the adapter wire.
23
+ *
24
+ * Generic parameters:
25
+ * - TOptions: option key to description (or `null` when the key is enough)
26
+ */
27
+ export interface WireChoiceQuestion<TOptions extends Record<string, string | null> = Record<string, string | null>> {
28
+ type: 'choice';
29
+ instructions: EvaluateInstructions;
30
+ criteria: TOptions;
31
+ }
32
+ /**
33
+ * TypeSafe score question on the adapter wire.
34
+ *
35
+ * Generic parameters:
36
+ * - TLevels: ordered level labels, at least two
37
+ */
38
+ export interface WireScoreQuestion<TLevels extends ReadonlyArray<string> = ReadonlyArray<string>> {
39
+ type: 'score';
40
+ instructions: EvaluateInstructions;
41
+ criteria: TLevels;
42
+ }
43
+ /**
44
+ * TypeSafe yes/no question on the adapter wire.
45
+ * Public helpers call this `boolean`. The wire type is `noul`.
46
+ */
47
+ export interface WireNoulQuestion {
48
+ type: 'noul';
49
+ instructions: EvaluateInstructions;
50
+ criteria?: {
51
+ true?: string;
52
+ false?: string;
53
+ };
54
+ }
55
+ /** Question payload adapters send to the provider. */
56
+ export type WireQuestion = WireChoiceQuestion | WireScoreQuestion | WireNoulQuestion;
57
+ /** TypeSafe choice answer. Adapters do not invent a public `.value`. */
58
+ export interface WireChoiceAnswer {
59
+ type: 'choice';
60
+ choice: string;
61
+ probabilities: Record<string, number>;
62
+ confidence: number;
63
+ }
64
+ /** TypeSafe score answer. `score` is the raw fraction. */
65
+ export interface WireScoreAnswer {
66
+ type: 'score';
67
+ score: number;
68
+ legend: Record<string, string>;
69
+ probabilities: Record<string, number>;
70
+ confidence: number;
71
+ }
72
+ /** TypeSafe yes/no answer. `noul` is P(true). */
73
+ export interface WireNoulAnswer {
74
+ type: 'noul';
75
+ noul: number;
76
+ }
77
+ /** Provider payload for one question. The activity maps this to a unified answer. */
78
+ export type WireAnswer = WireChoiceAnswer | WireScoreAnswer | WireNoulAnswer;
79
+ /**
80
+ * Options passed to {@link EvaluateAdapter.evaluate}.
81
+ */
82
+ export interface EvaluateOptions<TProviderOptions extends object = Record<string, unknown>> {
83
+ model: string;
84
+ /** Shared state every question judges. A JSON array is one state, not a batch. */
85
+ state: EvaluateState;
86
+ /** TypeSafe wire questions, keyed by the caller's question ids. */
87
+ questions: Record<string, WireQuestion>;
88
+ /** Provider-specific options forwarded by `decide()`. */
89
+ modelOptions?: TProviderOptions;
90
+ /** Forwarded to the provider request for cancellation. */
91
+ abortSignal?: AbortSignal;
92
+ /**
93
+ * Internal logger threaded from `decide()`. Adapters must call
94
+ * `logger.request()` before the provider call and `logger.errors()` in catch
95
+ * blocks.
96
+ */
97
+ logger: InternalLogger;
98
+ }
99
+ /**
100
+ * Provider-level evaluate result. Adapters return the wire payload plus usage.
101
+ * The activity maps answers to the unified public shape.
102
+ */
103
+ export interface EvaluateAdapterResult {
104
+ /** Resolved model id from the provider. */
105
+ model: string;
106
+ answers: Record<string, WireAnswer>;
107
+ usage: TokenUsage;
108
+ }
109
+ /**
110
+ * Evaluate adapter interface with pre-resolved generics.
111
+ *
112
+ * An adapter is created by a provider function: `provider('model')` → `adapter`.
113
+ * All type resolution happens at the provider call site, not in this interface.
114
+ *
115
+ * Generic parameters:
116
+ * - TModel: The specific model name (e.g. `'jev-latest'`)
117
+ * - TProviderOptions: Provider-specific options (already resolved)
118
+ */
119
+ export interface EvaluateAdapter<TModel extends string = string, TProviderOptions extends object = Record<string, unknown>> {
120
+ /** Discriminator for adapter kind */
121
+ readonly kind: 'evaluate';
122
+ /** Adapter name identifier */
123
+ readonly name: string;
124
+ /** The model this adapter is configured for */
125
+ readonly model: TModel;
126
+ /**
127
+ * @internal Type-only properties for inference. Not assigned at runtime.
128
+ */
129
+ '~types': {
130
+ providerOptions: TProviderOptions;
131
+ };
132
+ /**
133
+ * Evaluate typed questions against `state`. Return the provider payload.
134
+ * Do not invent unified `.value` fields. The activity maps wire answers.
135
+ */
136
+ evaluate: (options: EvaluateOptions<TProviderOptions>) => Promise<EvaluateAdapterResult>;
137
+ }
138
+ /**
139
+ * An EvaluateAdapter with any/unknown type parameters.
140
+ * Useful as a constraint in generic functions and interfaces.
141
+ */
142
+ export type AnyEvaluateAdapter = EvaluateAdapter<any, any>;
143
+ /**
144
+ * Abstract base class for evaluate adapters.
145
+ * Extend this class to implement an evaluate adapter for a specific provider.
146
+ *
147
+ * Generic parameters match EvaluateAdapter. The provider function resolves them.
148
+ */
149
+ export declare abstract class BaseEvaluateAdapter<TModel extends string = string, TProviderOptions extends object = Record<string, unknown>> implements EvaluateAdapter<TModel, TProviderOptions> {
150
+ readonly kind: "evaluate";
151
+ abstract readonly name: string;
152
+ readonly model: TModel;
153
+ '~types': {
154
+ providerOptions: TProviderOptions;
155
+ };
156
+ protected config: EvaluateAdapterConfig;
157
+ constructor(config: EvaluateAdapterConfig | undefined, model: TModel);
158
+ abstract evaluate(options: EvaluateOptions<TProviderOptions>): Promise<EvaluateAdapterResult>;
159
+ protected generateId(): string;
160
+ }
@@ -0,0 +1,23 @@
1
+ //#region src/activities/evaluate/adapter.ts
2
+ /**
3
+ * Abstract base class for evaluate adapters.
4
+ * Extend this class to implement an evaluate adapter for a specific provider.
5
+ *
6
+ * Generic parameters match EvaluateAdapter. The provider function resolves them.
7
+ */
8
+ var BaseEvaluateAdapter = class {
9
+ kind = "evaluate";
10
+ model;
11
+ config;
12
+ constructor(config = {}, model) {
13
+ this.config = config;
14
+ this.model = model;
15
+ }
16
+ generateId() {
17
+ return `${this.name}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
18
+ }
19
+ };
20
+ //#endregion
21
+ export { BaseEvaluateAdapter };
22
+
23
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","names":[],"sources":["../../../../src/activities/evaluate/adapter.ts"],"sourcesContent":["import type { InternalLogger } from '../../logger/internal-logger'\nimport type { TokenUsage } from '../../types'\n\n/**\n * Configuration for evaluate adapter instances.\n */\nexport interface EvaluateAdapterConfig {\n apiKey?: string\n baseUrl?: string\n timeout?: number\n headers?: Record<string, string>\n}\n\n/**\n * Shared JSON value for `state` and question `instructions`.\n * A JSON array is one value, not a batch.\n */\nexport type EvaluateJsonValue = string | object | Array<unknown>\n\n/** Content the model judges. A JSON array is one state, not a batch. */\nexport type EvaluateState = EvaluateJsonValue\n\n/** Question text. Matches TypeSafe: string, object, or array. */\nexport type EvaluateInstructions = EvaluateJsonValue\n\n/**\n * TypeSafe choice question on the adapter wire.\n *\n * Generic parameters:\n * - TOptions: option key to description (or `null` when the key is enough)\n */\nexport interface WireChoiceQuestion<\n TOptions extends Record<string, string | null> = Record<\n string,\n string | null\n >,\n> {\n type: 'choice'\n instructions: EvaluateInstructions\n criteria: TOptions\n}\n\n/**\n * TypeSafe score question on the adapter wire.\n *\n * Generic parameters:\n * - TLevels: ordered level labels, at least two\n */\nexport interface WireScoreQuestion<\n TLevels extends ReadonlyArray<string> = ReadonlyArray<string>,\n> {\n type: 'score'\n instructions: EvaluateInstructions\n criteria: TLevels\n}\n\n/**\n * TypeSafe yes/no question on the adapter wire.\n * Public helpers call this `boolean`. The wire type is `noul`.\n */\nexport interface WireNoulQuestion {\n type: 'noul'\n instructions: EvaluateInstructions\n criteria?: {\n true?: string\n false?: string\n }\n}\n\n/** Question payload adapters send to the provider. */\nexport type WireQuestion =\n | WireChoiceQuestion\n | WireScoreQuestion\n | WireNoulQuestion\n\n/** TypeSafe choice answer. Adapters do not invent a public `.value`. */\nexport interface WireChoiceAnswer {\n type: 'choice'\n choice: string\n probabilities: Record<string, number>\n confidence: number\n}\n\n/** TypeSafe score answer. `score` is the raw fraction. */\nexport interface WireScoreAnswer {\n type: 'score'\n score: number\n legend: Record<string, string>\n probabilities: Record<string, number>\n confidence: number\n}\n\n/** TypeSafe yes/no answer. `noul` is P(true). */\nexport interface WireNoulAnswer {\n type: 'noul'\n noul: number\n}\n\n/** Provider payload for one question. The activity maps this to a unified answer. */\nexport type WireAnswer = WireChoiceAnswer | WireScoreAnswer | WireNoulAnswer\n\n/**\n * Options passed to {@link EvaluateAdapter.evaluate}.\n */\nexport interface EvaluateOptions<\n TProviderOptions extends object = Record<string, unknown>,\n> {\n model: string\n /** Shared state every question judges. A JSON array is one state, not a batch. */\n state: EvaluateState\n /** TypeSafe wire questions, keyed by the caller's question ids. */\n questions: Record<string, WireQuestion>\n /** Provider-specific options forwarded by `decide()`. */\n modelOptions?: TProviderOptions\n /** Forwarded to the provider request for cancellation. */\n abortSignal?: AbortSignal\n /**\n * Internal logger threaded from `decide()`. Adapters must call\n * `logger.request()` before the provider call and `logger.errors()` in catch\n * blocks.\n */\n logger: InternalLogger\n}\n\n/**\n * Provider-level evaluate result. Adapters return the wire payload plus usage.\n * The activity maps answers to the unified public shape.\n */\nexport interface EvaluateAdapterResult {\n /** Resolved model id from the provider. */\n model: string\n answers: Record<string, WireAnswer>\n usage: TokenUsage\n}\n\n/**\n * Evaluate adapter interface with pre-resolved generics.\n *\n * An adapter is created by a provider function: `provider('model')` → `adapter`.\n * All type resolution happens at the provider call site, not in this interface.\n *\n * Generic parameters:\n * - TModel: The specific model name (e.g. `'jev-latest'`)\n * - TProviderOptions: Provider-specific options (already resolved)\n */\nexport interface EvaluateAdapter<\n TModel extends string = string,\n TProviderOptions extends object = Record<string, unknown>,\n> {\n /** Discriminator for adapter kind */\n readonly kind: 'evaluate'\n /** Adapter name identifier */\n readonly name: string\n /** The model this adapter is configured for */\n readonly model: TModel\n\n /**\n * @internal Type-only properties for inference. Not assigned at runtime.\n */\n '~types': {\n providerOptions: TProviderOptions\n }\n\n /**\n * Evaluate typed questions against `state`. Return the provider payload.\n * Do not invent unified `.value` fields. The activity maps wire answers.\n */\n evaluate: (\n options: EvaluateOptions<TProviderOptions>,\n ) => Promise<EvaluateAdapterResult>\n}\n\n/**\n * An EvaluateAdapter with any/unknown type parameters.\n * Useful as a constraint in generic functions and interfaces.\n */\nexport type AnyEvaluateAdapter = EvaluateAdapter<any, any>\n\n/**\n * Abstract base class for evaluate adapters.\n * Extend this class to implement an evaluate adapter for a specific provider.\n *\n * Generic parameters match EvaluateAdapter. The provider function resolves them.\n */\nexport abstract class BaseEvaluateAdapter<\n TModel extends string = string,\n TProviderOptions extends object = Record<string, unknown>,\n> implements EvaluateAdapter<TModel, TProviderOptions> {\n readonly kind = 'evaluate' as const\n abstract readonly name: string\n readonly model: TModel\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: TProviderOptions\n }\n\n protected config: EvaluateAdapterConfig\n\n constructor(config: EvaluateAdapterConfig = {}, model: TModel) {\n this.config = config\n this.model = model\n }\n\n abstract evaluate(\n options: EvaluateOptions<TProviderOptions>,\n ): Promise<EvaluateAdapterResult>\n\n protected generateId(): string {\n return `${this.name}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n }\n}\n"],"mappings":";;;;;;;AAwLA,IAAsB,sBAAtB,MAGuD;CACrD,OAAgB;CAEhB;CAOA;CAEA,YAAY,SAAgC,CAAC,GAAG,OAAe;EAC7D,KAAK,SAAS;EACd,KAAK,QAAQ;CACf;CAMA,aAA+B;EAC7B,OAAO,GAAG,KAAK,KAAK,GAAG,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;CAC5E;AACF"}