@tanstack/ai 0.16.0 → 0.17.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 (39) hide show
  1. package/dist/esm/activities/chat/adapter.d.ts +14 -0
  2. package/dist/esm/activities/chat/adapter.js.map +1 -1
  3. package/dist/esm/activities/chat/index.d.ts +16 -6
  4. package/dist/esm/activities/chat/index.js +235 -9
  5. package/dist/esm/activities/chat/index.js.map +1 -1
  6. package/dist/esm/activities/error-payload.d.ts +0 -8
  7. package/dist/esm/activities/error-payload.js +20 -2
  8. package/dist/esm/activities/error-payload.js.map +1 -1
  9. package/dist/esm/activities/generateImage/adapter.d.ts +2 -2
  10. package/dist/esm/activities/generateImage/adapter.js.map +1 -1
  11. package/dist/esm/activities/generateVideo/adapter.d.ts +2 -2
  12. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  13. package/dist/esm/activities/index.d.ts +1 -0
  14. package/dist/esm/activities/index.js +2 -0
  15. package/dist/esm/activities/index.js.map +1 -1
  16. package/dist/esm/activities/stream-generation-result.js +0 -2
  17. package/dist/esm/activities/stream-generation-result.js.map +1 -1
  18. package/dist/esm/activities/summarize/adapter.d.ts +4 -4
  19. package/dist/esm/activities/summarize/adapter.js.map +1 -1
  20. package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +45 -0
  21. package/dist/esm/activities/summarize/chat-stream-summarize.js +148 -0
  22. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -0
  23. package/dist/esm/activities/summarize/index.d.ts +1 -0
  24. package/dist/esm/activities/summarize/index.js +4 -2
  25. package/dist/esm/activities/summarize/index.js.map +1 -1
  26. package/dist/esm/types.d.ts +94 -3
  27. package/package.json +2 -2
  28. package/skills/ai-core/structured-outputs/SKILL.md +92 -1
  29. package/src/activities/chat/adapter.ts +17 -0
  30. package/src/activities/chat/index.ts +368 -26
  31. package/src/activities/error-payload.ts +31 -2
  32. package/src/activities/generateImage/adapter.ts +8 -2
  33. package/src/activities/generateVideo/adapter.ts +8 -2
  34. package/src/activities/index.ts +5 -0
  35. package/src/activities/stream-generation-result.ts +4 -6
  36. package/src/activities/summarize/adapter.ts +8 -4
  37. package/src/activities/summarize/chat-stream-summarize.ts +238 -0
  38. package/src/activities/summarize/index.ts +12 -9
  39. package/src/types.ts +107 -3
@@ -76,6 +76,20 @@ export interface TextAdapter<TModel extends string, TProviderOptions extends Rec
76
76
  * @returns Promise with the raw data (validation is done in the chat function)
77
77
  */
78
78
  structuredOutput: (options: StructuredOutputOptions<TProviderOptions>) => Promise<StructuredOutputResult<unknown>>;
79
+ /**
80
+ * Stream structured output using the provider's native streaming structured
81
+ * output API (stream + response_format json_schema in a single request).
82
+ *
83
+ * Optional — adapters without native streaming JSON omit this method and the
84
+ * activity layer synthesizes a stream around the non-streaming
85
+ * `structuredOutput` call.
86
+ *
87
+ * Implementations must emit standard AG-UI lifecycle events (RUN_STARTED,
88
+ * TEXT_MESSAGE_*, RUN_FINISHED) carrying raw JSON text deltas, plus a final
89
+ * `CUSTOM` event named `structured-output.complete` whose `value` is
90
+ * `{ object, raw, reasoning? }`.
91
+ */
92
+ structuredOutputStream?: (options: StructuredOutputOptions<TProviderOptions>) => AsyncIterable<StreamChunk>;
79
93
  }
