@tanstack/ai 0.44.1 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/esm/activities/chat/adapter.d.ts +13 -1
  2. package/dist/esm/activities/chat/adapter.js.map +1 -1
  3. package/dist/esm/activities/chat/index.js +108 -68
  4. package/dist/esm/activities/chat/index.js.map +1 -1
  5. package/dist/esm/activities/chat/messages.js +24 -27
  6. package/dist/esm/activities/chat/messages.js.map +1 -1
  7. package/dist/esm/activities/chat/stream/processor.js +14 -13
  8. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  9. package/dist/esm/activities/chat/tools/approval-schema.js +11 -8
  10. package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -1
  11. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
  12. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  13. package/dist/esm/activities/chat/tools/tool-calls.js +40 -31
  14. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  15. package/dist/esm/activities/generateVideo/index.js +3 -2
  16. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  17. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  18. package/dist/esm/adapter-internals.d.ts +2 -0
  19. package/dist/esm/adapter-internals.js +3 -1
  20. package/dist/esm/interrupt-resume.js +24 -20
  21. package/dist/esm/interrupt-resume.js.map +1 -1
  22. package/dist/esm/interrupts.js +2 -1
  23. package/dist/esm/interrupts.js.map +1 -1
  24. package/dist/esm/logger/console-logger.js +1 -3
  25. package/dist/esm/logger/console-logger.js.map +1 -1
  26. package/dist/esm/logger/resolve.js +2 -1
  27. package/dist/esm/logger/resolve.js.map +1 -1
  28. package/dist/esm/stream-durability.js +1 -1
  29. package/dist/esm/stream-durability.js.map +1 -1
  30. package/dist/esm/types.d.ts +19 -16
  31. package/dist/esm/utilities/chat-params.js +1 -3
  32. package/dist/esm/utilities/chat-params.js.map +1 -1
  33. package/dist/esm/utilities/media-prompt.js +1 -3
  34. package/dist/esm/utilities/media-prompt.js.map +1 -1
  35. package/dist/esm/utilities/structured-output-events.d.ts +17 -0
  36. package/dist/esm/utilities/structured-output-events.js +32 -0
  37. package/dist/esm/utilities/structured-output-events.js.map +1 -0
  38. package/dist/esm/utilities/structured-output-text.d.ts +7 -0
  39. package/dist/esm/utilities/structured-output-text.js +50 -0
  40. package/dist/esm/utilities/structured-output-text.js.map +1 -0
  41. package/package.json +2 -2
  42. package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +6 -2
  43. package/skills/ai-core/media-generation/SKILL.md +33 -19
  44. package/skills/ai-core/structured-outputs/SKILL.md +73 -1
  45. package/skills/ai-core/tool-calling/SKILL.md +1 -1
  46. package/src/activities/chat/adapter.ts +16 -1
  47. package/src/activities/chat/index.ts +128 -47
  48. package/src/activities/chat/stream/processor.ts +6 -3
  49. package/src/activities/chat/tools/tool-calls.ts +12 -4
  50. package/src/adapter-internals.ts +8 -0
  51. package/src/types.ts +27 -19
  52. package/src/utilities/structured-output-events.ts +44 -0
  53. package/src/utilities/structured-output-text.ts +63 -0
@@ -103,7 +103,8 @@ export interface TextAdapter<TModel extends string, TProviderOptions extends Rec
103
103
  * Implementations must emit standard AG-UI lifecycle events (RUN_STARTED,
104
104
  * TEXT_MESSAGE_*, RUN_FINISHED) carrying raw JSON text deltas, plus a final
105
105
  * `CUSTOM` event named `structured-output.complete` whose `value` is
106
- * `{ object, raw, reasoning? }`.
106
+ * `{ object, raw, reasoning? }`. Events must be timestamped when emitted so
107
+ * their timestamps follow stream order.
107
108
  */
108
109
  structuredOutputStream?: (options: StructuredOutputOptions<TProviderOptions>) => AsyncIterable<StreamChunk>;
109
110
  /**
@@ -126,6 +127,17 @@ export interface TextAdapter<TModel extends string, TProviderOptions extends Rec
126
127
  * answer per-request. Most adapters can return a constant.
127
128
  */
128
129
  supportsCombinedToolsAndSchema?: (modelOptions?: TProviderOptions | undefined) => boolean;
130
+ /**
131
+ * Where native-combined structured output is taken from.
132
+ *
133
+ * - `'text'` (default when omitted): the agent loop's accumulated
134
+ * assistant text is schema JSON. The engine parses it after the loop.
135
+ * HTTP adapters use this.
136
+ * - `'event'`: the adapter emits `structured-output.complete` during
137
+ * `chatStream`. The engine must not parse accumulated prose. Harness
138
+ * adapters use this.
139
+ */
140
+ combinedStructuredOutputSource?: (modelOptions?: TProviderOptions | undefined) => 'text' | 'event';
129
141
  }
