@tanstack/ai 0.51.0 → 0.52.1

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.
@@ -97,7 +97,8 @@ var MiddlewareRunner = class {
97
97
  if (hasTransform) {
98
98
  current = {
99
99
  ...current,
100
- ...result
100
+ ...result,
101
+ ..."messages" in result && !("providerMessages" in result) ? { providerMessages: result.messages } : {}
101
102
  };
102
103
  if (!skip) this.logger.config(`middleware=${mw.name ?? "unnamed"} keys=${Object.keys(result).join(",")}`, {
103
104
  middleware: mw.name ?? "unnamed",
@@ -142,7 +143,8 @@ var MiddlewareRunner = class {
142
143
  if (hasTransform) {
143
144
  current = {
144
145
  ...current,
145
- ...result
146
+ ...result,
147
+ ..."messages" in result && !("providerMessages" in result) ? { providerMessages: result.messages } : {}
146
148
  };
147
149
  if (!skip) this.logger.config(`middleware=${mw.name ?? "unnamed"} keys=${Object.keys(result).join(",")}`, {
148
150
  middleware: mw.name ?? "unnamed",
@@ -1 +1 @@
1
- {"version":3,"file":"compose.js","names":[],"sources":["../../../../../src/activities/chat/middleware/compose.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai-event-client'\nimport type { AgentLoopState, StreamChunk } from '../../../types'\nimport type { InternalLogger } from '../../../logger/internal-logger'\nimport type {\n AbortInfo,\n AfterToolCallInfo,\n BeforeToolCallDecision,\n ChatMiddleware,\n ChatMiddlewareConfig,\n ChatMiddlewareContext,\n ErrorInfo,\n FinishInfo,\n InterruptBoundaryPhase,\n InterruptResolutionCollection,\n InterruptToolResume,\n IterationInfo,\n SandboxFileHookEvent,\n StructuredOutputMiddlewareConfig,\n ToolCallHookContext,\n ToolPhaseCompleteInfo,\n UsageInfo,\n} from './types'\nimport type {\n GenericInterruptRequest,\n InterruptDefinition,\n} from '../../../interrupt-definition'\n\n/** One middleware's terminal-hook throw, captured instead of propagated. */\ninterface HookFailure {\n middleware: string\n error: unknown\n}\n\n/** Check if a middleware should be skipped for instrumentation events. */\nfunction shouldSkipInstrumentation(mw: ChatMiddleware<any, any>): boolean {\n return mw.name === 'devtools' || mw.name === 'strip-to-spec'\n}\n\n/** Build the base context for middleware instrumentation events. */\nfunction instrumentCtx(ctx: ChatMiddlewareContext<any>) {\n return {\n requestId: ctx.requestId,\n streamId: ctx.streamId,\n clientId: ctx.threadId,\n timestamp: Date.now(),\n }\n}\n\n/**\n * Internal middleware runner that manages composed execution of middleware hooks.\n * Created once per chat() invocation.\n */\nexport class MiddlewareRunner<\n TContext = unknown,\n TInterruptDefinitions extends InterruptDefinition<any, any, any, any> =\n InterruptDefinition<any, any, any, any>,\n> {\n private readonly middlewares: ReadonlyArray<\n ChatMiddleware<TContext, TInterruptDefinitions>\n >\n private readonly logger: InternalLogger\n\n constructor(\n middlewares: ReadonlyArray<ChatMiddleware<TContext, TInterruptDefinitions>>,\n logger: InternalLogger,\n ) {\n this.middlewares = middlewares\n this.logger = logger\n }\n\n get hasMiddleware(): boolean {\n return this.middlewares.length > 0\n }\n\n async runOnInterruptBoundary(\n ctx: ChatMiddlewareContext<TContext> & { phase: InterruptBoundaryPhase },\n ): Promise<ReadonlyArray<GenericInterruptRequest<TInterruptDefinitions>>> {\n const requests: Array<GenericInterruptRequest<TInterruptDefinitions>> = []\n for (const mw of this.middlewares) {\n if (mw.onInterruptBoundary) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onInterruptBoundary(ctx)\n if (result?.interrupts) requests.push(...result.interrupts)\n if (!skip) {\n this.logger.middleware(\n `hook=onInterruptBoundary middleware=${mw.name ?? 'unnamed'}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onInterruptBoundary',\n },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onInterruptBoundary',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: result?.interrupts !== undefined,\n })\n }\n }\n }\n return requests\n }\n\n async runOnInterruptResolution(\n ctx: ChatMiddlewareContext<TContext>,\n resolutions: InterruptResolutionCollection<TInterruptDefinitions>,\n ): Promise<{ toolResume?: InterruptToolResume }> {\n let toolResume: InterruptToolResume | undefined\n for (const mw of this.middlewares) {\n if (mw.onInterruptResolution) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const next = await mw.onInterruptResolution(ctx, resolutions)\n if (next?.toolResume !== undefined) {\n const priority: Record<InterruptToolResume, number> = {\n continue: 0,\n cancel: 1,\n stop: 2,\n }\n if (\n toolResume === undefined ||\n priority[next.toolResume] > priority[toolResume]\n ) {\n toolResume = next.toolResume\n }\n }\n if (!skip) {\n this.logger.middleware(\n `hook=onInterruptResolution middleware=${mw.name ?? 'unnamed'}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onInterruptResolution',\n },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onInterruptResolution',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: next !== undefined,\n })\n }\n }\n }\n return toolResume === undefined ? {} : { toolResume }\n }\n\n /**\n * Pipe config through all middleware onConfig hooks in order.\n * Each middleware receives the merged config from previous middleware.\n * Partial returns are shallow-merged with the current config.\n */\n async runOnConfig(\n ctx: ChatMiddlewareContext<TContext>,\n config: ChatMiddlewareConfig,\n ): Promise<ChatMiddlewareConfig> {\n let current = config\n for (const mw of this.middlewares) {\n if (mw.onConfig) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onConfig(ctx, current)\n const hasTransform = result !== undefined && result !== null\n if (hasTransform) {\n current = { ...current, ...result }\n if (!skip) {\n this.logger.config(\n `middleware=${mw.name ?? 'unnamed'} keys=${Object.keys(result).join(',')}`,\n {\n middleware: mw.name ?? 'unnamed',\n changes: result,\n },\n )\n }\n }\n if (!skip) {\n const base = instrumentCtx(ctx)\n aiEventClient.emit('middleware:hook:executed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n hookName: 'onConfig',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform,\n })\n if (hasTransform) {\n aiEventClient.emit('middleware:config:transformed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n iteration: ctx.iteration,\n changes: result,\n })\n }\n }\n }\n }\n return current\n }\n\n /**\n * Pipe config through all middleware onStructuredOutputConfig hooks in order.\n * Each middleware receives the merged config from previous middleware.\n * Partial returns are shallow-merged with the current config.\n *\n * Called once at the structured-output boundary, before runOnConfig at the\n * same boundary (which receives a ChatMiddlewareConfig view, no outputSchema).\n */\n async runOnStructuredOutputConfig(\n ctx: ChatMiddlewareContext<TContext>,\n config: StructuredOutputMiddlewareConfig,\n ): Promise<StructuredOutputMiddlewareConfig> {\n let current = config\n for (const mw of this.middlewares) {\n if (mw.onStructuredOutputConfig) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onStructuredOutputConfig(ctx, current)\n const hasTransform = result !== undefined && result !== null\n if (hasTransform) {\n current = { ...current, ...result }\n if (!skip) {\n this.logger.config(\n `middleware=${mw.name ?? 'unnamed'} keys=${Object.keys(result).join(',')}`,\n {\n middleware: mw.name ?? 'unnamed',\n changes: result,\n },\n )\n }\n }\n if (!skip) {\n const base = instrumentCtx(ctx)\n aiEventClient.emit('middleware:hook:executed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n hookName: 'onStructuredOutputConfig',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform,\n })\n if (hasTransform) {\n aiEventClient.emit('middleware:config:transformed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n iteration: ctx.iteration,\n // `result` is `Partial<StructuredOutputMiddlewareConfig>` —\n // Object.fromEntries(Object.entries(result)) yields the\n // structural `Record<string, unknown>` the event emitter wants\n // without an `as` cast.\n changes: Object.fromEntries(Object.entries(result)),\n })\n }\n }\n }\n }\n return current\n }\n\n /**\n * Run all `setup` hooks in array order, then assert every declared `provides`\n * capability was actually provided. Wires the last-wins duplicate-provide\n * warning into the registry. Runs before init `onConfig`.\n *\n * Takes the full `ChatMiddlewareContext` — the same stable context the engine\n * threads through every other hook — because it both forwards `ctx` to each\n * `setup` hook and emits instrumentation events from it.\n */\n async runSetup(ctx: ChatMiddlewareContext<TContext>): Promise<void> {\n ctx.capabilities.setOnDuplicate((name) => {\n this.logger.warn(\n `capability \"${name}\" was provided more than once; last provider wins`,\n { capability: name },\n )\n })\n\n for (const mw of this.middlewares) {\n if (mw.setup) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.setup(ctx)\n if (!skip) {\n this.logger.middleware(\n `hook=setup middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'setup' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'setup',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n\n for (const mw of this.middlewares) {\n for (const handle of mw.provides ?? []) {\n if (!ctx.capabilities.has(handle)) {\n throw new Error(\n `Middleware \"${mw.name ?? 'unnamed'}\" declares it provides ` +\n `\"${handle.capabilityName}\" but never called provide() in setup().`,\n )\n }\n }\n }\n }\n\n /**\n * Call onStart on all middleware in order.\n */\n async runOnStart(ctx: ChatMiddlewareContext<TContext>): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onStart) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onStart(ctx)\n if (!skip) {\n this.logger.middleware(\n `hook=onStart middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onStart' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onStart',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Pipe a single chunk through all middleware onChunk hooks in order.\n * Returns the resulting chunks (0..N) to yield to the consumer.\n *\n * - void: pass through unchanged\n * - chunk: replace with this chunk\n * - chunk[]: expand to multiple chunks\n * - null: drop the chunk entirely\n */\n async runOnChunk(\n ctx: ChatMiddlewareContext<TContext>,\n chunk: StreamChunk,\n ): Promise<Array<StreamChunk>> {\n let chunks: Array<StreamChunk> = [chunk]\n\n for (const mw of this.middlewares) {\n if (!mw.onChunk) continue\n const skip = shouldSkipInstrumentation(mw)\n\n const nextChunks: Array<StreamChunk> = []\n for (const c of chunks) {\n // Cast: @ag-ui/core Zod passthrough types prevent direct `.type` access\n const chunkType = c.type\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onChunk', in: c },\n )\n }\n const result = await mw.onChunk(ctx, c)\n if (result === null) {\n // Drop this chunk\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=<dropped>`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n dropped: true,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: 0,\n wasDropped: true,\n })\n }\n continue\n } else if (result === undefined) {\n // Pass through — no instrumentation for pass-throughs\n nextChunks.push(c)\n } else if (Array.isArray(result)) {\n // Expand\n nextChunks.push(...result)\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=[${result.map((r: StreamChunk) => r.type).join(',')}]`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n in: c,\n out: result,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: result.length,\n wasDropped: false,\n })\n }\n } else {\n // Replace\n nextChunks.push(result)\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=${result.type}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n in: c,\n out: result,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: 1,\n wasDropped: false,\n })\n }\n }\n }\n chunks = nextChunks\n }\n\n return chunks\n }\n\n /**\n * Dispatch a sandbox file event to every middleware's `sandbox` hooks, in\n * array order: the catch-all `onFile` then the type-specific hook. Errors are\n * logged and swallowed so one bad hook can't break the run.\n */\n async runSandboxFile(\n ctx: ChatMiddlewareContext<TContext>,\n event: SandboxFileHookEvent,\n ): Promise<void> {\n const typed = (\n {\n create: 'onFileCreate',\n change: 'onFileChange',\n delete: 'onFileDelete',\n } as const\n )[event.type]\n for (const mw of this.middlewares) {\n const hooks = mw.sandbox\n if (!hooks) continue\n for (const fn of [hooks.onFile, hooks[typed]]) {\n if (!fn) continue\n try {\n await fn(ctx, event)\n } catch (error) {\n this.logger.sandbox(\n `hook=${typed} middleware=${mw.name ?? 'unnamed'} threw`,\n { middleware: mw.name ?? 'unnamed', error },\n )\n }\n }\n }\n }\n\n /**\n * Run onBeforeToolCall through middleware in order.\n * Returns the first non-void decision, or undefined to continue normally.\n */\n async runOnBeforeToolCall(\n ctx: ChatMiddlewareContext<TContext>,\n hookCtx: ToolCallHookContext,\n ): Promise<BeforeToolCallDecision> {\n for (const mw of this.middlewares) {\n if (mw.onBeforeToolCall) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const decision = await mw.onBeforeToolCall(ctx, hookCtx)\n const hasTransform = decision !== undefined && decision !== null\n if (!skip) {\n this.logger.middleware(\n `hook=onBeforeToolCall middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onBeforeToolCall' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onBeforeToolCall',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform,\n })\n }\n if (hasTransform) {\n return decision\n }\n }\n }\n return undefined\n }\n\n /**\n * Run onAfterToolCall on all middleware in order.\n */\n async runOnAfterToolCall(\n ctx: ChatMiddlewareContext<TContext>,\n info: AfterToolCallInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onAfterToolCall) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onAfterToolCall(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onAfterToolCall middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onAfterToolCall' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onAfterToolCall',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onUsage on all middleware in order.\n */\n async runOnUsage(\n ctx: ChatMiddlewareContext<TContext>,\n usage: UsageInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onUsage) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onUsage(ctx, usage)\n if (!skip) {\n this.logger.middleware(\n `hook=onUsage middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onUsage' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onUsage',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Await ONE terminal hook and RETURN its throw instead of letting it escape\n * the caller's loop, logging it on the `errors` channel first so the failure\n * is never invisible. `undefined` means the hook completed.\n *\n * Capturing (rather than swallowing at this level) is what lets isolation and\n * reporting coexist: every caller gives every middleware its turn, and then\n * each decides on its own whether the collected failures are worth telling the\n * caller about. See {@link runOnFinish} vs {@link runOnAbort} /\n * {@link runOnError}.\n */\n private async captureTerminalHook(\n mw: ChatMiddleware<TContext, TInterruptDefinitions>,\n hookName: 'onFinish' | 'onAbort' | 'onError',\n invoke: () => void | Promise<void>,\n ): Promise<HookFailure | undefined> {\n try {\n await invoke()\n return undefined\n } catch (error) {\n this.logger.errors(`middleware ${hookName} hook failed`, {\n middleware: mw.name ?? 'unnamed',\n hook: hookName,\n error,\n })\n return { middleware: mw.name ?? 'unnamed', error }\n }\n }\n\n /**\n * Run onFinish on all middleware in order.\n *\n * ISOLATED **and** REPORTED. `onFinish` is the only terminal fan-out on the\n * SUCCESS path, and it is where `withPersistence.onFinish` writes the\n * assistant turn through the store. So the two properties are needed together\n * and neither may be traded for the other:\n *\n * - ISOLATION: every middleware's hook runs even if an earlier one threw, so a\n * transient store error cannot skip a later middleware's own bookkeeping.\n * Each failure is captured by {@link captureTerminalHook}, not propagated\n * mid-loop.\n * - REPORTING: after the loop, the failures are rethrown. `chat()`'s catch\n * treats what we throw as a genuine error (it is not a\n * `MiddlewareAbortError`, and `structuralInterruptFailure` does not match\n * it) and rethrows it out of the generator.\n *\n * What that rethrow can and cannot achieve depends on the transport, because\n * this fan-out is awaited AFTER the adapter's `RUN_FINISHED` has already been\n * yielded (`chat()` yields terminal chunks while streaming, then awaits this\n * hook on its way out). The success terminal is therefore already gone; the\n * rethrow can only append to what the consumer saw, never retract it:\n *\n * - NON-DURABLE transport: the throw escapes the generator mid-response, and\n * the SSE / HTTP-stream encoder turns it into a TRAILING `RUN_ERROR` on the\n * wire carrying the store's own message and `code`. `ai-client` surfaces\n * that as an error status, so the user is not told the turn was saved when\n * it was not.\n * - DURABLE transport: the throw reaches the durability sink instead. The\n * terminal was already persisted AND forwarded, so the sink deliberately\n * does NOT append a second, contradictory terminal, and `terminalForwarded`\n * (see `stream-to-response.ts`) suppresses the rethrow to the live consumer.\n * The `RUN_FINISHED` stands and the failure is RECORDED SERVER-SIDE on the\n * sink's `errors` channel. That is the intended outcome, not a gap: the save\n * failed, not the run — the consumer did receive the complete stream, so\n * telling it the run errored would be the lie. What the rethrow buys here is\n * that the sink sees the failure at all; while this loop swallowed, the only\n * trace anywhere was {@link captureTerminalHook}'s log line.\n *\n * Either way, swallowing is the one option ruled out: a failed\n * `messages.append` would otherwise leave a `completed` run record with the\n * assistant turn missing from storage and nothing beyond a middleware log\n * line, and the client would go on to send a history the server has no record\n * of.\n *\n * A single failure is rethrown AS-IS so the store's own error — its message,\n * `cause`, `code` and `instanceof` identity — is what reaches the caller and\n * the wire; wrapping the common case would bury it. Two or more become an\n * `AggregateError` (never a `MiddlewareAbortError`, so it cannot be mistaken\n * for an abort) rather than picking a winner and dropping the rest.\n */\n async runOnFinish(\n ctx: ChatMiddlewareContext<TContext>,\n info: FinishInfo,\n ): Promise<void> {\n const failures: Array<HookFailure> = []\n let firstFailure: HookFailure | undefined\n\n for (const mw of this.middlewares) {\n const hook = mw.onFinish\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onFinish', () =>\n hook.call(mw, ctx, info),\n )\n if (failure !== undefined) {\n firstFailure ??= failure\n failures.push(failure)\n continue\n }\n if (!skip) {\n this.logger.middleware(\n `hook=onFinish middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onFinish' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onFinish',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n\n if (firstFailure !== undefined) {\n throw failures.length === 1\n ? firstFailure.error\n : new AggregateError(\n failures.map((f) => f.error),\n `${failures.length} middleware onFinish hooks failed: ` +\n failures.map((f) => f.middleware).join(', '),\n )\n }\n }\n\n /**\n * Run onAbort on all middleware in order.\n *\n * ISOLATED and DELIBERATELY SWALLOWED. `onAbort` is a pure teardown fan-out\n * released from `chat()`'s `finally`, on a path where the outcome is already\n * decided: the run stopped, and the caller is being told why. A throw here has\n * nothing better to report than the abort reason it would DISPLACE — the\n * `finally` would surface a flaky store's error in place of \"client\n * disconnected\" — so failures are logged on the `errors` channel and go no\n * further. That is not a silent failure; it is refusing to let teardown\n * rewrite an outcome it did not produce.\n *\n * Isolation matters independently: these hooks release PER-MIDDLEWARE\n * resources (`withSandbox.onAbort` detaches or destroys the sandbox and stamps\n * `detachedSince`; `withPersistence.onAbort` records the run status through the\n * store), so an unguarded loop turns one transient store error into a\n * permanently leaked sandbox for every middleware ordered after it.\n */\n async runOnAbort(\n ctx: ChatMiddlewareContext<TContext>,\n info: AbortInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n const hook = mw.onAbort\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onAbort', () =>\n hook.call(mw, ctx, info),\n )\n if (failure === undefined && !skip) {\n this.logger.middleware(\n `hook=onAbort middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onAbort' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onAbort',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onError on all middleware in order.\n *\n * ISOLATED and DELIBERATELY SWALLOWED, for the same reason as\n * {@link runOnAbort} and NOT merely because it is teardown: the run has\n * already failed, `info.error` IS that failure, and `chat()` rethrows it to the\n * caller the moment this fan-out returns. A propagated hook throw could only\n * REPLACE the run's real error with a teardown artifact — strictly less\n * information for the caller, who is already learning the run failed. Reporting\n * would buy nothing and cost the diagnosis, so failures are logged on the\n * `errors` channel and stop there.\n *\n * Contrast {@link runOnFinish}, where nothing else is telling the caller\n * anything is wrong — which is why that one reports.\n */\n async runOnError(\n ctx: ChatMiddlewareContext<TContext>,\n info: ErrorInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n const hook = mw.onError\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onError', () =>\n hook.call(mw, ctx, info),\n )\n if (failure === undefined && !skip) {\n this.logger.middleware(\n `hook=onError middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onError' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onError',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onIteration on all middleware in order.\n * Called at the start of each agent loop iteration.\n */\n async runOnIteration(\n ctx: ChatMiddlewareContext<TContext>,\n info: IterationInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onIteration) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onIteration(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onIteration middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onIteration' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onIteration',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onShouldContinue through middleware in order (AND semantics).\n * Any explicit `false` stops further iterations; `true` / void / undefined pass.\n * Called after `agentLoopStrategy` has already approved continuation.\n */\n async runOnShouldContinue(\n ctx: ChatMiddlewareContext<TContext>,\n state: AgentLoopState,\n ): Promise<boolean> {\n for (const mw of this.middlewares) {\n if (mw.onShouldContinue) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onShouldContinue(ctx, state)\n if (!skip) {\n this.logger.middleware(\n `hook=onShouldContinue middleware=${mw.name ?? 'unnamed'}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onShouldContinue',\n result,\n },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onShouldContinue',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: result === false,\n })\n }\n if (result === false) {\n return false\n }\n }\n }\n return true\n }\n\n /**\n * Run onToolPhaseComplete on all middleware in order.\n * Called after all tool calls in an iteration have been processed.\n */\n async runOnToolPhaseComplete(\n ctx: ChatMiddlewareContext<TContext>,\n info: ToolPhaseCompleteInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onToolPhaseComplete) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onToolPhaseComplete(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onToolPhaseComplete middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onToolPhaseComplete' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onToolPhaseComplete',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"compose.js","names":[],"sources":["../../../../../src/activities/chat/middleware/compose.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai-event-client'\nimport type { AgentLoopState, StreamChunk } from '../../../types'\nimport type { InternalLogger } from '../../../logger/internal-logger'\nimport type {\n AbortInfo,\n AfterToolCallInfo,\n BeforeToolCallDecision,\n ChatMiddleware,\n ChatMiddlewareConfig,\n ChatMiddlewareContext,\n ErrorInfo,\n FinishInfo,\n InterruptBoundaryPhase,\n InterruptResolutionCollection,\n InterruptToolResume,\n IterationInfo,\n SandboxFileHookEvent,\n StructuredOutputMiddlewareConfig,\n ToolCallHookContext,\n ToolPhaseCompleteInfo,\n UsageInfo,\n} from './types'\nimport type {\n GenericInterruptRequest,\n InterruptDefinition,\n} from '../../../interrupt-definition'\n\n/** One middleware's terminal-hook throw, captured instead of propagated. */\ninterface HookFailure {\n middleware: string\n error: unknown\n}\n\n/** Check if a middleware should be skipped for instrumentation events. */\nfunction shouldSkipInstrumentation(mw: ChatMiddleware<any, any>): boolean {\n return mw.name === 'devtools' || mw.name === 'strip-to-spec'\n}\n\n/** Build the base context for middleware instrumentation events. */\nfunction instrumentCtx(ctx: ChatMiddlewareContext<any>) {\n return {\n requestId: ctx.requestId,\n streamId: ctx.streamId,\n clientId: ctx.threadId,\n timestamp: Date.now(),\n }\n}\n\n/**\n * Internal middleware runner that manages composed execution of middleware hooks.\n * Created once per chat() invocation.\n */\nexport class MiddlewareRunner<\n TContext = unknown,\n TInterruptDefinitions extends InterruptDefinition<any, any, any, any> =\n InterruptDefinition<any, any, any, any>,\n> {\n private readonly middlewares: ReadonlyArray<\n ChatMiddleware<TContext, TInterruptDefinitions>\n >\n private readonly logger: InternalLogger\n\n constructor(\n middlewares: ReadonlyArray<ChatMiddleware<TContext, TInterruptDefinitions>>,\n logger: InternalLogger,\n ) {\n this.middlewares = middlewares\n this.logger = logger\n }\n\n get hasMiddleware(): boolean {\n return this.middlewares.length > 0\n }\n\n async runOnInterruptBoundary(\n ctx: ChatMiddlewareContext<TContext> & { phase: InterruptBoundaryPhase },\n ): Promise<ReadonlyArray<GenericInterruptRequest<TInterruptDefinitions>>> {\n const requests: Array<GenericInterruptRequest<TInterruptDefinitions>> = []\n for (const mw of this.middlewares) {\n if (mw.onInterruptBoundary) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onInterruptBoundary(ctx)\n if (result?.interrupts) requests.push(...result.interrupts)\n if (!skip) {\n this.logger.middleware(\n `hook=onInterruptBoundary middleware=${mw.name ?? 'unnamed'}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onInterruptBoundary',\n },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onInterruptBoundary',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: result?.interrupts !== undefined,\n })\n }\n }\n }\n return requests\n }\n\n async runOnInterruptResolution(\n ctx: ChatMiddlewareContext<TContext>,\n resolutions: InterruptResolutionCollection<TInterruptDefinitions>,\n ): Promise<{ toolResume?: InterruptToolResume }> {\n let toolResume: InterruptToolResume | undefined\n for (const mw of this.middlewares) {\n if (mw.onInterruptResolution) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const next = await mw.onInterruptResolution(ctx, resolutions)\n if (next?.toolResume !== undefined) {\n const priority: Record<InterruptToolResume, number> = {\n continue: 0,\n cancel: 1,\n stop: 2,\n }\n if (\n toolResume === undefined ||\n priority[next.toolResume] > priority[toolResume]\n ) {\n toolResume = next.toolResume\n }\n }\n if (!skip) {\n this.logger.middleware(\n `hook=onInterruptResolution middleware=${mw.name ?? 'unnamed'}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onInterruptResolution',\n },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onInterruptResolution',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: next !== undefined,\n })\n }\n }\n }\n return toolResume === undefined ? {} : { toolResume }\n }\n\n /**\n * Pipe config through all middleware onConfig hooks in order.\n * Each middleware receives the merged config from previous middleware.\n * Partial returns are shallow-merged with the current config.\n */\n async runOnConfig(\n ctx: ChatMiddlewareContext<TContext>,\n config: ChatMiddlewareConfig,\n ): Promise<ChatMiddlewareConfig> {\n let current = config\n for (const mw of this.middlewares) {\n if (mw.onConfig) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onConfig(ctx, current)\n const hasTransform = result !== undefined && result !== null\n if (hasTransform) {\n current = {\n ...current,\n ...result,\n ...('messages' in result && !('providerMessages' in result)\n ? { providerMessages: result.messages }\n : {}),\n }\n if (!skip) {\n this.logger.config(\n `middleware=${mw.name ?? 'unnamed'} keys=${Object.keys(result).join(',')}`,\n {\n middleware: mw.name ?? 'unnamed',\n changes: result,\n },\n )\n }\n }\n if (!skip) {\n const base = instrumentCtx(ctx)\n aiEventClient.emit('middleware:hook:executed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n hookName: 'onConfig',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform,\n })\n if (hasTransform) {\n aiEventClient.emit('middleware:config:transformed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n iteration: ctx.iteration,\n changes: result,\n })\n }\n }\n }\n }\n return current\n }\n\n /**\n * Pipe config through all middleware onStructuredOutputConfig hooks in order.\n * Each middleware receives the merged config from previous middleware.\n * Partial returns are shallow-merged with the current config.\n *\n * Called once at the structured-output boundary, before runOnConfig at the\n * same boundary (which receives a ChatMiddlewareConfig view, no outputSchema).\n */\n async runOnStructuredOutputConfig(\n ctx: ChatMiddlewareContext<TContext>,\n config: StructuredOutputMiddlewareConfig,\n ): Promise<StructuredOutputMiddlewareConfig> {\n let current = config\n for (const mw of this.middlewares) {\n if (mw.onStructuredOutputConfig) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onStructuredOutputConfig(ctx, current)\n const hasTransform = result !== undefined && result !== null\n if (hasTransform) {\n current = {\n ...current,\n ...result,\n ...('messages' in result && !('providerMessages' in result)\n ? { providerMessages: result.messages }\n : {}),\n }\n if (!skip) {\n this.logger.config(\n `middleware=${mw.name ?? 'unnamed'} keys=${Object.keys(result).join(',')}`,\n {\n middleware: mw.name ?? 'unnamed',\n changes: result,\n },\n )\n }\n }\n if (!skip) {\n const base = instrumentCtx(ctx)\n aiEventClient.emit('middleware:hook:executed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n hookName: 'onStructuredOutputConfig',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform,\n })\n if (hasTransform) {\n aiEventClient.emit('middleware:config:transformed', {\n ...base,\n middlewareName: mw.name || 'unnamed',\n iteration: ctx.iteration,\n // `result` is `Partial<StructuredOutputMiddlewareConfig>` —\n // Object.fromEntries(Object.entries(result)) yields the\n // structural `Record<string, unknown>` the event emitter wants\n // without an `as` cast.\n changes: Object.fromEntries(Object.entries(result)),\n })\n }\n }\n }\n }\n return current\n }\n\n /**\n * Run all `setup` hooks in array order, then assert every declared `provides`\n * capability was actually provided. Wires the last-wins duplicate-provide\n * warning into the registry. Runs before init `onConfig`.\n *\n * Takes the full `ChatMiddlewareContext` — the same stable context the engine\n * threads through every other hook — because it both forwards `ctx` to each\n * `setup` hook and emits instrumentation events from it.\n */\n async runSetup(ctx: ChatMiddlewareContext<TContext>): Promise<void> {\n ctx.capabilities.setOnDuplicate((name) => {\n this.logger.warn(\n `capability \"${name}\" was provided more than once; last provider wins`,\n { capability: name },\n )\n })\n\n for (const mw of this.middlewares) {\n if (mw.setup) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.setup(ctx)\n if (!skip) {\n this.logger.middleware(\n `hook=setup middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'setup' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'setup',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n\n for (const mw of this.middlewares) {\n for (const handle of mw.provides ?? []) {\n if (!ctx.capabilities.has(handle)) {\n throw new Error(\n `Middleware \"${mw.name ?? 'unnamed'}\" declares it provides ` +\n `\"${handle.capabilityName}\" but never called provide() in setup().`,\n )\n }\n }\n }\n }\n\n /**\n * Call onStart on all middleware in order.\n */\n async runOnStart(ctx: ChatMiddlewareContext<TContext>): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onStart) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onStart(ctx)\n if (!skip) {\n this.logger.middleware(\n `hook=onStart middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onStart' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onStart',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Pipe a single chunk through all middleware onChunk hooks in order.\n * Returns the resulting chunks (0..N) to yield to the consumer.\n *\n * - void: pass through unchanged\n * - chunk: replace with this chunk\n * - chunk[]: expand to multiple chunks\n * - null: drop the chunk entirely\n */\n async runOnChunk(\n ctx: ChatMiddlewareContext<TContext>,\n chunk: StreamChunk,\n ): Promise<Array<StreamChunk>> {\n let chunks: Array<StreamChunk> = [chunk]\n\n for (const mw of this.middlewares) {\n if (!mw.onChunk) continue\n const skip = shouldSkipInstrumentation(mw)\n\n const nextChunks: Array<StreamChunk> = []\n for (const c of chunks) {\n // Cast: @ag-ui/core Zod passthrough types prevent direct `.type` access\n const chunkType = c.type\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onChunk', in: c },\n )\n }\n const result = await mw.onChunk(ctx, c)\n if (result === null) {\n // Drop this chunk\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=<dropped>`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n dropped: true,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: 0,\n wasDropped: true,\n })\n }\n continue\n } else if (result === undefined) {\n // Pass through — no instrumentation for pass-throughs\n nextChunks.push(c)\n } else if (Array.isArray(result)) {\n // Expand\n nextChunks.push(...result)\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=[${result.map((r: StreamChunk) => r.type).join(',')}]`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n in: c,\n out: result,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: result.length,\n wasDropped: false,\n })\n }\n } else {\n // Replace\n nextChunks.push(result)\n if (!skip) {\n this.logger.middleware(\n `hook=onChunk middleware=${mw.name ?? 'unnamed'} in=${chunkType} out=${result.type}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onChunk',\n in: c,\n out: result,\n },\n )\n aiEventClient.emit('middleware:chunk:transformed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n originalChunkType: chunkType,\n resultCount: 1,\n wasDropped: false,\n })\n }\n }\n }\n chunks = nextChunks\n }\n\n return chunks\n }\n\n /**\n * Dispatch a sandbox file event to every middleware's `sandbox` hooks, in\n * array order: the catch-all `onFile` then the type-specific hook. Errors are\n * logged and swallowed so one bad hook can't break the run.\n */\n async runSandboxFile(\n ctx: ChatMiddlewareContext<TContext>,\n event: SandboxFileHookEvent,\n ): Promise<void> {\n const typed = (\n {\n create: 'onFileCreate',\n change: 'onFileChange',\n delete: 'onFileDelete',\n } as const\n )[event.type]\n for (const mw of this.middlewares) {\n const hooks = mw.sandbox\n if (!hooks) continue\n for (const fn of [hooks.onFile, hooks[typed]]) {\n if (!fn) continue\n try {\n await fn(ctx, event)\n } catch (error) {\n this.logger.sandbox(\n `hook=${typed} middleware=${mw.name ?? 'unnamed'} threw`,\n { middleware: mw.name ?? 'unnamed', error },\n )\n }\n }\n }\n }\n\n /**\n * Run onBeforeToolCall through middleware in order.\n * Returns the first non-void decision, or undefined to continue normally.\n */\n async runOnBeforeToolCall(\n ctx: ChatMiddlewareContext<TContext>,\n hookCtx: ToolCallHookContext,\n ): Promise<BeforeToolCallDecision> {\n for (const mw of this.middlewares) {\n if (mw.onBeforeToolCall) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const decision = await mw.onBeforeToolCall(ctx, hookCtx)\n const hasTransform = decision !== undefined && decision !== null\n if (!skip) {\n this.logger.middleware(\n `hook=onBeforeToolCall middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onBeforeToolCall' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onBeforeToolCall',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform,\n })\n }\n if (hasTransform) {\n return decision\n }\n }\n }\n return undefined\n }\n\n /**\n * Run onAfterToolCall on all middleware in order.\n */\n async runOnAfterToolCall(\n ctx: ChatMiddlewareContext<TContext>,\n info: AfterToolCallInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onAfterToolCall) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onAfterToolCall(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onAfterToolCall middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onAfterToolCall' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onAfterToolCall',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onUsage on all middleware in order.\n */\n async runOnUsage(\n ctx: ChatMiddlewareContext<TContext>,\n usage: UsageInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onUsage) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onUsage(ctx, usage)\n if (!skip) {\n this.logger.middleware(\n `hook=onUsage middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onUsage' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onUsage',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Await ONE terminal hook and RETURN its throw instead of letting it escape\n * the caller's loop, logging it on the `errors` channel first so the failure\n * is never invisible. `undefined` means the hook completed.\n *\n * Capturing (rather than swallowing at this level) is what lets isolation and\n * reporting coexist: every caller gives every middleware its turn, and then\n * each decides on its own whether the collected failures are worth telling the\n * caller about. See {@link runOnFinish} vs {@link runOnAbort} /\n * {@link runOnError}.\n */\n private async captureTerminalHook(\n mw: ChatMiddleware<TContext, TInterruptDefinitions>,\n hookName: 'onFinish' | 'onAbort' | 'onError',\n invoke: () => void | Promise<void>,\n ): Promise<HookFailure | undefined> {\n try {\n await invoke()\n return undefined\n } catch (error) {\n this.logger.errors(`middleware ${hookName} hook failed`, {\n middleware: mw.name ?? 'unnamed',\n hook: hookName,\n error,\n })\n return { middleware: mw.name ?? 'unnamed', error }\n }\n }\n\n /**\n * Run onFinish on all middleware in order.\n *\n * ISOLATED **and** REPORTED. `onFinish` is the only terminal fan-out on the\n * SUCCESS path, and it is where `withPersistence.onFinish` writes the\n * assistant turn through the store. So the two properties are needed together\n * and neither may be traded for the other:\n *\n * - ISOLATION: every middleware's hook runs even if an earlier one threw, so a\n * transient store error cannot skip a later middleware's own bookkeeping.\n * Each failure is captured by {@link captureTerminalHook}, not propagated\n * mid-loop.\n * - REPORTING: after the loop, the failures are rethrown. `chat()`'s catch\n * treats what we throw as a genuine error (it is not a\n * `MiddlewareAbortError`, and `structuralInterruptFailure` does not match\n * it) and rethrows it out of the generator.\n *\n * What that rethrow can and cannot achieve depends on the transport, because\n * this fan-out is awaited AFTER the adapter's `RUN_FINISHED` has already been\n * yielded (`chat()` yields terminal chunks while streaming, then awaits this\n * hook on its way out). The success terminal is therefore already gone; the\n * rethrow can only append to what the consumer saw, never retract it:\n *\n * - NON-DURABLE transport: the throw escapes the generator mid-response, and\n * the SSE / HTTP-stream encoder turns it into a TRAILING `RUN_ERROR` on the\n * wire carrying the store's own message and `code`. `ai-client` surfaces\n * that as an error status, so the user is not told the turn was saved when\n * it was not.\n * - DURABLE transport: the throw reaches the durability sink instead. The\n * terminal was already persisted AND forwarded, so the sink deliberately\n * does NOT append a second, contradictory terminal, and `terminalForwarded`\n * (see `stream-to-response.ts`) suppresses the rethrow to the live consumer.\n * The `RUN_FINISHED` stands and the failure is RECORDED SERVER-SIDE on the\n * sink's `errors` channel. That is the intended outcome, not a gap: the save\n * failed, not the run — the consumer did receive the complete stream, so\n * telling it the run errored would be the lie. What the rethrow buys here is\n * that the sink sees the failure at all; while this loop swallowed, the only\n * trace anywhere was {@link captureTerminalHook}'s log line.\n *\n * Either way, swallowing is the one option ruled out: a failed\n * `messages.append` would otherwise leave a `completed` run record with the\n * assistant turn missing from storage and nothing beyond a middleware log\n * line, and the client would go on to send a history the server has no record\n * of.\n *\n * A single failure is rethrown AS-IS so the store's own error — its message,\n * `cause`, `code` and `instanceof` identity — is what reaches the caller and\n * the wire; wrapping the common case would bury it. Two or more become an\n * `AggregateError` (never a `MiddlewareAbortError`, so it cannot be mistaken\n * for an abort) rather than picking a winner and dropping the rest.\n */\n async runOnFinish(\n ctx: ChatMiddlewareContext<TContext>,\n info: FinishInfo,\n ): Promise<void> {\n const failures: Array<HookFailure> = []\n let firstFailure: HookFailure | undefined\n\n for (const mw of this.middlewares) {\n const hook = mw.onFinish\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onFinish', () =>\n hook.call(mw, ctx, info),\n )\n if (failure !== undefined) {\n firstFailure ??= failure\n failures.push(failure)\n continue\n }\n if (!skip) {\n this.logger.middleware(\n `hook=onFinish middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onFinish' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onFinish',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n\n if (firstFailure !== undefined) {\n throw failures.length === 1\n ? firstFailure.error\n : new AggregateError(\n failures.map((f) => f.error),\n `${failures.length} middleware onFinish hooks failed: ` +\n failures.map((f) => f.middleware).join(', '),\n )\n }\n }\n\n /**\n * Run onAbort on all middleware in order.\n *\n * ISOLATED and DELIBERATELY SWALLOWED. `onAbort` is a pure teardown fan-out\n * released from `chat()`'s `finally`, on a path where the outcome is already\n * decided: the run stopped, and the caller is being told why. A throw here has\n * nothing better to report than the abort reason it would DISPLACE — the\n * `finally` would surface a flaky store's error in place of \"client\n * disconnected\" — so failures are logged on the `errors` channel and go no\n * further. That is not a silent failure; it is refusing to let teardown\n * rewrite an outcome it did not produce.\n *\n * Isolation matters independently: these hooks release PER-MIDDLEWARE\n * resources (`withSandbox.onAbort` detaches or destroys the sandbox and stamps\n * `detachedSince`; `withPersistence.onAbort` records the run status through the\n * store), so an unguarded loop turns one transient store error into a\n * permanently leaked sandbox for every middleware ordered after it.\n */\n async runOnAbort(\n ctx: ChatMiddlewareContext<TContext>,\n info: AbortInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n const hook = mw.onAbort\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onAbort', () =>\n hook.call(mw, ctx, info),\n )\n if (failure === undefined && !skip) {\n this.logger.middleware(\n `hook=onAbort middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onAbort' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onAbort',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onError on all middleware in order.\n *\n * ISOLATED and DELIBERATELY SWALLOWED, for the same reason as\n * {@link runOnAbort} and NOT merely because it is teardown: the run has\n * already failed, `info.error` IS that failure, and `chat()` rethrows it to the\n * caller the moment this fan-out returns. A propagated hook throw could only\n * REPLACE the run's real error with a teardown artifact — strictly less\n * information for the caller, who is already learning the run failed. Reporting\n * would buy nothing and cost the diagnosis, so failures are logged on the\n * `errors` channel and stop there.\n *\n * Contrast {@link runOnFinish}, where nothing else is telling the caller\n * anything is wrong — which is why that one reports.\n */\n async runOnError(\n ctx: ChatMiddlewareContext<TContext>,\n info: ErrorInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n const hook = mw.onError\n if (hook) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const failure = await this.captureTerminalHook(mw, 'onError', () =>\n hook.call(mw, ctx, info),\n )\n if (failure === undefined && !skip) {\n this.logger.middleware(\n `hook=onError middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onError' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onError',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onIteration on all middleware in order.\n * Called at the start of each agent loop iteration.\n */\n async runOnIteration(\n ctx: ChatMiddlewareContext<TContext>,\n info: IterationInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onIteration) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onIteration(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onIteration middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onIteration' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onIteration',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n\n /**\n * Run onShouldContinue through middleware in order (AND semantics).\n * Any explicit `false` stops further iterations; `true` / void / undefined pass.\n * Called after `agentLoopStrategy` has already approved continuation.\n */\n async runOnShouldContinue(\n ctx: ChatMiddlewareContext<TContext>,\n state: AgentLoopState,\n ): Promise<boolean> {\n for (const mw of this.middlewares) {\n if (mw.onShouldContinue) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n const result = await mw.onShouldContinue(ctx, state)\n if (!skip) {\n this.logger.middleware(\n `hook=onShouldContinue middleware=${mw.name ?? 'unnamed'}`,\n {\n middleware: mw.name ?? 'unnamed',\n hook: 'onShouldContinue',\n result,\n },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onShouldContinue',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: result === false,\n })\n }\n if (result === false) {\n return false\n }\n }\n }\n return true\n }\n\n /**\n * Run onToolPhaseComplete on all middleware in order.\n * Called after all tool calls in an iteration have been processed.\n */\n async runOnToolPhaseComplete(\n ctx: ChatMiddlewareContext<TContext>,\n info: ToolPhaseCompleteInfo,\n ): Promise<void> {\n for (const mw of this.middlewares) {\n if (mw.onToolPhaseComplete) {\n const skip = shouldSkipInstrumentation(mw)\n const start = Date.now()\n await mw.onToolPhaseComplete(ctx, info)\n if (!skip) {\n this.logger.middleware(\n `hook=onToolPhaseComplete middleware=${mw.name ?? 'unnamed'}`,\n { middleware: mw.name ?? 'unnamed', hook: 'onToolPhaseComplete' },\n )\n aiEventClient.emit('middleware:hook:executed', {\n ...instrumentCtx(ctx),\n middlewareName: mw.name || 'unnamed',\n hookName: 'onToolPhaseComplete',\n iteration: ctx.iteration,\n duration: Date.now() - start,\n hasTransform: false,\n })\n }\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -13,5 +13,7 @@ export { validateCapabilities } from './validate.js';
13
13
  export type { AnyChatMiddleware } from './types.js';
14
14
  export { LocksCapability, getLocks, provideLocks, InMemoryLockStore, withLocks, defineLock, } from './locks.js';
15
15
  export type { LockStore } from './locks.js';
16
+ export { MetadataCapability, getMetadata, provideMetadata } from './metadata.js';
17
+ export type { MetadataStore } from './metadata.js';
16
18
  export { isRunStatus, isTerminalRunStatus, defineRunStore, InMemoryRunStore, } from './run-store.js';
17
19
  export type { RunStatus, TerminalRunStatus, RunRecord, RunError, RunStore, } from './run-store.js';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Namespaced key/value store for app and middleware metadata.
3
+ *
4
+ * `(namespace, key)` is the composite identity. Keep both values separate;
5
+ * joining them with a delimiter can create collisions.
6
+ */
7
+ export interface MetadataStore {
8
+ /** Return the value for `(namespace, key)`, or `null` when it is absent. */
9
+ get: (namespace: string, key: string) => Promise<unknown | null>;
10
+ /** Insert or replace the value for `(namespace, key)`. */
11
+ set: (namespace: string, key: string, value: unknown) => Promise<void>;
12
+ /** Delete `(namespace, key)`. Do nothing when it is absent. */
13
+ delete: (namespace: string, key: string) => Promise<void>;
14
+ }
15
+ export declare const MetadataCapability: import('./capabilities.js').Capability<MetadataStore, "metadata">;
16
+ export declare const getMetadata: import('./capabilities.js').CapabilityGetter<MetadataStore>, provideMetadata: import('./capabilities.js').CapabilityProvider<MetadataStore>;
@@ -0,0 +1,8 @@
1
+ import { createCapability } from "./capabilities.js";
2
+ //#region src/activities/chat/middleware/metadata.ts
3
+ var MetadataCapability = createCapability()("metadata");
4
+ var [getMetadata, provideMetadata] = MetadataCapability;
5
+ //#endregion
6
+ export { MetadataCapability, getMetadata, provideMetadata };
7
+
8
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","names":[],"sources":["../../../../../src/activities/chat/middleware/metadata.ts"],"sourcesContent":["import { createCapability } from './capabilities'\n\n/**\n * Namespaced key/value store for app and middleware metadata.\n *\n * `(namespace, key)` is the composite identity. Keep both values separate;\n * joining them with a delimiter can create collisions.\n */\nexport interface MetadataStore {\n /** Return the value for `(namespace, key)`, or `null` when it is absent. */\n get: (namespace: string, key: string) => Promise<unknown | null>\n /** Insert or replace the value for `(namespace, key)`. */\n set: (namespace: string, key: string, value: unknown) => Promise<void>\n /** Delete `(namespace, key)`. Do nothing when it is absent. */\n delete: (namespace: string, key: string) => Promise<void>\n}\n\nexport const MetadataCapability = createCapability<MetadataStore>()('metadata')\n\nexport const [getMetadata, provideMetadata] = MetadataCapability\n"],"mappings":";;AAiBA,IAAa,qBAAqB,iBAAgC,CAAC,CAAC,UAAU;AAE9E,IAAa,CAAC,aAAa,mBAAmB"}
@@ -113,6 +113,12 @@ export interface ChatMiddlewareContext<TContext = unknown> {
113
113
  signal?: AbortSignal;
114
114
  /** Abort the chat run with a reason */
115
115
  abort: (reason?: string) => void;
116
+ /**
117
+ * Push a `CUSTOM` chunk onto the chat stream immediately.
118
+ * The engine yields it as soon as it can (including while `onConfig`
119
+ * is still awaiting work such as a summarize call).
120
+ */
121
+ emitCustomEvent: (name: string, value: Record<string, any>) => void;
116
122
  /** Runtime context provided by chat() options */
117
123
  context: TContext;
118
124
  /**
@@ -185,7 +191,10 @@ export interface ChatMiddlewareContext<TContext = unknown> {
185
191
  * that middleware is allowed to modify.
186
192
  */
187
193
  export interface ChatMiddlewareConfig {
194
+ /** Canonical conversation history. Middleware and persistence read this. */
188
195
  messages: Array<ModelMessage>;
196
+ /** Provider-only context. Defaults to `messages` when it is not set. */
197
+ providerMessages?: Array<ModelMessage> | undefined;
189
198
  systemPrompts: Array<SystemPrompt>;
190
199
  tools: Array<Tool>;
191
200
  resume?: Array<RunAgentResumeItem> | undefined;
@@ -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 /** 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 messages: Array<ModelMessage>\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 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"}
@@ -730,6 +730,7 @@ var StreamProcessor = class StreamProcessor {
730
730
  state.lastEmittedText = "";
731
731
  state.hasToolCallsSinceTextStart = false;
732
732
  }
733
+ state.hasToolCallsSinceTextStart = false;
733
734
  const currentText = state.currentSegmentText;
734
735
  const delta = chunk.delta || "";
735
736
  const nextText = delta !== "" ? currentText + delta : currentText;