80
94
  /**
81
95
  * A TextAdapter with any/unknown type parameters.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","sources":["../../../../src/activities/chat/adapter.ts"],"sourcesContent":["import type {\n DefaultMessageMetadataByModality,\n JSONSchema,\n Modality,\n StreamChunk,\n TextOptions,\n} from '../../types'\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}\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 */\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> {\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 * @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 }\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/**\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>\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> implements TextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadataByModality,\n TToolCapabilities,\n TToolCallMetadata\n> {\n readonly kind = 'text' as const\n abstract readonly name: string\n readonly model: TModel\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 }\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"],"names":[],"mappings":"AAoHO,MAAe,gBAcpB;AAAA,EAgBA,YAAY,SAA4B,CAAA,GAAI,OAAe;AAf3D,SAAS,OAAO;AAgBd,SAAK,SAAS;AACd,SAAK,QAAQ;AAAA,EACf;AAAA,EAcU,aAAqB;AAC7B,WAAO,GAAG,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AAAA,EAC9E;AACF;"}
1
+ {"version":3,"file":"adapter.js","sources":["../../../../src/activities/chat/adapter.ts"],"sourcesContent":["import type {\n DefaultMessageMetadataByModality,\n JSONSchema,\n Modality,\n StreamChunk,\n TextOptions,\n} from '../../types'\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}\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 */\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> {\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 * @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 }\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/**\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>\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> implements TextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadataByModality,\n TToolCapabilities,\n TToolCallMetadata\n> {\n readonly kind = 'text' as const\n abstract readonly name: string\n readonly model: TModel\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 }\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"],"names":[],"mappings":"AAqIO,MAAe,gBAcpB;AAAA,EAgBA,YAAY,SAA4B,CAAA,GAAI,OAAe;AAf3D,SAAS,OAAO;AAgBd,SAAK,SAAS;AACd,SAAK,QAAQ;AAAA,EACf;AAAA,EAcU,aAAqB;AAC7B,WAAO,GAAG,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AAAA,EAC9E;AACF;"}
@@ -1,5 +1,5 @@
1
1
  import { AnyTextAdapter } from './adapter.js';
2
- import { ConstrainedModelMessage, InferSchemaType, SchemaInput, StreamChunk, TextOptions, Tool } from '../../types.js';
2
+ import { ConstrainedModelMessage, InferSchemaType, SchemaInput, StreamChunk, StructuredOutputStream, TextOptions, Tool } from '../../types.js';
3
3
  import { ChatMiddleware } from './middleware/types.js';
4
4
  import { DebugOption } from '../../logger/types.js';
5
5
  import { ProviderTool } from '../../tools/provider-tool.js';
@@ -139,11 +139,21 @@ export interface TextActivityOptions<TAdapter extends AnyTextAdapter, TSchema ex
139
139
  export declare function createChatOptions<TAdapter extends AnyTextAdapter, TSchema extends SchemaInput | undefined = undefined, TStream extends boolean = true>(options: TextActivityOptions<TAdapter, TSchema, TStream>): TextActivityOptions<TAdapter, TSchema, TStream>;
140
140
  /**
141
141
  * Result type for the text activity.
142
- * - If outputSchema is provided: Promise<InferSchemaType<TSchema>>
143
- * - If stream is false: Promise<string>
144
- * - Otherwise (stream is true, default): AsyncIterable<StreamChunk>
142
+ * - If outputSchema is provided AND stream is explicitly true:
143
+ * StructuredOutputStream<InferSchemaType<TSchema>> — yields raw JSON deltas
144
+ * via TEXT_MESSAGE_CONTENT plus a terminal StructuredOutputCompleteEvent
145
+ * carrying the validated object.
146
+ * - If outputSchema is provided without explicit stream:true:
147
+ * Promise<InferSchemaType<TSchema>>.
148
+ * - If stream is explicitly false (no schema): Promise<string>.
149
+ * - Otherwise (default): AsyncIterable<StreamChunk>.
150
+ *
151
+ * `[TStream] extends [true]` is used (not `TStream extends true`) so that the
152
+ * default `boolean` value of `TStream` does *not* match the streaming branch.
153
+ * Without this, plain `chat({ outputSchema })` would type as a stream while
154
+ * the runtime returns a Promise — see issue #526.
145
155
  */