130
142
  /**
131
143
  * A TextAdapter with any/unknown type parameters.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","names":[],"sources":["../../../../src/activities/chat/adapter.ts"],"sourcesContent":["import type {\n DefaultMessageMetadataByModality,\n JSONSchema,\n Modality,\n StreamChunk,\n TextOptions,\n TokenUsage,\n} from '../../types'\nimport type { CapabilityHandle } from './middleware/capabilities'\n\n/**\n * Configuration for adapter instances\n */\nexport interface TextAdapterConfig {\n apiKey?: string\n baseUrl?: string\n timeout?: number\n maxRetries?: number\n headers?: Record<string, string>\n}\n\n/**\n * Options for structured output generation.\n *\n * The internal logger is threaded through `chatOptions.logger` (inherited from\n * `TextOptions`). Adapter implementations must call `logger.request()` before\n * SDK calls, `logger.provider()` for each chunk received, and `logger.errors()`\n * in catch blocks.\n */\nexport interface StructuredOutputOptions<TProviderOptions extends object> {\n /** Text options for the request */\n chatOptions: TextOptions<TProviderOptions>\n /** JSON Schema for structured output - already converted from Zod in the ai layer */\n outputSchema: JSONSchema\n}\n\n/**\n * Result from structured output generation\n */\nexport interface StructuredOutputResult<T = unknown> {\n /** The parsed data conforming to the schema */\n data: T\n /** The raw text response from the model before parsing */\n rawText: string\n /** Token usage information (if provided by the adapter) */\n usage?: TokenUsage\n}\n\n/**\n * Text adapter interface with pre-resolved generics.\n *\n * An adapter is created by a provider function: `provider('model')` → `adapter`\n * All type resolution happens at the provider call site, not in this interface.\n *\n * Generic parameters:\n * - TModel: The specific model name (e.g., 'gpt-4o')\n * - TProviderOptions: Provider-specific options for this model (already resolved)\n * - TInputModalities: Supported input modalities for this model (already resolved)\n * - TMessageMetadata: Metadata types for content parts (already resolved)\n * - TToolCapabilities: Tuple of tool-kind strings supported by this model, resolved from `supports.tools`\n * - TToolCallMetadata: Metadata type that round-trips with tool calls (e.g. Gemini's `thoughtSignature`)\n * - TSystemPromptMetadata: Provider-typed metadata accepted on each\n * `systemPrompts[i]` entry (e.g. Anthropic `cache_control`). Defaults to\n * `never` — adapters without per-prompt metadata reject the `metadata`\n * field at the call site.\n */\nexport interface TextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n TToolCallMetadata = unknown,\n TSystemPromptMetadata = never,\n> {\n /** Discriminator for adapter kind */\n readonly kind: 'text'\n /** Provider name identifier (e.g., 'openai', 'anthropic') */\n readonly name: string\n /** The model this adapter is configured for */\n readonly model: TModel\n\n /**\n * Capabilities this adapter requires at runtime. `chat()` validates that the\n * configured middleware provides each one. Model adapters omit this; harness\n * adapters (e.g. a future `claudeCode()`) declare e.g. `[sandboxCapability]`.\n * Runtime access to capabilities from inside the adapter is not yet wired —\n * this is the declaration/validation surface only.\n */\n readonly requires?: ReadonlyArray<CapabilityHandle>\n\n /**\n * @internal Type-only properties for inference. Not assigned at runtime.\n */\n '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n toolCapabilities: TToolCapabilities\n toolCallMetadata: TToolCallMetadata\n systemPromptMetadata: TSystemPromptMetadata\n }\n\n /**\n * Stream text completions from the model\n */\n chatStream: (\n options: TextOptions<TProviderOptions>,\n ) => AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * This method uses stream: false and sends the JSON schema to the provider\n * to ensure the response conforms to the expected structure.\n *\n * @param options - Structured output options containing chat options and JSON schema\n * @returns Promise with the raw data (validation is done in the chat function)\n */\n structuredOutput: (\n options: StructuredOutputOptions<TProviderOptions>,\n ) => Promise<StructuredOutputResult<unknown>>\n\n /**\n * Stream structured output using the provider's native streaming structured\n * output API (stream + response_format json_schema in a single request).\n *\n * Optional — adapters without native streaming JSON omit this method and the\n * activity layer synthesizes a stream around the non-streaming\n * `structuredOutput` call.\n *\n * Implementations must emit standard AG-UI lifecycle events (RUN_STARTED,\n * TEXT_MESSAGE_*, RUN_FINISHED) carrying raw JSON text deltas, plus a final\n * `CUSTOM` event named `structured-output.complete` whose `value` is\n * `{ object, raw, reasoning? }`.\n */\n structuredOutputStream?: (\n options: StructuredOutputOptions<TProviderOptions>,\n ) => AsyncIterable<StreamChunk>\n\n /**\n * Declares whether the adapter supports combining `tools` and a\n * schema-constrained final answer in a single streaming request.\n *\n * When `true`, the engine wires `outputSchema` into the regular\n * `chatStream()` call and skips the separate `runStructuredFinalization`\n * round-trip. The model's natural final turn carries the\n * schema-constrained JSON text and the engine harvests it from the agent\n * loop's accumulated content.\n *\n * When `false`, `undefined`, or the method is omitted, the engine runs\n * the agent loop without `outputSchema` and then issues a separate\n * `structuredOutput` / `structuredOutputStream` call against the JSON\n * schema for finalization (the legacy path).\n *\n * The method receives the per-call `modelOptions` so providers whose\n * support depends on the resolved upstream model (e.g. OpenRouter) can\n * answer per-request. Most adapters can return a constant.\n */\n supportsCombinedToolsAndSchema?: (\n modelOptions?: TProviderOptions | undefined,\n ) => boolean\n}\n\n/**\n * A TextAdapter with any/unknown type parameters.\n * Useful as a constraint in generic functions and interfaces.\n */\nexport type AnyTextAdapter = TextAdapter<any, any, any, any, any, any, any>\n\n/**\n * Abstract base class for text adapters.\n * Extend this class to implement a text adapter for a specific provider.\n *\n * Generic parameters match TextAdapter - all pre-resolved by the provider function.\n */\nexport abstract class BaseTextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n TToolCallMetadata = unknown,\n TSystemPromptMetadata = never,\n> implements TextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadataByModality,\n TToolCapabilities,\n TToolCallMetadata,\n TSystemPromptMetadata\n> {\n readonly kind = 'text' as const\n abstract readonly name: string\n readonly model: TModel\n readonly requires?: ReadonlyArray<CapabilityHandle> = undefined\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n toolCapabilities: TToolCapabilities\n toolCallMetadata: TToolCallMetadata\n systemPromptMetadata: TSystemPromptMetadata\n }\n\n protected config: TextAdapterConfig\n\n constructor(config: TextAdapterConfig = {}, model: TModel) {\n this.config = config\n this.model = model\n }\n\n abstract chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * Concrete implementations should override this to use provider-specific structured output.\n */\n abstract structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>>\n\n protected generateId(): string {\n return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n}\n"],"mappings":";;;;;;;AA+KA,IAAsB,kBAAtB,MAgBE;CACA,OAAgB;CAEhB;CACA,WAAsD,KAAA;CAYtD;CAEA,YAAY,SAA4B,CAAC,GAAG,OAAe;EACzD,KAAK,SAAS;EACd,KAAK,QAAQ;CACf;CAcA,aAA+B;EAC7B,OAAO,GAAG,KAAK,KAAK,GAAG,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;CAC7E;AACF"}
