@tanstack/ai 0.44.0 → 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 +126 -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 +80 -34
  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 +155 -49
  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"}
@@ -142,6 +142,8 @@ var TextEngine = class {
142
142
  streamStartTime = 0;
143
143
  totalChunkCount = 0;
144
144
  currentMessageId = null;
145
+ currentMessageCreatedAt = null;
146
+ streamIdentityCaptured = false;
145
147
  accumulatedContent = "";
146
148
  accumulatedThinking = [];
147
149
  currentThinkingContent = "";
@@ -185,6 +187,7 @@ var TextEngine = class {
185
187
  validatedStructuredOutput = void 0;
186
188
  hasValidatedStructuredOutput = false;
187
189
  finalizationError = null;
190
+ combinedCompleteEmitted = false;
188
191
  finalStructuredOutput;
189
192
  constructor(config, logger) {
190
193
  this.logger = logger;
@@ -334,27 +337,31 @@ var TextEngine = class {
334
337
  this.endCycle();
335
338
  } while (await this.shouldContinue());
336
339
  this.logger.agentLoop("run finished", { finishReason: this.lastFinishReason });
337
- if (this.finalStructuredOutput && this.toolPhase !== "wait" && !this.isCancelled() && !this.finalizationError) if (this.finalStructuredOutput.nativeCombined === true) yield* this.harvestCombinedStructuredOutput();
338
- else yield* this.runStructuredFinalization();
339
- if (!this.terminalHookCalled && this.toolPhase !== "wait" && !this.isCancelled()) if (this.finalizationError) {
340
- this.terminalHookCalled = true;
341
- const errForHook = new Error(this.finalizationError.message, this.finalizationError.cause !== void 0 ? { cause: this.finalizationError.cause } : void 0);
342
- if (this.finalizationError.code !== void 0) Object.defineProperty(errForHook, "code", {
343
- value: this.finalizationError.code,
344
- enumerable: true
345
- });
346
- await this.middlewareRunner.runOnError(this.middlewareCtx, {
347
- error: errForHook,
348
- duration: Date.now() - this.streamStartTime
349
- });
350
- } else {
351
- this.terminalHookCalled = true;
352
- await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
353
- finishReason: this.lastFinishReason,
354
- duration: Date.now() - this.streamStartTime,
355
- content: this.accumulatedContent,
356
- usage: this.finishedEvent?.usage
357
- });
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
+ }
358
365
  }
