@tanstack/ai 0.44.1 → 0.45.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.
Files changed (54) hide show
  1. package/README.md +10 -1
  2. package/dist/esm/activities/chat/adapter.d.ts +13 -1
  3. package/dist/esm/activities/chat/adapter.js.map +1 -1
  4. package/dist/esm/activities/chat/index.js +122 -69
  5. package/dist/esm/activities/chat/index.js.map +1 -1
  6. package/dist/esm/activities/chat/messages.js +24 -27
  7. package/dist/esm/activities/chat/messages.js.map +1 -1
  8. package/dist/esm/activities/chat/stream/processor.js +14 -13
  9. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  10. package/dist/esm/activities/chat/tools/approval-schema.js +11 -8
  11. package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -1
  12. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
  13. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  14. package/dist/esm/activities/chat/tools/tool-calls.js +40 -31
  15. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  16. package/dist/esm/activities/generateVideo/index.js +3 -2
  17. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  18. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  19. package/dist/esm/adapter-internals.d.ts +2 -0
  20. package/dist/esm/adapter-internals.js +3 -1
  21. package/dist/esm/interrupt-resume.js +24 -20
  22. package/dist/esm/interrupt-resume.js.map +1 -1
  23. package/dist/esm/interrupts.js +2 -1
  24. package/dist/esm/interrupts.js.map +1 -1
  25. package/dist/esm/logger/console-logger.js +1 -3
  26. package/dist/esm/logger/console-logger.js.map +1 -1
  27. package/dist/esm/logger/resolve.js +2 -1
  28. package/dist/esm/logger/resolve.js.map +1 -1
  29. package/dist/esm/stream-durability.js +1 -1
  30. package/dist/esm/stream-durability.js.map +1 -1
  31. package/dist/esm/types.d.ts +19 -16
  32. package/dist/esm/utilities/chat-params.js +1 -3
  33. package/dist/esm/utilities/chat-params.js.map +1 -1
  34. package/dist/esm/utilities/media-prompt.js +1 -3
  35. package/dist/esm/utilities/media-prompt.js.map +1 -1
  36. package/dist/esm/utilities/structured-output-events.d.ts +17 -0
  37. package/dist/esm/utilities/structured-output-events.js +32 -0
  38. package/dist/esm/utilities/structured-output-events.js.map +1 -0
  39. package/dist/esm/utilities/structured-output-text.d.ts +7 -0
  40. package/dist/esm/utilities/structured-output-text.js +50 -0
  41. package/dist/esm/utilities/structured-output-text.js.map +1 -0
  42. package/package.json +2 -2
  43. package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +6 -2
  44. package/skills/ai-core/media-generation/SKILL.md +33 -19
  45. package/skills/ai-core/structured-outputs/SKILL.md +73 -1
  46. package/skills/ai-core/tool-calling/SKILL.md +1 -1
  47. package/src/activities/chat/adapter.ts +16 -1
  48. package/src/activities/chat/index.ts +155 -52
  49. package/src/activities/chat/stream/processor.ts +6 -3
  50. package/src/activities/chat/tools/tool-calls.ts +12 -4
  51. package/src/adapter-internals.ts +8 -0
  52. package/src/types.ts +27 -19
  53. package/src/utilities/structured-output-events.ts +44 -0
  54. package/src/utilities/structured-output-text.ts +63 -0
package/README.md CHANGED
@@ -36,6 +36,14 @@
36
36
  </a>
37
37
  </div>
38
38
 
39
+ <br />
40
+
41
+ <div align="center">
42
+ <a href="https://tanstack.com/blog/tanstack-open-source-awards-2026">
43
+ <img src="https://raw.githubusercontent.com/TanStack/ai/16826d81cade868956df240d6239a671e689193c/media/js-open-source-award-2026-ai-project-of-the-year.svg" alt="Winner of the 2026 JavaScript Open Source Award for AI Project of the Year" width="250" />
44
+ </a>
45
+ </div>
46
+
39
47
  # TanStack AI
40
48
 
41
49
  Type-safe, provider-agnostic TypeScript SDK for building streaming chat,