1
+ {"version":3,"file":"adapter.js","names":[],"sources":["../../../../src/activities/chat/adapter.ts"],"sourcesContent":["import type {\n DefaultMessageMetadataByModality,\n JSONSchema,\n Modality,\n StreamChunk,\n TextOptions,\n TokenUsage,\n} from '../../types'\nimport type { CapabilityHandle } from './middleware/capabilities'\n\n/**\n * Configuration for adapter instances\n */\nexport interface TextAdapterConfig {\n apiKey?: string\n baseUrl?: string\n timeout?: number\n maxRetries?: number\n headers?: Record<string, string>\n}\n\n/**\n * Options for structured output generation.\n *\n * The internal logger is threaded through `chatOptions.logger` (inherited from\n * `TextOptions`). Adapter implementations must call `logger.request()` before\n * SDK calls, `logger.provider()` for each chunk received, and `logger.errors()`\n * in catch blocks.\n */\nexport interface StructuredOutputOptions<TProviderOptions extends object> {\n /** Text options for the request */\n chatOptions: TextOptions<TProviderOptions>\n /** JSON Schema for structured output - already converted from Zod in the ai layer */\n outputSchema: JSONSchema\n}\n\n/**\n * Result from structured output generation\n */\nexport interface StructuredOutputResult<T = unknown> {\n /** The parsed data conforming to the schema */\n data: T\n /** The raw text response from the model before parsing */\n rawText: string\n /** Token usage information (if provided by the adapter) */\n usage?: TokenUsage\n}\n\n/**\n * Text adapter interface with pre-resolved generics.\n *\n * An adapter is created by a provider function: `provider('model')` → `adapter`\n * All type resolution happens at the provider call site, not in this interface.\n *\n * Generic parameters:\n * - TModel: The specific model name (e.g., 'gpt-4o')\n * - TProviderOptions: Provider-specific options for this model (already resolved)\n * - TInputModalities: Supported input modalities for this model (already resolved)\n * - TMessageMetadata: Metadata types for content parts (already resolved)\n * - TToolCapabilities: Tuple of tool-kind strings supported by this model, resolved from `supports.tools`\n * - TToolCallMetadata: Metadata type that round-trips with tool calls (e.g. Gemini's `thoughtSignature`)\n * - TSystemPromptMetadata: Provider-typed metadata accepted on each\n * `systemPrompts[i]` entry (e.g. Anthropic `cache_control`). Defaults to\n * `never` — adapters without per-prompt metadata reject the `metadata`\n * field at the call site.\n */\nexport interface TextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n TToolCallMetadata = unknown,\n TSystemPromptMetadata = never,\n> {\n /** Discriminator for adapter kind */\n readonly kind: 'text'\n /** Provider name identifier (e.g., 'openai', 'anthropic') */\n readonly name: string\n /** The model this adapter is configured for */\n readonly model: TModel\n\n /**\n * Capabilities this adapter requires at runtime. `chat()` validates that the\n * configured middleware provides each one. Model adapters omit this; harness\n * adapters (e.g. a future `claudeCode()`) declare e.g. `[sandboxCapability]`.\n * Runtime access to capabilities from inside the adapter is not yet wired —\n * this is the declaration/validation surface only.\n */\n readonly requires?: ReadonlyArray<CapabilityHandle>\n\n /**\n * @internal Type-only properties for inference. Not assigned at runtime.\n */\n '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n toolCapabilities: TToolCapabilities\n toolCallMetadata: TToolCallMetadata\n systemPromptMetadata: TSystemPromptMetadata\n }\n\n /**\n * Stream text completions from the model\n */\n chatStream: (\n options: TextOptions<TProviderOptions>,\n ) => AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * This method uses stream: false and sends the JSON schema to the provider\n * to ensure the response conforms to the expected structure.\n *\n * @param options - Structured output options containing chat options and JSON schema\n * @returns Promise with the raw data (validation is done in the chat function)\n */\n structuredOutput: (\n options: StructuredOutputOptions<TProviderOptions>,\n ) => Promise<StructuredOutputResult<unknown>>\n\n /**\n * Stream structured output using the provider's native streaming structured\n * output API (stream + response_format json_schema in a single request).\n *\n * Optional — adapters without native streaming JSON omit this method and the\n * activity layer synthesizes a stream around the non-streaming\n * `structuredOutput` call.\n *\n * Implementations must emit standard AG-UI lifecycle events (RUN_STARTED,\n * TEXT_MESSAGE_*, RUN_FINISHED) carrying raw JSON text deltas, plus a final\n * `CUSTOM` event named `structured-output.complete` whose `value` is\n * `{ object, raw, reasoning? }`. Events must be timestamped when emitted so\n * their timestamps follow stream order.\n */\n structuredOutputStream?: (\n options: StructuredOutputOptions<TProviderOptions>,\n ) => AsyncIterable<StreamChunk>\n\n /**\n * Declares whether the adapter supports combining `tools` and a\n * schema-constrained final answer in a single streaming request.\n *\n * When `true`, the engine wires `outputSchema` into the regular\n * `chatStream()` call and skips the separate `runStructuredFinalization`\n * round-trip. The model's natural final turn carries the\n * schema-constrained JSON text and the engine harvests it from the agent\n * loop's accumulated content.\n *\n * When `false`, `undefined`, or the method is omitted, the engine runs\n * the agent loop without `outputSchema` and then issues a separate\n * `structuredOutput` / `structuredOutputStream` call against the JSON\n * schema for finalization (the legacy path).\n *\n * The method receives the per-call `modelOptions` so providers whose\n * support depends on the resolved upstream model (e.g. OpenRouter) can\n * answer per-request. Most adapters can return a constant.\n */\n supportsCombinedToolsAndSchema?: (\n modelOptions?: TProviderOptions | undefined,\n ) => boolean\n\n /**\n * Where native-combined structured output is taken from.\n *\n * - `'text'` (default when omitted): the agent loop's accumulated\n * assistant text is schema JSON. The engine parses it after the loop.\n * HTTP adapters use this.\n * - `'event'`: the adapter emits `structured-output.complete` during\n * `chatStream`. The engine must not parse accumulated prose. Harness\n * adapters use this.\n */\n combinedStructuredOutputSource?: (\n modelOptions?: TProviderOptions | undefined,\n ) => 'text' | 'event'\n}\n\n/**\n * A TextAdapter with any/unknown type parameters.\n * Useful as a constraint in generic functions and interfaces.\n */\nexport type AnyTextAdapter = TextAdapter<any, any, any, any, any, any, any>\n\n/**\n * Abstract base class for text adapters.\n * Extend this class to implement a text adapter for a specific provider.\n *\n * Generic parameters match TextAdapter - all pre-resolved by the provider function.\n */\nexport abstract class BaseTextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n TToolCallMetadata = unknown,\n TSystemPromptMetadata = never,\n> implements TextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadataByModality,\n TToolCapabilities,\n TToolCallMetadata,\n TSystemPromptMetadata\n> {\n readonly kind = 'text' as const\n abstract readonly name: string\n readonly model: TModel\n readonly requires?: ReadonlyArray<CapabilityHandle> = undefined\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n toolCapabilities: TToolCapabilities\n toolCallMetadata: TToolCallMetadata\n systemPromptMetadata: TSystemPromptMetadata\n }\n\n protected config: TextAdapterConfig\n\n constructor(config: TextAdapterConfig = {}, model: TModel) {\n this.config = config\n this.model = model\n }\n\n abstract chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * Concrete implementations should override this to use provider-specific structured output.\n */\n abstract structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>>\n\n protected generateId(): string {\n return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n}\n"],"mappings":";;;;;;;AA8LA,IAAsB,kBAAtB,MAgBE;CACA,OAAgB;CAEhB;CACA,WAAsD,KAAA;CAYtD;CAEA,YAAY,SAA4B,CAAC,GAAG,OAAe;EACzD,KAAK,SAAS;EACd,KAAK,QAAQ;CACf;CAcA,aAA+B;EAC7B,OAAO,GAAG,KAAK,KAAK,GAAG,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;CAC7E;AACF"}
@@ -187,6 +187,7 @@ var TextEngine = class {
187
187
  validatedStructuredOutput = void 0;
188
188
  hasValidatedStructuredOutput = false;
189
189
  finalizationError = null;
190
+ combinedCompleteEmitted = false;
190
191
  finalStructuredOutput;
191
192
  constructor(config, logger) {
192
193
  this.logger = logger;
@@ -336,27 +337,31 @@ var TextEngine = class {
336
337
  this.endCycle();
337
338
  } while (await this.shouldContinue());
338
339
  this.logger.agentLoop("run finished", { finishReason: this.lastFinishReason });
339
- if (this.finalStructuredOutput && this.toolPhase !== "wait" && !this.isCancelled() && !this.finalizationError) if (this.finalStructuredOutput.nativeCombined === true) yield* this.harvestCombinedStructuredOutput();
340
- else yield* this.runStructuredFinalization();
341
- if (!this.terminalHookCalled && this.toolPhase !== "wait" && !this.isCancelled()) if (this.finalizationError) {
342
- this.terminalHookCalled = true;
343
- const errForHook = new Error(this.finalizationError.message, this.finalizationError.cause !== void 0 ? { cause: this.finalizationError.cause } : void 0);
344
- if (this.finalizationError.code !== void 0) Object.defineProperty(errForHook, "code", {
345
- value: this.finalizationError.code,
346
- enumerable: true
347
- });
348
- await this.middlewareRunner.runOnError(this.middlewareCtx, {
349
- error: errForHook,
350
- duration: Date.now() - this.streamStartTime
351
- });
352
- } else {
353
- this.terminalHookCalled = true;
354
- await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
355
- finishReason: this.lastFinishReason,
356
- duration: Date.now() - this.streamStartTime,
357
- content: this.accumulatedContent,
358
- usage: this.finishedEvent?.usage
359
- });
340
+ if (this.finalStructuredOutput && this.toolPhase !== "wait" && !this.isCancelled() && !this.finalizationError && !this.earlyTermination) {
341
+ if (this.finalStructuredOutput.nativeCombined === true) yield* this.harvestCombinedStructuredOutput();
342
+ else yield* this.runStructuredFinalization();
343
+ }
344
+ if (!this.terminalHookCalled && this.toolPhase !== "wait" && !this.isCancelled()) {
345
+ if (this.finalizationError) {
346
+ this.terminalHookCalled = true;
347
+ const errForHook = new Error(this.finalizationError.message, this.finalizationError.cause !== void 0 ? { cause: this.finalizationError.cause } : void 0);
348
+ if (this.finalizationError.code !== void 0) Object.defineProperty(errForHook, "code", {
349
+ value: this.finalizationError.code,
350
+ enumerable: true
351
+ });
352
+ await this.middlewareRunner.runOnError(this.middlewareCtx, {
353
+ error: errForHook,
354
+ duration: Date.now() - this.streamStartTime
355
+ });
356
+ } else {
357
+ this.terminalHookCalled = true;
358
+ await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
359
+ finishReason: this.lastFinishReason,
360
+ duration: Date.now() - this.streamStartTime,
361
+ content: this.accumulatedContent,
362
+ usage: this.finishedEvent?.usage
363
+ });
364
+ }
360
365
  }