359
366
  } catch (error) {
360
367
  if (error instanceof Error && error.name === "InterruptReplaySignal" && "continuationRunId" in error && typeof error.continuationRunId === "string") {
@@ -444,6 +451,8 @@ var TextEngine = class {
444
451
  }
445
452
  async beginIteration() {
446
453
  this.currentMessageId = this.createId("msg");
454
+ this.currentMessageCreatedAt = /* @__PURE__ */ new Date();
455
+ this.streamIdentityCaptured = false;
447
456
  this.accumulatedContent = "";
448
457
  this.accumulatedThinking = [];
449
458
  this.currentThinkingContent = "";
@@ -494,7 +503,32 @@ var TextEngine = class {
494
503
  if (this.isCancelled()) break;
495
504
  this.totalChunkCount++;
496
505
  this.handleStreamChunk(chunk);
497
- 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) {
498
532
  this.combinedStartEmitted = true;
499
533
  const messageId = typeof chunk.messageId === "string" && chunk.messageId !== "" ? chunk.messageId : generateMessageId();
500
534
  this.combinedStructuredMessageId = messageId;
@@ -513,7 +547,7 @@ var TextEngine = class {
513
547
  this.middlewareCtx.chunkIndex++;
514
548
  }
515
549
  }
516
- const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, chunk);
550
+ const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, outboundChunk);
517
551
  const suppressAgentLifecycle = !!this.finalStructuredOutput && this.finalStructuredOutput.yieldChunks && this.finalStructuredOutput.nativeCombined !== true;
518
552
  for (const outputChunk of outputChunks) {
519
553
  if (suppressAgentLifecycle && (outputChunk.type === EventType.RUN_STARTED || outputChunk.type === EventType.RUN_FINISHED)) continue;
@@ -533,6 +567,9 @@ var TextEngine = class {
533
567
  }
534
568
  handleStreamChunk(chunk) {
535
569
  switch (chunk.type) {
570
+ case "TEXT_MESSAGE_START":
571
+ if (typeof chunk.messageId === "string" && chunk.messageId !== "") this.captureStreamMessageIdentity(chunk.messageId);
572
+ break;
536
573
  case "TEXT_MESSAGE_CONTENT":
537
574
  this.handleTextMessageContentEvent(chunk);
538
575
  break;
@@ -554,16 +591,7 @@ var TextEngine = class {
554
591
  case "STEP_STARTED":
555
592
  this.handleStepStartedEvent();
556
593
  break;
557
- case "STEP_FINISHED":
558
- this.handleStepFinishedEvent(chunk);
559
- break;
560
- case "TOOL_CALL_RESULT": break;
561
- case "REASONING_START":
562
- case "REASONING_MESSAGE_START":
563
- case "REASONING_MESSAGE_CONTENT":
564
- case "REASONING_MESSAGE_END":
565
- case "REASONING_END": break;
566
- default: break;
594
+ case "STEP_FINISHED": this.handleStepFinishedEvent(chunk);
567
595
  }
568
596
  }
569
597
  handleTextMessageContentEvent(chunk) {
@@ -571,7 +599,16 @@ var TextEngine = class {
571
599
  else this.accumulatedContent += chunk.delta;
572
600
  this.middlewareCtx.accumulatedContent = this.accumulatedContent;
573
601
  }
602
+ captureStreamMessageIdentity(messageId) {
603
+ this.currentMessageId = messageId;
604
+ this.middlewareCtx.currentMessageId = messageId;
605
+ if (!this.streamIdentityCaptured) {
606
+ this.currentMessageCreatedAt = /* @__PURE__ */ new Date();
607
+ this.streamIdentityCaptured = true;
608
+ }
609
+ }
574
610
  handleToolCallStartEvent(chunk) {
611
+ if (typeof chunk.parentMessageId === "string" && chunk.parentMessageId !== "") this.captureStreamMessageIdentity(chunk.parentMessageId);
575
612
  this.toolCallManager.addToolCallStartEvent(chunk);
576
613
  }
577
614
  handleToolCallArgsEvent(chunk) {
@@ -584,8 +621,15 @@ var TextEngine = class {
584
621
  this.finishedEvent = chunk;
585
622
  this.lastFinishReason = chunk.finishReason ?? null;
586
623
  }
587
- handleRunErrorEvent(_chunk) {
624
+ handleRunErrorEvent(chunk) {
588
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
+ }
589
633
  }
590
634
  finalizeCurrentThinkingStep() {
591
635
  if (this.currentThinkingContent) {
@@ -808,6 +852,8 @@ var TextEngine = class {
808
852
  role: "assistant",
809
853
  content: this.accumulatedContent || null,
810
854
  toolCalls,
855
+ id: this.currentMessageId ?? void 0,
856
+ createdAt: this.currentMessageCreatedAt ?? void 0,
811
857
  ...this.accumulatedThinking.length > 0 && { thinking: this.accumulatedThinking }
812
858
  }];
813
859
  this.middlewareCtx.messages = this.messages;
@@ -922,7 +968,7 @@ var TextEngine = class {
922
968
  const messages = this.messages.map((message, index) => {
923
969
  const content = typeof message.content === "string" ? message.content : message.content === null ? void 0 : JSON.stringify(message.content);
924
970
  return {
925
- id: `snapshot_${this.runIdOverride ?? this.requestId}_${index}`,
971
+ id: message.id || `snapshot_${this.runIdOverride ?? this.requestId}_${index}`,
926
972
  role: message.role,
927
973
  ...content !== void 0 ? { content } : {},
928
974
  ..."toolCalls" in message && message.toolCalls ? { toolCalls: message.toolCalls } : {},
@@ -1354,7 +1400,7 @@ var TextEngine = class {
1354
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;
1355
1401
  return null;
1356
1402
  };
1357
- const buildSynthesizedStart = () => {
1403
+ const buildSynthesizedStart = (timestamp = Date.now()) => {
1358
1404
  const idForStart = structuredMessageId ?? generateMessageId();
1359
1405
  structuredMessageId = idForStart;
1360
1406
  return {
@@ -1362,7 +1408,7 @@ var TextEngine = class {
1362
1408
  name: "structured-output.start",
1363
1409
  value: { messageId: idForStart },
1364
1410
  model: this.params.model,
1365
- timestamp: Date.now(),
1411
+ timestamp,
1366
1412
  threadId: this.threadId,
1367
1413
  ...this.runIdOverride ? { runId: this.runIdOverride } : {}
1368
1414
  };
@@ -1379,7 +1425,7 @@ var TextEngine = class {
1379
1425
  if (this.finalStructuredOutput.yieldChunks) {
1380
1426
  if (!startEmitted && (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT || chunk.type === EventType.TEXT_MESSAGE_END)) {
1381
1427
  startEmitted = true;
1382
- const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart());
1428
+ const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart(chunk.timestamp));
1383
1429
  for (const outputChunk of synthOutputs) {
1384
1430
  yield outputChunk;
1385
1431
  this.middlewareCtx.chunkIndex++;
@@ -1387,7 +1433,7 @@ var TextEngine = class {
1387
1433
  }
1388
1434
  if (!startEmitted && chunk.type === EventType.RUN_ERROR) {
1389
1435
  startEmitted = true;
1390
- const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart());
1436
+ const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart(chunk.timestamp));
1391
1437
  for (const outputChunk of synthOutputs) {
1392
1438
  yield outputChunk;
1393
1439
  this.middlewareCtx.chunkIndex++;
@@ -1493,25 +1539,32 @@ var TextEngine = class {
1493
1539
  async *harvestCombinedStructuredOutput() {
1494
1540
  if (!this.finalStructuredOutput) throw new Error("harvestCombinedStructuredOutput called without finalStructuredOutput config");
1495
1541
  const yieldChunks = this.finalStructuredOutput.yieldChunks;
1496
- const rawText = this.accumulatedContent;
1497
- if (rawText.length === 0) this.finalizationError = {
1498
- message: "missing structured result",
1499
- code: "structured-output-missing-result"
1500
- };
1501
- else try {
1502
- const parsed = JSON.parse(rawText);
1503
- const data = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed) : parsed;
1504
- this.structuredOutputResult = {
1505
- data,
1506
- 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"
1507
1546
  };
1508
- } catch (err) {
1509
- const detail = rawText.slice(0, 200) + (rawText.length > 200 ? "..." : "");
1510
- this.finalizationError = {
1511
- message: `Failed to parse structured output as JSON. Content: ${detail}`,
1512
- code: "structured-output-parse-failed",
1513
- 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"
1514
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
+ }
1515
1568
  }
1516
1569
  if (this.structuredOutputResult && !this.finalizationError && this.finalStructuredOutput.validate) try {
1517
1570
  const validated = this.finalStructuredOutput.validate(this.structuredOutputResult.data);
@@ -1545,7 +1598,7 @@ var TextEngine = class {
1545
1598
  this.middlewareCtx.chunkIndex++;
1546
1599
  }
1547
1600
  }
1548
- if (this.structuredOutputResult && !this.finalizationError) {
1601
+ if (this.structuredOutputResult && !this.finalizationError && !this.combinedCompleteEmitted) {
1549
1602
  const completeChunk = {
1550
1603
  type: EventType.CUSTOM,
1551
1604
  name: "structured-output.complete",
@@ -1893,7 +1946,8 @@ async function* streamTextChunks(options, engineRef) {
1893
1946
  * Runs the full agentic loop (if tools are provided) but returns collected text.
1894
1947
  */
1895
1948
  function runNonStreamingText(options) {
1896
- return streamToText(runStreamingText(options));
1949
+ const stream = runStreamingText(options);
1950
+ return streamToText(stream);
1897
1951
  }
1898
1952
  /**
1899
1953
  * Run agentic structured output:
@@ -1911,6 +1965,7 @@ async function runAgenticStructuredOutput(options) {
1911
1965
  const normalize = (data) => undoNullWidening(data, nullWideningMap);
1912
1966
  const validate = isStandardSchema(outputSchema) ? (data) => parseWithStandardSchema(outputSchema, data) : void 0;
1913
1967
  const nativeCombined = adapter.supportsCombinedToolsAndSchema?.(options.modelOptions) === true;
1968
+ const source = adapter.combinedStructuredOutputSource?.(options.modelOptions) ?? "text";
1914
1969
  const mcpManager = MCPManager.from(mcp);
1915
1970
  const mcpTools = await mcpManager.discover();
1916
1971
  if (mcpTools.length > 0) textOptions.tools = [...textOptions.tools ?? [], ...mcpTools];
@@ -1928,7 +1983,8 @@ async function runAgenticStructuredOutput(options) {
1928
1983
  yieldChunks: false,
1929
1984
  normalize,
1930
1985
  ...validate ? { validate } : {},
1931
- ...nativeCombined ? { nativeCombined: true } : {}
1986
+ ...nativeCombined ? { nativeCombined: true } : {},
1987
+ source
1932
1988
  }
1933
1989
  }, logger);
1934
1990
  try {
@@ -1989,13 +2045,13 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
1989
2045
  const threadId = chatOptions.threadId ?? `fallback-${Date.now()}-${fallbackRand}`;
1990
2046
  const messageId = `fallback-${Date.now()}-${fallbackRand}`;
1991
2047
  const model = chatOptions.model;
1992
- const timestamp = Date.now();
2048
+ const startedAt = Date.now();
1993
2049
  yield {
1994
2050
  type: EventType.RUN_STARTED,
1995
2051
  runId,
1996
2052
  threadId,
1997
2053
  model,
1998
- timestamp
2054
+ timestamp: startedAt
1999
2055
  };
2000
2056
  let result;
2001
2057
  try {
@@ -2008,7 +2064,7 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2008
2064
  runId,
2009
2065
  threadId,
2010
2066
  model,
2011
- timestamp,
2067
+ timestamp: Date.now(),
2012
2068
  message,
2013
2069
  error: { message }
2014
2070
  };
@@ -2019,20 +2075,20 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2019
2075
  messageId,
2020
2076
  role: "assistant",
2021
2077
  model,
2022
- timestamp
2078
+ timestamp: Date.now()
2023
2079
  };
2024
2080
  yield {
2025
2081
  type: EventType.TEXT_MESSAGE_CONTENT,
2026
2082
  messageId,
2027
2083
  delta: result.rawText,
2028
2084
  model,
2029
- timestamp
2085
+ timestamp: Date.now()
2030
2086
  };
2031
2087
  yield {
2032
2088
  type: EventType.TEXT_MESSAGE_END,
2033
2089
  messageId,
2034
2090
  model,
2035
- timestamp
2091
+ timestamp: Date.now()
2036
2092
  };
2037
2093
  yield {
2038
2094
  type: EventType.CUSTOM,
@@ -2042,14 +2098,14 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2042
2098
  raw: result.rawText
2043
2099
  },
2044
2100
  model,
2045
- timestamp
2101
+ timestamp: Date.now()
2046
2102
  };
2047
2103
  yield {
2048
2104
  type: EventType.RUN_FINISHED,
2049
2105
  runId,
2050
2106
  threadId,
2051
2107
  model,
2052
- timestamp,
2108
+ timestamp: Date.now(),
2053
2109
  finishReason: "stop",
2054
2110
  ...result.usage ? { usage: result.usage } : {}
2055
2111
  };
@@ -2097,6 +2153,7 @@ async function* runStreamingStructuredOutputImpl(options, jsonSchema, normalize,
2097
2153
  const model = adapter.model;
2098
2154
  const logger = resolveDebugOption(debug);
2099
2155
  const nativeCombined = adapter.supportsCombinedToolsAndSchema?.(options.modelOptions) === true;
2156
+ const source = adapter.combinedStructuredOutputSource?.(options.modelOptions) ?? "text";
2100
2157
  const mcpManager = MCPManager.from(mcp);
2101
2158
  const mcpTools = await mcpManager.discover();
2102
2159
  if (mcpTools.length > 0) textOptions.tools = [...textOptions.tools ?? [], ...mcpTools];
@@ -2113,7 +2170,8 @@ async function* runStreamingStructuredOutputImpl(options, jsonSchema, normalize,
2113
2170
  jsonSchema,
2114
2171
  yieldChunks: true,
2115
2172
  normalize,
2116
- ...nativeCombined ? { nativeCombined: true } : {}
2173
+ ...nativeCombined ? { nativeCombined: true } : {},
2174
+ source
2117
2175
  }
2118
2176
  }, logger);
2119
2177
  engineRef.current = engine;