@@ -191,7 +199,7 @@ Learn more in the
191
199
  build low-latency realtime voice experiences.
192
200
  - [Code Mode](https://tanstack.com/ai/latest/docs/code-mode/code-mode) - let
193
201
  models write and execute TypeScript inside a secure isolate.
194
- - [Code Mode with Skills](https://tanstack.com/ai/latest/docs/code-mode/code-mode-with-skills) -
202
+ - [Code Mode with Snippets](https://tanstack.com/ai/latest/docs/code-mode/code-mode-with-snippets) -
195
203
  give Code Mode reusable runtime capabilities.
196
204
 
197
205
  ## Providers
@@ -208,6 +216,7 @@ Official adapters include:
208
216
  | [`@tanstack/ai-grok`](https://tanstack.com/ai/latest/docs/adapters/grok) | xAI Grok chat, images, and realtime |
209
217
  | [`@tanstack/ai-groq`](https://tanstack.com/ai/latest/docs/adapters/groq) | Groq low-latency inference |
210
218
  | [`@tanstack/ai-elevenlabs`](https://tanstack.com/ai/latest/docs/adapters/elevenlabs) | ElevenLabs realtime voice, speech, transcription, music, and sound effects |
219
+ | [`@tanstack/ai-byteplus`](https://tanstack.com/ai/latest/docs/adapters/byteplus) | BytePlus Seed chat, Seedance video, Seedream image, and Seed Speech TTS/ASR |
211
220
  | [`@tanstack/ai-fal`](https://tanstack.com/ai/latest/docs/adapters/fal) | fal.ai image, video, audio, speech, and transcription models |
212
221
 
213
222
  The adapter system is tree-shakeable by activity. Import `openaiText` for chat,
@@ -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"}
@@ -151,6 +151,7 @@ var TextEngine = class {
151
151
  eventOptions;
152
152
  eventToolNames;
153
153
  finishedEvent = null;
154
+ streamedToolErrorResults = /* @__PURE__ */ new Map();
154
155
  deferredToolCallRunFinishedChunks = [];
155
156
  earlyTermination = false;
156
157
  toolPhase = "continue";
@@ -187,6 +188,7 @@ var TextEngine = class {
187
188
  validatedStructuredOutput = void 0;
188
189
  hasValidatedStructuredOutput = false;
189
190
  finalizationError = null;
191
+ combinedCompleteEmitted = false;
190
192
  finalStructuredOutput;
191
193
  constructor(config, logger) {
192
194
  this.logger = logger;
@@ -336,27 +338,31 @@ var TextEngine = class {
336
338
  this.endCycle();
337
339
  } while (await this.shouldContinue());
338
340
  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
- });
341
+ if (this.finalStructuredOutput && this.toolPhase !== "wait" && !this.isCancelled() && !this.finalizationError && !this.earlyTermination) {
342
+ if (this.finalStructuredOutput.nativeCombined === true) yield* this.harvestCombinedStructuredOutput();
343
+ else yield* this.runStructuredFinalization();
344
+ }
345
+ if (!this.terminalHookCalled && this.toolPhase !== "wait" && !this.isCancelled()) {
346
+ if (this.finalizationError) {
347
+ this.terminalHookCalled = true;
348
+ const errForHook = new Error(this.finalizationError.message, this.finalizationError.cause !== void 0 ? { cause: this.finalizationError.cause } : void 0);
349
+ if (this.finalizationError.code !== void 0) Object.defineProperty(errForHook, "code", {
350
+ value: this.finalizationError.code,
351
+ enumerable: true
352
+ });
353
+ await this.middlewareRunner.runOnError(this.middlewareCtx, {
354
+ error: errForHook,
355
+ duration: Date.now() - this.streamStartTime
356
+ });
357
+ } else {
358
+ this.terminalHookCalled = true;
359
+ await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
360
+ finishReason: this.lastFinishReason,
361
+ duration: Date.now() - this.streamStartTime,
362
+ content: this.accumulatedContent,
363
+ usage: this.finishedEvent?.usage
364
+ });
365
+ }
360
366
  }
361
367
  } catch (error) {
362
368
  if (error instanceof Error && error.name === "InterruptReplaySignal" && "continuationRunId" in error && typeof error.continuationRunId === "string") {
@@ -453,6 +459,7 @@ var TextEngine = class {
453
459
  this.currentThinkingContent = "";
454
460
  this.currentThinkingSignature = "";
455
461
  this.finishedEvent = null;
462
+ this.streamedToolErrorResults.clear();
456
463
  this.middlewareCtx.currentMessageId = this.currentMessageId;
457
464
  this.middlewareCtx.accumulatedContent = "";
458
465
  await this.middlewareRunner.runOnIteration(this.middlewareCtx, {
@@ -498,7 +505,32 @@ var TextEngine = class {
498
505
  if (this.isCancelled()) break;
499
506
  this.totalChunkCount++;
500
507
  this.handleStreamChunk(chunk);
501
- if (this.finalStructuredOutput?.nativeCombined === true && this.finalStructuredOutput.yieldChunks && !this.combinedStartEmitted && chunk.type === EventType.TEXT_MESSAGE_START) {
508
+ if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") {
509
+ this.combinedStartEmitted = true;
510
+ const startValue = chunk.value;
511
+ if (startValue && typeof startValue === "object" && "messageId" in startValue && typeof startValue.messageId === "string") this.combinedStructuredMessageId = startValue.messageId;
512
+ }
513
+ let outboundChunk = chunk;
514
+ if (this.finalStructuredOutput?.source === "event" && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
515
+ const parsed = readStructuredOutputCompleteValue(chunk.value);
516
+ if (parsed) {
517
+ const object = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed.object) : parsed.object;
518
+ this.structuredOutputResult = {
519
+ data: object,
520
+ rawText: parsed.raw
521
+ };
522
+ this.combinedCompleteEmitted = true;
523
+ const value = chunk.value;
524
+ if (object !== parsed.object && value && typeof value === "object") outboundChunk = {
525
+ ...chunk,
526
+ value: {
527
+ ...value,
528
+ object
529
+ }
530
+ };
531
+ }
532
+ }
533
+ if (this.finalStructuredOutput?.nativeCombined === true && this.finalStructuredOutput.yieldChunks && this.finalStructuredOutput.source !== "event" && !this.combinedStartEmitted && chunk.type === EventType.TEXT_MESSAGE_START) {
502
534
  this.combinedStartEmitted = true;
503
535
  const messageId = typeof chunk.messageId === "string" && chunk.messageId !== "" ? chunk.messageId : generateMessageId();
504
536
  this.combinedStructuredMessageId = messageId;
@@ -517,7 +549,7 @@ var TextEngine = class {
517
549
  this.middlewareCtx.chunkIndex++;
518
550
  }
519
551
  }
520
- const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, chunk);
552
+ const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, outboundChunk);
521
553
  const suppressAgentLifecycle = !!this.finalStructuredOutput && this.finalStructuredOutput.yieldChunks && this.finalStructuredOutput.nativeCombined !== true;
522
554
  for (const outputChunk of outputChunks) {
523
555
  if (suppressAgentLifecycle && (outputChunk.type === EventType.RUN_STARTED || outputChunk.type === EventType.RUN_FINISHED)) continue;
@@ -561,16 +593,7 @@ var TextEngine = class {
561
593
  case "STEP_STARTED":
562
594
  this.handleStepStartedEvent();
563
595
  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;
596
+ case "STEP_FINISHED": this.handleStepFinishedEvent(chunk);
574
597
  }
575
598
  }
576
599
  handleTextMessageContentEvent(chunk) {
@@ -595,13 +618,30 @@ var TextEngine = class {
595
618
  }
596
619
  handleToolCallEndEvent(chunk) {
597
620
  this.toolCallManager.completeToolCall(chunk);
621
+ if (chunk.state !== "output-error" || chunk.result === void 0) return;
622
+ const toolCall = this.toolCallManager.getToolCalls().find((candidate) => candidate.id === chunk.toolCallId);
623
+ if (!toolCall) return;
624
+ this.streamedToolErrorResults.set(chunk.toolCallId, {
625
+ toolCallId: chunk.toolCallId,
626
+ toolName: toolCall.function.name,
627
+ result: chunk.result,
628
+ ...chunk.input !== void 0 && { input: chunk.input },
629
+ state: "output-error"
630
+ });
598
631
  }
599
632
  handleRunFinishedEvent(chunk) {
600
633
  this.finishedEvent = chunk;
601
634
  this.lastFinishReason = chunk.finishReason ?? null;
602
635
  }
603
- handleRunErrorEvent(_chunk) {
636
+ handleRunErrorEvent(chunk) {
604
637
  this.earlyTermination = true;
638
+ if (this.finalStructuredOutput && this.finalizationError === null) {
639
+ const message = chunk.message || chunk.error?.message || "Run failed before structured output completed";
640
+ this.finalizationError = {
641
+ message,
642
+ ...chunk.code !== void 0 ? { code: chunk.code } : chunk.error?.code !== void 0 ? { code: chunk.error.code } : {}
643
+ };
644
+ }
605
645
  }
606
646
  finalizeCurrentThinkingStep() {
607
647
  if (this.currentThinkingContent) {
@@ -723,6 +763,7 @@ var TextEngine = class {
723
763
  this.addAssistantToolCallMessage(toolCalls);
724
764
  const undiscoveredLazyResults = [];
725
765
  const executableToolCalls = toolCalls.filter((tc) => {
766
+ if (this.streamedToolErrorResults.has(tc.id)) return false;
726
767
  if (this.lazyToolManager.isUndiscoveredLazyTool(tc.function.name)) {
727
768
  undiscoveredLazyResults.push({
728
769
  toolCallId: tc.id,
@@ -734,7 +775,7 @@ var TextEngine = class {
734
775
  }
735
776
  return true;
736
777
  });
737
- const deferredErrorResults = [...undiscoveredLazyResults];
778
+ const deferredErrorResults = [...this.streamedToolErrorResults.values(), ...undiscoveredLazyResults];
738
779
  if (executableToolCalls.length === 0) {
739
780
  yield* this.flushDeferredToolCallRunFinishedChunks();
740
781
  if (deferredErrorResults.length > 0) for (const chunk of this.buildToolResultChunks(deferredErrorResults, finishEvent)) yield* this.pipeThroughMiddleware(chunk);
@@ -940,7 +981,7 @@ var TextEngine = class {
940
981
  const messages = this.messages.map((message, index) => {
941
982
  const content = typeof message.content === "string" ? message.content : message.content === null ? void 0 : JSON.stringify(message.content);
942
983
  return {
943
- id: `snapshot_${this.runIdOverride ?? this.requestId}_${index}`,
984
+ id: message.id || `snapshot_${this.runIdOverride ?? this.requestId}_${index}`,
944
985
  role: message.role,
945
986
  ...content !== void 0 ? { content } : {},
946
987
  ..."toolCalls" in message && message.toolCalls ? { toolCalls: message.toolCalls } : {},
@@ -1372,7 +1413,7 @@ var TextEngine = class {
1372
1413
  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
1414
  return null;
1374
1415
  };
1375
- const buildSynthesizedStart = () => {
1416
+ const buildSynthesizedStart = (timestamp = Date.now()) => {
1376
1417
  const idForStart = structuredMessageId ?? generateMessageId();
1377
1418
  structuredMessageId = idForStart;
1378
1419
  return {
@@ -1380,7 +1421,7 @@ var TextEngine = class {
1380
1421
  name: "structured-output.start",
1381
1422
  value: { messageId: idForStart },
1382
1423
  model: this.params.model,
1383
- timestamp: Date.now(),
1424
+ timestamp,
1384
1425
  threadId: this.threadId,
1385
1426
  ...this.runIdOverride ? { runId: this.runIdOverride } : {}
1386
1427
  };
@@ -1397,7 +1438,7 @@ var TextEngine = class {
1397
1438
  if (this.finalStructuredOutput.yieldChunks) {
1398
1439
  if (!startEmitted && (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT || chunk.type === EventType.TEXT_MESSAGE_END)) {
1399
1440
  startEmitted = true;
1400
- const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart());
1441
+ const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart(chunk.timestamp));
1401
1442
  for (const outputChunk of synthOutputs) {
1402
1443
  yield outputChunk;
1403
1444
  this.middlewareCtx.chunkIndex++;
@@ -1405,7 +1446,7 @@ var TextEngine = class {
1405
1446
  }
1406
1447
  if (!startEmitted && chunk.type === EventType.RUN_ERROR) {
1407
1448
  startEmitted = true;
1408
- const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart());
1449
+ const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart(chunk.timestamp));
1409
1450
  for (const outputChunk of synthOutputs) {
1410
1451
  yield outputChunk;
1411
1452
  this.middlewareCtx.chunkIndex++;
@@ -1511,25 +1552,32 @@ var TextEngine = class {
1511
1552
  async *harvestCombinedStructuredOutput() {
1512
1553
  if (!this.finalStructuredOutput) throw new Error("harvestCombinedStructuredOutput called without finalStructuredOutput config");
1513
1554
  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
1555
+ if ((this.finalStructuredOutput.source ?? "text") === "event") {
1556
+ if (!this.structuredOutputResult) this.finalizationError = {
1557
+ message: "missing structured result",
1558
+ code: "structured-output-missing-result"
1525
1559
  };
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
1560
+ } else {
1561
+ const rawText = this.accumulatedContent;
1562
+ if (rawText.length === 0) this.finalizationError = {
1563
+ message: "missing structured result",
1564
+ code: "structured-output-missing-result"
1532
1565
  };
1566
+ else try {
1567
+ const parsed = JSON.parse(rawText);
1568
+ const data = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed) : parsed;
1569
+ this.structuredOutputResult = {
1570
+ data,
1571
+ rawText
1572
+ };
1573
+ } catch (err) {
1574
+ const detail = rawText.slice(0, 200) + (rawText.length > 200 ? "..." : "");
1575
+ this.finalizationError = {
1576
+ message: `Failed to parse structured output as JSON. Content: ${detail}`,
1577
+ code: "structured-output-parse-failed",
1578
+ cause: err
1579
+ };
1580
+ }
1533
1581
  }
1534
1582
  if (this.structuredOutputResult && !this.finalizationError && this.finalStructuredOutput.validate) try {
1535
1583
  const validated = this.finalStructuredOutput.validate(this.structuredOutputResult.data);
@@ -1563,7 +1611,7 @@ var TextEngine = class {
1563
1611
  this.middlewareCtx.chunkIndex++;
1564
1612
  }
1565
1613
  }
1566
- if (this.structuredOutputResult && !this.finalizationError) {
1614
+ if (this.structuredOutputResult && !this.finalizationError && !this.combinedCompleteEmitted) {
1567
1615
  const completeChunk = {
1568
1616
  type: EventType.CUSTOM,
1569
1617
  name: "structured-output.complete",
@@ -1911,7 +1959,8 @@ async function* streamTextChunks(options, engineRef) {
1911
1959
  * Runs the full agentic loop (if tools are provided) but returns collected text.
1912
1960
  */
1913
1961
  function runNonStreamingText(options) {
1914
- return streamToText(runStreamingText(options));
1962
+ const stream = runStreamingText(options);
1963
+ return streamToText(stream);
1915
1964
  }
1916
1965
  /**
1917
1966
  * Run agentic structured output:
@@ -1929,6 +1978,7 @@ async function runAgenticStructuredOutput(options) {
1929
1978
  const normalize = (data) => undoNullWidening(data, nullWideningMap);
1930
1979
  const validate = isStandardSchema(outputSchema) ? (data) => parseWithStandardSchema(outputSchema, data) : void 0;
1931
1980
  const nativeCombined = adapter.supportsCombinedToolsAndSchema?.(options.modelOptions) === true;
1981
+ const source = adapter.combinedStructuredOutputSource?.(options.modelOptions) ?? "text";
1932
1982
  const mcpManager = MCPManager.from(mcp);
1933
1983
  const mcpTools = await mcpManager.discover();
1934
1984
  if (mcpTools.length > 0) textOptions.tools = [...textOptions.tools ?? [], ...mcpTools];
@@ -1946,7 +1996,8 @@ async function runAgenticStructuredOutput(options) {
1946
1996
  yieldChunks: false,
1947
1997
  normalize,
1948
1998
  ...validate ? { validate } : {},
1949
- ...nativeCombined ? { nativeCombined: true } : {}
1999
+ ...nativeCombined ? { nativeCombined: true } : {},
2000
+ source
1950
2001
  }
1951
2002
  }, logger);
1952
2003
  try {
@@ -2007,13 +2058,13 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2007
2058
  const threadId = chatOptions.threadId ?? `fallback-${Date.now()}-${fallbackRand}`;
2008
2059
  const messageId = `fallback-${Date.now()}-${fallbackRand}`;
2009
2060
  const model = chatOptions.model;
2010
- const timestamp = Date.now();
2061
+ const startedAt = Date.now();
2011
2062
  yield {
2012
2063
  type: EventType.RUN_STARTED,
2013
2064
  runId,
2014
2065
  threadId,
2015
2066
  model,
2016
- timestamp
2067
+ timestamp: startedAt
2017
2068
  };
2018
2069
  let result;
2019
2070
  try {
@@ -2026,7 +2077,7 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2026
2077
  runId,
2027
2078
  threadId,
2028
2079
  model,
2029
- timestamp,
2080
+ timestamp: Date.now(),
2030
2081
  message,
2031
2082
  error: { message }
2032
2083
  };
@@ -2037,20 +2088,20 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2037
2088
  messageId,
2038
2089
  role: "assistant",
2039
2090
  model,
2040
- timestamp
2091
+ timestamp: Date.now()
2041
2092
  };
2042
2093
  yield {
2043
2094
  type: EventType.TEXT_MESSAGE_CONTENT,
2044
2095
  messageId,
2045
2096
  delta: result.rawText,
2046
2097
  model,
2047
- timestamp
2098
+ timestamp: Date.now()
2048
2099
  };
2049
2100
  yield {
2050
2101
  type: EventType.TEXT_MESSAGE_END,
2051
2102
  messageId,
2052
2103
  model,
2053
- timestamp
2104
+ timestamp: Date.now()
2054
2105
  };
2055
2106
  yield {
2056
2107
  type: EventType.CUSTOM,
@@ -2060,14 +2111,14 @@ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError)
2060
2111
  raw: result.rawText
2061
2112
  },
2062
2113
  model,
2063
- timestamp
2114
+ timestamp: Date.now()
2064
2115
  };
2065
2116
  yield {
2066
2117
  type: EventType.RUN_FINISHED,
2067
2118
  runId,
2068
2119
  threadId,
2069
2120
  model,
2070
- timestamp,
2121
+ timestamp: Date.now(),
2071
2122
  finishReason: "stop",
2072
2123
  ...result.usage ? { usage: result.usage } : {}
2073
2124
  };
@@ -2115,6 +2166,7 @@ async function* runStreamingStructuredOutputImpl(options, jsonSchema, normalize,
2115
2166
  const model = adapter.model;
2116
2167
  const logger = resolveDebugOption(debug);
2117
2168
  const nativeCombined = adapter.supportsCombinedToolsAndSchema?.(options.modelOptions) === true;
2169
+ const source = adapter.combinedStructuredOutputSource?.(options.modelOptions) ?? "text";
2118
2170
  const mcpManager = MCPManager.from(mcp);
2119
2171
  const mcpTools = await mcpManager.discover();
2120
2172
  if (mcpTools.length > 0) textOptions.tools = [...textOptions.tools ?? [], ...mcpTools];
@@ -2131,7 +2183,8 @@ async function* runStreamingStructuredOutputImpl(options, jsonSchema, normalize,
2131
2183
  jsonSchema,
2132
2184
  yieldChunks: true,
2133
2185
  normalize,
2134
- ...nativeCombined ? { nativeCombined: true } : {}
2186
+ ...nativeCombined ? { nativeCombined: true } : {},
2187
+ source
2135
2188
  }
2136
2189
  }, logger);
2137
2190
  engineRef.current = engine;