361
366
  } catch (error) {
362
367
  if (error instanceof Error && error.name === "InterruptReplaySignal" && "continuationRunId" in error && typeof error.continuationRunId === "string") {
@@ -498,7 +503,32 @@ var TextEngine = class {
498
503
  if (this.isCancelled()) break;
499
504
  this.totalChunkCount++;
500
505
  this.handleStreamChunk(chunk);
501
- if (this.finalStructuredOutput?.nativeCombined === true && this.finalStructuredOutput.yieldChunks && !this.combinedStartEmitted && chunk.type === EventType.TEXT_MESSAGE_START) {
506
+ if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") {
507
+ this.combinedStartEmitted = true;
508
+ const startValue = chunk.value;
509
+ if (startValue && typeof startValue === "object" && "messageId" in startValue && typeof startValue.messageId === "string") this.combinedStructuredMessageId = startValue.messageId;
510
+ }
511
+ let outboundChunk = chunk;
512
+ if (this.finalStructuredOutput?.source === "event" && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
513
+ const parsed = readStructuredOutputCompleteValue(chunk.value);
514
+ if (parsed) {
515
+ const object = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed.object) : parsed.object;
516
+ this.structuredOutputResult = {
517
+ data: object,
518
+ rawText: parsed.raw
519
+ };
520
+ this.combinedCompleteEmitted = true;
521
+ const value = chunk.value;
522
+ if (object !== parsed.object && value && typeof value === "object") outboundChunk = {
523
+ ...chunk,
524
+ value: {
525
+ ...value,
526
+ object
527
+ }
528
+ };
529
+ }
530
+ }
531
+ if (this.finalStructuredOutput?.nativeCombined === true && this.finalStructuredOutput.yieldChunks && this.finalStructuredOutput.source !== "event" && !this.combinedStartEmitted && chunk.type === EventType.TEXT_MESSAGE_START) {
502
532
  this.combinedStartEmitted = true;
503
533
  const messageId = typeof chunk.messageId === "string" && chunk.messageId !== "" ? chunk.messageId : generateMessageId();
504
534
  this.combinedStructuredMessageId = messageId;
@@ -517,7 +547,7 @@ var TextEngine = class {
517
547
  this.middlewareCtx.chunkIndex++;
518
548
  }
519
549
  }
520
- const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, chunk);
550
+ const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, outboundChunk);
521
551
  const suppressAgentLifecycle = !!this.finalStructuredOutput && this.finalStructuredOutput.yieldChunks && this.finalStructuredOutput.nativeCombined !== true;