146
- export type TextActivityResult<TSchema extends SchemaInput | undefined, TStream extends boolean = true> = TSchema extends SchemaInput ? Promise<InferSchemaType<TSchema>> : TStream extends false ? Promise<string> : AsyncIterable<StreamChunk>;
156
+ export type TextActivityResult<TSchema extends SchemaInput | undefined, TStream extends boolean = boolean> = TSchema extends SchemaInput ? [TStream] extends [true] ? StructuredOutputStream<InferSchemaType<TSchema>> : Promise<InferSchemaType<TSchema>> : [TStream] extends [false] ? Promise<string> : AsyncIterable<StreamChunk>;
147
157
  /**
148
158
  * Text activity - handles agentic text generation, one-shot text generation, and agentic structured output.
149
159
  *
@@ -205,6 +215,6 @@ export type TextActivityResult<TSchema extends SchemaInput | undefined, TStream
205
215
  * // result is { summary: string, keyPoints: string[] }
206
216
  * ```
207
217
  */
208
- export declare function chat<TAdapter extends AnyTextAdapter, TSchema extends SchemaInput | undefined = undefined, TStream extends boolean = true>(options: TextActivityOptions<TAdapter, TSchema, TStream>): TextActivityResult<TSchema, TStream>;
218
+ export declare function chat<TAdapter extends AnyTextAdapter, TSchema extends SchemaInput | undefined = undefined, TStream extends boolean = boolean>(options: TextActivityOptions<TAdapter, TSchema, TStream>): TextActivityResult<TSchema, TStream>;
209
219
  export type { TextAdapter, TextAdapterConfig, StructuredOutputOptions, StructuredOutputResult, } from './adapter.js';
210
220
  export { BaseTextAdapter } from './adapter.js';
@@ -8,6 +8,7 @@ import { convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema }
8
8
  import { maxIterations } from "./agent-loop-strategies.js";
9
9
  import { convertMessagesToModelMessages } from "./messages.js";
10
10
  import { MiddlewareRunner } from "./middleware/compose.js";
11
+ import { EventType } from "@ag-ui/core";
11
12
  const kind = "text";
