@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.
- package/README.md +10 -1
- package/dist/esm/activities/chat/adapter.d.ts +13 -1
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.js +122 -69
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +24 -27
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.js +14 -13
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/approval-schema.js +11 -8
- package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.js +40 -31
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +3 -2
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -0
- package/dist/esm/adapter-internals.js +3 -1
- package/dist/esm/interrupt-resume.js +24 -20
- package/dist/esm/interrupt-resume.js.map +1 -1
- package/dist/esm/interrupts.js +2 -1
- package/dist/esm/interrupts.js.map +1 -1
- package/dist/esm/logger/console-logger.js +1 -3
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/resolve.js +2 -1
- package/dist/esm/logger/resolve.js.map +1 -1
- package/dist/esm/stream-durability.js +1 -1
- package/dist/esm/stream-durability.js.map +1 -1
- package/dist/esm/types.d.ts +19 -16
- package/dist/esm/utilities/chat-params.js +1 -3
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/media-prompt.js +1 -3
- package/dist/esm/utilities/media-prompt.js.map +1 -1
- package/dist/esm/utilities/structured-output-events.d.ts +17 -0
- package/dist/esm/utilities/structured-output-events.js +32 -0
- package/dist/esm/utilities/structured-output-events.js.map +1 -0
- package/dist/esm/utilities/structured-output-text.d.ts +7 -0
- package/dist/esm/utilities/structured-output-text.js +50 -0
- package/dist/esm/utilities/structured-output-text.js.map +1 -0
- package/package.json +2 -2
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +6 -2
- package/skills/ai-core/media-generation/SKILL.md +33 -19
- package/skills/ai-core/structured-outputs/SKILL.md +73 -1
- package/skills/ai-core/tool-calling/SKILL.md +1 -1
- package/src/activities/chat/adapter.ts +16 -1
- package/src/activities/chat/index.ts +155 -52
- package/src/activities/chat/stream/processor.ts +6 -3
- package/src/activities/chat/tools/tool-calls.ts +12 -4
- package/src/adapter-internals.ts +8 -0
- package/src/types.ts +27 -19
- package/src/utilities/structured-output-events.ts +44 -0
- 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
|
|
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? }
|
|
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
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (this.finalizationError
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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 (
|
|
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,
|
|
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(
|
|
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
|
|
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
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
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
|
-
}
|
|
1527
|
-
const
|
|
1528
|
-
this.finalizationError = {
|
|
1529
|
-
message:
|
|
1530
|
-
code: "structured-output-
|
|
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
|
-
|
|
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
|
|
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;
|