522
552
  for (const outputChunk of outputChunks) {
523
553
  if (suppressAgentLifecycle && (outputChunk.type === EventType.RUN_STARTED || outputChunk.type === EventType.RUN_FINISHED)) continue;
@@ -561,16 +591,7 @@ var TextEngine = class {
561
591
  case "STEP_STARTED":
562
592
  this.handleStepStartedEvent();
563
593
  break;
564
- case "STEP_FINISHED":
565
- this.handleStepFinishedEvent(chunk);
566
- break;
567
- case "TOOL_CALL_RESULT": break;
568
- case "REASONING_START":
569
- case "REASONING_MESSAGE_START":
570
- case "REASONING_MESSAGE_CONTENT":
571
- case "REASONING_MESSAGE_END":
572
- case "REASONING_END": break;
573
- default: break;
594
+ case "STEP_FINISHED": this.handleStepFinishedEvent(chunk);
574
595
  }
575
596
  }
576
597
  handleTextMessageContentEvent(chunk) {
@@ -600,8 +621,15 @@ var TextEngine = class {
600
621
  this.finishedEvent = chunk;
601
622
  this.lastFinishReason = chunk.finishReason ?? null;
602
623
  }
603
- handleRunErrorEvent(_chunk) {
624
+ handleRunErrorEvent(chunk) {
604
625
  this.earlyTermination = true;
626
+ if (this.finalStructuredOutput && this.finalizationError === null) {
627
+ const message = chunk.message || chunk.error?.message || "Run failed before structured output completed";
628
+ this.finalizationError = {
629
+ message,
630
+ ...chunk.code !== void 0 ? { code: chunk.code } : chunk.error?.code !== void 0 ? { code: chunk.error.code } : {}
631
+ };
632
+ }
605
633
  }
606
634
  finalizeCurrentThinkingStep() {
607
635
  if (this.currentThinkingContent) {
@@ -940,7 +968,7 @@ var TextEngine = class {
940
968
  const messages = this.messages.map((message, index) => {
941
969
  const content = typeof message.content === "string" ? message.content : message.content === null ? void 0 : JSON.stringify(message.content);
942
970
  return {
943
- id: `snapshot_${this.runIdOverride ?? this.requestId}_${index}`,
971
+ id: message.id || `snapshot_${this.runIdOverride ?? this.requestId}_${index}`,
944
972
  role: message.role,
945
973
  ...content !== void 0 ? { content } : {},
946
974
  ..."toolCalls" in message && message.toolCalls ? { toolCalls: message.toolCalls } : {},
@@ -1372,7 +1400,7 @@ var TextEngine = class {
1372
1400
  if (c.type === EventType.TEXT_MESSAGE_START || c.type === EventType.TEXT_MESSAGE_CONTENT || c.type === EventType.TEXT_MESSAGE_END) return typeof c.messageId === "string" && c.messageId !== "" ? c.messageId : null;
1373
1401
  return null;
1374
1402
  };
1375
- const buildSynthesizedStart = () => {
1403
+ const buildSynthesizedStart = (timestamp = Date.now()) => {
1376
1404
  const idForStart = structuredMessageId ?? generateMessageId();
1377
1405
  structuredMessageId = idForStart;
1378
1406
  return {
@@ -1380,7 +1408,7 @@ var TextEngine = class {
1380
1408
  name: "structured-output.start",
1381
1409
  value: { messageId: idForStart },
1382
1410
  model: this.params.model,
1383
- timestamp: Date.now(),
1411
+ timestamp,
1384
1412
  threadId: this.threadId,
1385
1413
  ...this.runIdOverride ? { runId: this.runIdOverride } : {}
1386
1414
  };
@@ -1397,7 +1425,7 @@ var TextEngine = class {
1397
1425
  if (this.finalStructuredOutput.yieldChunks) {
1398
1426
  if (!startEmitted && (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT || chunk.type === EventType.TEXT_MESSAGE_END)) {
1399
1427
  startEmitted = true;
1400
- const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart());
1428
+ const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart(chunk.timestamp));
1401
1429
  for (const outputChunk of synthOutputs) {
1402
1430
  yield outputChunk;
1403
1431
  this.middlewareCtx.chunkIndex++;
@@ -1405,7 +1433,7 @@ var TextEngine = class {
1405
1433
  }
1406
1434
  if (!startEmitted && chunk.type === EventType.RUN_ERROR) {
1407
1435
  startEmitted = true;
1408
- const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart());
1436
+ const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart(chunk.timestamp));
1409
1437
  for (const outputChunk of synthOutputs) {
1410
1438
  yield outputChunk;
1411
1439
  this.middlewareCtx.chunkIndex++;
@@ -1511,25 +1539,32 @@ var TextEngine = class {
1511
1539
  async *harvestCombinedStructuredOutput() {
1512
1540
  if (!this.finalStructuredOutput) throw new Error("harvestCombinedStructuredOutput called without finalStructuredOutput config");
1513
1541
  const yieldChunks = this.finalStructuredOutput.yieldChunks;
1514
- const rawText = this.accumulatedContent;
1515
- if (rawText.length === 0) this.finalizationError = {
1516
- message: "missing structured result",
1517
- code: "structured-output-missing-result"
1518
- };
1519
- else try {
1520
- const parsed = JSON.parse(rawText);
1521
- const data = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed) : parsed;
1522
- this.structuredOutputResult = {
1523
- data,
1524
- rawText
1542
+ if ((this.finalStructuredOutput.source ?? "text") === "event") {
1543
+ if (!this.structuredOutputResult) this.finalizationError = {
1544
+ message: "missing structured result",
1545
+ code: "structured-output-missing-result"
1525
1546
  };
1526
- } catch (err) {
1527
- const detail = rawText.slice(0, 200) + (rawText.length > 200 ? "..." : "");
1528
- this.finalizationError = {
1529
- message: `Failed to parse structured output as JSON. Content: ${detail}`,
1530
- code: "structured-output-parse-failed",
1531
- cause: err
1547
+ } else {
1548
+ const rawText = this.accumulatedContent;
1549
+ if (rawText.length === 0) this.finalizationError = {
1550
+ message: "missing structured result",
1551
+ code: "structured-output-missing-result"
1532
1552
  };
1553
+ else try {
1554
+ const parsed = JSON.parse(rawText);
1555
+ const data = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed) : parsed;
1556
+ this.structuredOutputResult = {
1557
+ data,
1558
+ rawText
1559
+ };
1560
+ } catch (err) {
1561
+ const detail = rawText.slice(0, 200) + (rawText.length > 200 ? "..." : "");
1562
+ this.finalizationError = {
1563
+ message: `Failed to parse structured output as JSON. Content: ${detail}`,
1564
+ code: "structured-output-parse-failed",
1565
+ cause: err
1566
+ };
1567
+ }
1533
1568
  }
1534
1569
  if (this.structuredOutputResult && !this.finalizationError && this.finalStructuredOutput.validate) try {
1535
1570
  const validated = this.finalStructuredOutput.validate(this.structuredOutputResult.data);
@@ -1563,7 +1598,7 @@ var TextEngine = class {
1563
1598
  this.middlewareCtx.chunkIndex++;
1564
1599
  }
1565
1600
  }
1566
- if (this.structuredOutputResult && !this.finalizationError) {
1601
+ if (this.structuredOutputResult && !this.finalizationError && !this.combinedCompleteEmitted) {
1567
1602
  const completeChunk = {
1568
1603
  type: EventType.CUSTOM,
1569
1604
  name: "structured-output.complete",
@@ -1911,7 +1946,8 @@ async function* streamTextChunks(options, engineRef) {
1911
1946
  * Runs the full agentic loop (if tools are provided) but returns collected text.
1912
1947
  */
1913
1948
  function runNonStreamingText(options) {
1914
- return streamToText(runStreamingText(options));
1949
+ const stream = runStreamingText(options);
1950
+ return streamToText(stream);
1915
1951
  }
1916
1952
  /**
1917
1953
  * Run agentic structured output:
@@ -1929,6 +1965,7 @@ async function runAgenticStructuredOutput(options) {
1929
1965
  const normalize = (data) => undoNullWidening(data, nullWideningMap);
1930
1966
  const validate = isStandardSchema(outputSchema) ? (data) => parseWithStandardSchema(outputSchema, data) : void 0;
1931
1967
  const nativeCombined = adapter.supportsCombinedToolsAndSchema?.(options.modelOptions) === true;
1968
+ const source = adapter.combinedStructuredOutputSource?.(options.modelOptions) ?? "text";
1932
1969
  const mcpManager = MCPManager.from(mcp);
1933
1970
  const mcpTools = await mcpManager.discover();
1934
1971
  if (mcpTools.length > 0) textOptions.tools = [...textOptions.tools ?? [], ...mcpTools];
@@ -1946,7 +1983,8 @@ async function runAgenticStructuredOutput(options) {
1946
1983
  yieldChunks: false,
1947
1984
  normalize,
1948
1985
  ...validate ? { validate } : {},
1949
- ...nativeCombined ? { nativeCombined: true } : {}
1986
+ ...nativeCombined ? { nativeCombined: true } : {},
1987
+ source
1950
1988
  }
1951
1989
  }, logger);
1952
1990
  try {
@@ -2007,13 +2045,13 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2007
2045
  const threadId = chatOptions.threadId ?? `fallback-${Date.now()}-${fallbackRand}`;
2008
2046
  const messageId = `fallback-${Date.now()}-${fallbackRand}`;
2009
2047
  const model = chatOptions.model;
2010
- const timestamp = Date.now();
2048
+ const startedAt = Date.now();
2011
2049
  yield {
2012
2050
  type: EventType.RUN_STARTED,
2013
2051
  runId,
2014
2052
  threadId,
2015
2053
  model,
2016
- timestamp
2054
+ timestamp: startedAt
2017
2055
  };
2018
2056
  let result;
2019
2057
  try {
@@ -2026,7 +2064,7 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2026
2064
  runId,
2027
2065
  threadId,
2028
2066
  model,
2029
- timestamp,
2067
+ timestamp: Date.now(),
2030
2068
  message,
2031
2069
  error: { message }
2032
2070
  };
@@ -2037,20 +2075,20 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2037
2075
  messageId,
2038
2076
  role: "assistant",
2039
2077
  model,
2040
- timestamp
2078
+ timestamp: Date.now()
2041
2079
  };
2042
2080
  yield {
2043
2081
  type: EventType.TEXT_MESSAGE_CONTENT,
2044
2082
  messageId,
2045
2083
  delta: result.rawText,
2046
2084
  model,
2047
- timestamp
2085
+ timestamp: Date.now()
2048
2086
  };
2049
2087
  yield {
2050
2088
  type: EventType.TEXT_MESSAGE_END,
2051
2089
  messageId,
2052
2090
  model,
2053
- timestamp
2091
+ timestamp: Date.now()
2054
2092
  };
2055
2093
  yield {
2056
2094
  type: EventType.CUSTOM,
@@ -2060,14 +2098,14 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2060
2098
  raw: result.rawText
2061
2099
  },
2062
2100
  model,
2063
- timestamp
2101
+ timestamp: Date.now()
2064
2102
  };
2065
2103
  yield {
2066
2104
  type: EventType.RUN_FINISHED,
2067
2105
  runId,
2068
2106
  threadId,
2069
2107
  model,
2070
- timestamp,
2108
+ timestamp: Date.now(),
2071
2109
  finishReason: "stop",
2072
2110
  ...result.usage ? { usage: result.usage } : {}
2073
2111
  };
@@ -2115,6 +2153,7 @@ async function* runStreamingStructuredOutputImpl(options, jsonSchema, normalize,
2115
2153
  const model = adapter.model;
2116
2154
  const logger = resolveDebugOption(debug);
2117
2155
  const nativeCombined = adapter.supportsCombinedToolsAndSchema?.(options.modelOptions) === true;
2156
+ const source = adapter.combinedStructuredOutputSource?.(options.modelOptions) ?? "text";
2118
2157
  const mcpManager = MCPManager.from(mcp);
2119
2158
  const mcpTools = await mcpManager.discover();
2120
2159
  if (mcpTools.length > 0) textOptions.tools = [...textOptions.tools ?? [], ...mcpTools];
@@ -2131,7 +2170,8 @@ async function* runStreamingStructuredOutputImpl(options, jsonSchema, normalize,
2131
2170
  jsonSchema,
2132
2171
  yieldChunks: true,
2133
2172
  normalize,
2134
- ...nativeCombined ? { nativeCombined: true } : {}
2173
+ ...nativeCombined ? { nativeCombined: true } : {},
2174
+ source
2135
2175
  }
2136
2176
  }, logger);
2137
2177
  engineRef.current = engine;