12
13
  function createChatOptions(options) {
13
14
  return options;
@@ -926,19 +927,31 @@ class TextEngine {
926
927
  }
927
928
  function chat(options) {
928
929
  const { outputSchema, stream } = options;
930
+ if (outputSchema && stream === true) {
931
+ return runStreamingStructuredOutput({
932
+ ...options,
933
+ outputSchema,
934
+ stream
935
+ });
936
+ }
929
937
  if (outputSchema) {
930
- return runAgenticStructuredOutput(
931
- options
932
- );
938
+ return runAgenticStructuredOutput({
939
+ ...options,
940
+ outputSchema
941
+ });
933
942
  }
934
943
  if (stream === false) {
935
- return runNonStreamingText(
936
- options
937
- );
944
+ return runNonStreamingText({
945
+ ...options,
946
+ outputSchema: void 0,
947
+ stream
948
+ });
938
949
  }
939
- return runStreamingText(
940
- options
941
- );
950
+ return runStreamingText({
951
+ ...options,
952
+ outputSchema: void 0,
953
+ stream
954
+ });
942
955
  }
943
956
  async function* runStreamingText(options) {
944
957
  const { adapter, middleware, context, debug, ...textOptions } = options;
@@ -1017,6 +1030,219 @@ async function runAgenticStructuredOutput(options) {
1017
1030
  }
1018
1031
  return result.data;
1019
1032
  }
1033
+ async function* fallbackStructuredOutputStream(adapter, options) {
1034
+ const { chatOptions } = options;
1035
+ const runId = chatOptions.runId ?? `mock-${Date.now()}`;
1036
+ const threadId = chatOptions.threadId ?? `mock-${Date.now()}`;
1037
+ const messageId = `mock-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1038
+ const model = chatOptions.model;
1039
+ const timestamp = Date.now();
1040
+ yield {
1041
+ type: EventType.RUN_STARTED,
1042
+ runId,
1043
+ threadId,
1044
+ model,
1045
+ timestamp
1046
+ };
1047
+ let result;
1048
+ try {
1049
+ result = await adapter.structuredOutput(options);
1050
+ } catch (error) {
1051
+ const message = error instanceof Error ? error.message : "Unknown error";
1052
+ yield {
1053
+ type: EventType.RUN_ERROR,
1054
+ runId,
1055
+ model,
1056
+ timestamp,
1057
+ message,
1058
+ error: { message }
1059
+ };
1060
+ return;
1061
+ }
1062
+ yield {
1063
+ type: EventType.TEXT_MESSAGE_START,
1064
+ messageId,
1065
+ role: "assistant",
1066
+ model,
1067
+ timestamp
1068
+ };
1069
+ yield {
1070
+ type: EventType.TEXT_MESSAGE_CONTENT,
1071
+ messageId,
1072
+ delta: result.rawText,
1073
+ model,
1074
+ timestamp
1075
+ };
1076
+ yield {
1077
+ type: EventType.TEXT_MESSAGE_END,
1078
+ messageId,
1079
+ model,
1080
+ timestamp
1081
+ };
1082
+ yield {
1083
+ type: EventType.CUSTOM,
1084
+ name: "structured-output.complete",
1085
+ value: { object: result.data, raw: result.rawText },
1086
+ model,
1087
+ timestamp
1088
+ };
1089
+ yield {
1090
+ type: EventType.RUN_FINISHED,
1091
+ runId,
1092
+ threadId,
1093
+ model,
1094
+ timestamp,
1095
+ finishReason: "stop"
1096
+ };
1097
+ }
1098
+ function runStreamingStructuredOutput(options) {
1099
+ const { outputSchema } = options;
1100
+ if (!outputSchema) {
1101
+ throw new Error("outputSchema is required for streaming structured output");
1102
+ }
1103
+ const jsonSchema = convertSchemaToJsonSchema(outputSchema, {
1104
+ forStructuredOutput: true
1105
+ });
1106
+ if (!jsonSchema) {
1107
+ throw new Error("Failed to convert output schema to JSON Schema");
1108
+ }
1109
+ return runStreamingStructuredOutputImpl(
1110
+ options,
1111
+ jsonSchema
1112
+ );
1113
+ }
1114
+ async function* runStreamingStructuredOutputImpl(options, jsonSchema) {
1115
+ const { adapter, outputSchema, middleware, context, debug, ...textOptions } = options;
1116
+ const model = adapter.model;
1117
+ const logger = resolveDebugOption(debug);
1118
+ const runId = textOptions.runId;
1119
+ let finalMessages = convertMessagesToModelMessages(textOptions.messages ?? []);
1120
+ if (textOptions.tools?.length) {
1121
+ const engine = new TextEngine(
1122
+ {
1123
+ adapter,
1124
+ params: { ...textOptions, model, logger, messages: finalMessages },
1125
+ middleware,
1126
+ context
1127
+ },
1128
+ logger
1129
+ );
1130
+ let agentLoopErrored = false;
1131
+ try {
1132
+ for await (const chunk of engine.run()) {
1133
+ if (chunk.type === "RUN_STARTED" || chunk.type === "RUN_FINISHED") {
1134
+ continue;
1135
+ }
1136
+ if (chunk.type === "RUN_ERROR") {
1137
+ agentLoopErrored = true;
1138
+ yield chunk;
1139
+ continue;
1140
+ }
1141
+ yield chunk;
1142
+ }
1143
+ } catch (engineError) {
1144
+ const message = engineError.message || "Agent loop failed";
1145
+ logger.errors("runStreamingStructuredOutput agent loop failed", {
1146
+ error: engineError,
1147
+ source: "runStreamingStructuredOutput"
1148
+ });
1149
+ yield {
1150
+ type: EventType.RUN_ERROR,
1151
+ runId,
1152
+ model,
1153
+ timestamp: Date.now(),
1154
+ message,
1155
+ code: "agent-loop-failed",
1156
+ error: { message, code: "agent-loop-failed" }
1157
+ };
1158
+ return;
1159
+ }
1160
+ if (agentLoopErrored) {
1161
+ return;
1162
+ }
1163
+ finalMessages = engine.getMessages();
1164
+ }
1165
+ const {
1166
+ tools: _tools,
1167
+ agentLoopStrategy: _als,
1168
+ ...structuredTextOptions
1169
+ } = textOptions;
1170
+ logger.request(
1171
+ `activity=chat-structured-stream provider=${adapter.name} model=${model} messages=${finalMessages.length}`,
1172
+ {
1173
+ provider: adapter.name,
1174
+ model,
1175
+ messageCount: finalMessages.length
1176
+ }
1177
+ );
1178
+ const structuredChatOptions = {
1179
+ ...structuredTextOptions,
1180
+ model,
1181
+ messages: finalMessages,
1182
+ logger,
1183
+ request: textOptions.abortController ? { signal: textOptions.abortController.signal } : void 0
1184
+ };
1185
+ const stream = adapter.structuredOutputStream ? adapter.structuredOutputStream({
1186
+ chatOptions: structuredChatOptions,
1187
+ outputSchema: jsonSchema
1188
+ }) : fallbackStructuredOutputStream(adapter, {
1189
+ chatOptions: structuredChatOptions,
1190
+ outputSchema: jsonSchema
1191
+ });
1192
+ for await (const chunk of stream) {
1193
+ if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
1194
+ const value = chunk.value;
1195
+ if (isStandardSchema(outputSchema)) {
1196
+ try {
1197
+ const validated = parseWithStandardSchema(
1198
+ outputSchema,
1199
+ value.object
1200
+ );
1201
+ yield {
1202
+ ...chunk,
1203
+ // Forward `reasoning` through schema validation so consumers that
1204
+ // only listen for the terminal event don't lose chain-of-thought.
1205
+ value: {
1206
+ object: validated,
1207
+ raw: value.raw,
1208
+ ...value.reasoning ? { reasoning: value.reasoning } : {}
1209
+ }
1210
+ };
1211
+ continue;
1212
+ } catch (err) {
1213
+ const message = err.message || "Schema validation failed";
1214
+ logger.errors(
1215
+ "runStreamingStructuredOutput schema validation failed",
1216
+ {
1217
+ error: err,
1218
+ source: "runStreamingStructuredOutput",
1219
+ // Include reasoning in error meta so post-mortems can recover
1220
+ // what the model thought through before producing invalid JSON.
1221
+ ...value.reasoning ? { reasoning: value.reasoning } : {}
1222
+ }
1223
+ );
1224
+ yield {
1225
+ type: EventType.RUN_ERROR,
1226
+ runId,
1227
+ model: chunk.model ?? model,
1228
+ timestamp: chunk.timestamp ?? Date.now(),
1229
+ message,
1230
+ code: "schema-validation",
1231
+ error: {
1232
+ message,
1233
+ code: "schema-validation",
1234
+ ...value.reasoning ? { reasoning: value.reasoning } : {}
1235
+ }
1236
+ };
1237
+ return;
1238
+ }
1239
+ }
1240
+ yield chunk;
1241
+ continue;
1242
+ }
1243
+ yield chunk;
1244
+ }
1245
+ }
1020
1246
  export {
1021
1247
  chat,
1022
1248
  createChatOptions,