@tanstack/ai 0.20.1 → 0.21.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 (40) hide show
  1. package/dist/esm/activities/chat/index.d.ts +1 -1
  2. package/dist/esm/activities/chat/index.js +357 -258
  3. package/dist/esm/activities/chat/index.js.map +1 -1
  4. package/dist/esm/activities/chat/messages.js.map +1 -1
  5. package/dist/esm/activities/chat/middleware/compose.d.ts +10 -1
  6. package/dist/esm/activities/chat/middleware/compose.js +55 -0
  7. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  8. package/dist/esm/activities/chat/middleware/index.d.ts +1 -1
  9. package/dist/esm/activities/chat/middleware/types.d.ts +35 -3
  10. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  11. package/dist/esm/activities/chat/tools/schema-converter.d.ts +12 -1
  12. package/dist/esm/activities/chat/tools/schema-converter.js +13 -4
  13. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  14. package/dist/esm/activities/generateImage/index.js.map +1 -1
  15. package/dist/esm/extend-adapter.js.map +1 -1
  16. package/dist/esm/index.d.ts +2 -2
  17. package/dist/esm/index.js +2 -1
  18. package/dist/esm/middlewares/content-guard.js.map +1 -1
  19. package/dist/esm/middlewares/otel.js +1 -4
  20. package/dist/esm/middlewares/otel.js.map +1 -1
  21. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  22. package/dist/esm/utilities/ag-ui-wire.js +4 -1
  23. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  24. package/package.json +2 -2
  25. package/skills/ai-core/middleware/SKILL.md +124 -18
  26. package/skills/ai-core/structured-outputs/SKILL.md +13 -0
  27. package/src/activities/chat/index.ts +653 -370
  28. package/src/activities/chat/messages.ts +1 -1
  29. package/src/activities/chat/middleware/compose.ts +65 -5
  30. package/src/activities/chat/middleware/index.ts +1 -0
  31. package/src/activities/chat/middleware/types.ts +53 -2
  32. package/src/activities/chat/stream/processor.ts +2 -2
  33. package/src/activities/chat/tools/schema-converter.ts +22 -7
  34. package/src/activities/generateImage/index.ts +3 -3
  35. package/src/extend-adapter.ts +1 -1
  36. package/src/index.ts +5 -1
  37. package/src/middlewares/content-guard.ts +1 -1
  38. package/src/middlewares/otel.ts +7 -10
  39. package/src/strip-to-spec-middleware.ts +1 -4
  40. package/src/utilities/ag-ui-wire.ts +2 -1
@@ -57,9 +57,19 @@ class TextEngine {
57
57
  middlewareAbortController;
58
58
  terminalHookCalled = false;
59
59
  logger;
60
+ // Structured-output finalization state (populated by runStructuredFinalization)
61
+ structuredOutputResult = null;
62
+ // Holds the validated value when `finalStructuredOutput.validate` is provided
63
+ // and succeeds. Distinct from `structuredOutputResult.data` (the raw,
64
+ // unvalidated payload from the structured-output.complete chunk).
65
+ validatedStructuredOutput = void 0;
66
+ hasValidatedStructuredOutput = false;
67
+ finalizationError = null;
68
+ finalStructuredOutput;
60
69
  constructor(config, logger) {
61
70
  this.logger = logger;
62
71
  this.adapter = config.adapter;
72
+ this.finalStructuredOutput = config.finalStructuredOutput;
63
73
  this.params = config.params;
64
74
  this.systemPrompts = config.params.systemPrompts || [];
65
75
  this.loopStrategy = config.params.agentLoopStrategy || maxIterations(5);
@@ -69,9 +79,7 @@ class TextEngine {
69
79
  );
70
80
  this.initialApprovals = approvals;
71
81
  this.initialClientToolResults = clientToolResults;
72
- this.messages = convertMessagesToModelMessages(
73
- config.params.messages
74
- );
82
+ this.messages = convertMessagesToModelMessages(config.params.messages);
75
83
  this.lazyToolManager = new LazyToolManager(
76
84
  config.params.tools || [],
77
85
  this.messages
@@ -140,6 +148,24 @@ class TextEngine {
140
148
  getMessages() {
141
149
  return this.messages;
142
150
  }
151
+ /** Returns the structured-output result if finalization ran successfully. */
152
+ getStructuredOutputResult() {
153
+ return this.structuredOutputResult;
154
+ }
155
+ /**
156
+ * Returns the validated structured-output value (the result of running
157
+ * `finalStructuredOutput.validate` against the raw structured-output data)
158
+ * wrapped in a `{ value }` object so callers can distinguish "no validation
159
+ * happened" from "validation produced undefined". Returns `null` when no
160
+ * validator was configured or validation hasn't been performed yet.
161
+ */
162
+ getValidatedStructuredOutput() {
163
+ return this.hasValidatedStructuredOutput ? { value: this.validatedStructuredOutput } : null;
164
+ }
165
+ /** Returns the recorded finalization error, if any. */
166
+ getFinalizationError() {
167
+ return this.finalizationError;
168
+ }
143
169
  async *run() {
144
170
  this.beforeRun();
145
171
  this.logger.agentLoop("run started", {
@@ -158,40 +184,64 @@ class TextEngine {
158
184
  if (pendingPhase === "wait") {
159
185
  return;
160
186
  }
161
- do {
162
- if (this.earlyTermination || this.isCancelled()) {
163
- return;
164
- }
165
- this.logger.agentLoop(`iteration=${this.middlewareCtx.iteration}`, {
166
- iteration: this.middlewareCtx.iteration
167
- });
168
- await this.beginCycle();
169
- if (this.cyclePhase === "processText") {
170
- this.middlewareCtx.phase = "beforeModel";
171
- this.middlewareCtx.iteration = this.iterationCount;
172
- const iterConfig = this.buildMiddlewareConfig();
173
- const transformedConfig2 = await this.middlewareRunner.runOnConfig(
174
- this.middlewareCtx,
175
- iterConfig
176
- );
177
- this.applyMiddlewareConfig(transformedConfig2);
178
- yield* this.streamModelResponse();
179
- } else {
180
- yield* this.processToolCalls();
181
- }
182
- this.endCycle();
183
- } while (this.shouldContinue());
187
+ const skipAgentLoop = !!this.finalStructuredOutput && this.tools.length === 0;
188
+ if (!skipAgentLoop) {
189
+ do {
190
+ if (this.earlyTermination || this.isCancelled()) {
191
+ return;
192
+ }
193
+ this.logger.agentLoop(`iteration=${this.middlewareCtx.iteration}`, {
194
+ iteration: this.middlewareCtx.iteration
195
+ });
196
+ await this.beginCycle();
197
+ if (this.cyclePhase === "processText") {
198
+ this.middlewareCtx.phase = "beforeModel";
199
+ this.middlewareCtx.iteration = this.iterationCount;
200
+ const iterConfig = this.buildMiddlewareConfig();
201
+ const transformedConfig2 = await this.middlewareRunner.runOnConfig(
202
+ this.middlewareCtx,
203
+ iterConfig
204
+ );
205
+ this.applyMiddlewareConfig(transformedConfig2);
206
+ yield* this.streamModelResponse();
207
+ } else {
208
+ yield* this.processToolCalls();
209
+ }
210
+ this.endCycle();
211
+ } while (this.shouldContinue());
212
+ }
184
213
  this.logger.agentLoop("run finished", {
185
214
  finishReason: this.lastFinishReason
186
215
  });
187
- if (!this.terminalHookCalled && this.toolPhase !== "wait") {
188
- this.terminalHookCalled = true;
189
- await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
190
- finishReason: this.lastFinishReason,
191
- duration: Date.now() - this.streamStartTime,
192
- content: this.accumulatedContent,
193
- usage: this.finishedEvent?.usage
194
- });
216
+ if (this.finalStructuredOutput && !this.isCancelled() && !this.finalizationError) {
217
+ yield* this.runStructuredFinalization();
218
+ }
219
+ if (!this.terminalHookCalled && this.toolPhase !== "wait" && !this.isCancelled()) {
220
+ if (this.finalizationError) {
221
+ this.terminalHookCalled = true;
222
+ const errForHook = new Error(
223
+ this.finalizationError.message,
224
+ this.finalizationError.cause !== void 0 ? { cause: this.finalizationError.cause } : void 0
225
+ );
226
+ if (this.finalizationError.code !== void 0) {
227
+ Object.defineProperty(errForHook, "code", {
228
+ value: this.finalizationError.code,
229
+ enumerable: true
230
+ });
231
+ }
232
+ await this.middlewareRunner.runOnError(this.middlewareCtx, {
233
+ error: errForHook,
234
+ duration: Date.now() - this.streamStartTime
235
+ });
236
+ } else {
237
+ this.terminalHookCalled = true;
238
+ await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
239
+ finishReason: this.lastFinishReason,
240
+ duration: Date.now() - this.streamStartTime,
241
+ content: this.accumulatedContent,
242
+ usage: this.finishedEvent?.usage
243
+ });
244
+ }
195
245
  }
196
246
  } catch (error) {
197
247
  if (!this.terminalHookCalled) {
@@ -313,7 +363,11 @@ class TextEngine {
313
363
  this.middlewareCtx,
314
364
  chunk
315
365
  );
366
+ const suppressAgentLifecycle = !!this.finalStructuredOutput && this.finalStructuredOutput.yieldChunks;
316
367
  for (const outputChunk of outputChunks) {
368
+ if (suppressAgentLifecycle && (outputChunk.type === EventType.RUN_STARTED || outputChunk.type === EventType.RUN_FINISHED)) {
369
+ continue;
370
+ }
317
371
  this.logger.output(`type=${outputChunk.type}`, { chunk: outputChunk });
318
372
  yield outputChunk;
319
373
  this.middlewareCtx.chunkIndex++;
@@ -885,6 +939,212 @@ class TextEngine {
885
939
  isCancelled() {
886
940
  return this.isAborted() || this.isMiddlewareAborted();
887
941
  }
942
+ /**
943
+ * Run the final structured-output adapter call through the middleware
944
+ * pipeline. Yields chunks to the caller only when
945
+ * `this.finalStructuredOutput.yieldChunks` is true; otherwise consumes
946
+ * silently while still piping through middleware.
947
+ *
948
+ * On success, populates this.structuredOutputResult.
949
+ * On failure, populates this.finalizationError.
950
+ */
951
+ async *runStructuredFinalization() {
952
+ if (!this.finalStructuredOutput) {
953
+ throw new Error(
954
+ "runStructuredFinalization called without finalStructuredOutput config"
955
+ );
956
+ }
957
+ this.middlewareCtx.phase = "structuredOutput";
958
+ const baseConfig = this.buildMiddlewareConfig();
959
+ const { tools: _omitTools, ...baseWithoutTools } = baseConfig;
960
+ let structuredConfig = {
961
+ ...baseWithoutTools,
962
+ outputSchema: this.finalStructuredOutput.jsonSchema
963
+ };
964
+ structuredConfig = await this.middlewareRunner.runOnStructuredOutputConfig(
965
+ this.middlewareCtx,
966
+ structuredConfig
967
+ );
968
+ const { outputSchema: pinnedSchema, ...chatConfigSlice } = structuredConfig;
969
+ const postOnConfig = await this.middlewareRunner.runOnConfig(
970
+ this.middlewareCtx,
971
+ { ...chatConfigSlice, tools: baseConfig.tools }
972
+ );
973
+ this.applyMiddlewareConfig(postOnConfig);
974
+ const structuredCallOptions = {
975
+ chatOptions: {
976
+ model: this.params.model,
977
+ messages: this.messages,
978
+ temperature: postOnConfig.temperature,
979
+ topP: postOnConfig.topP,
980
+ maxTokens: postOnConfig.maxTokens,
981
+ metadata: postOnConfig.metadata,
982
+ modelOptions: postOnConfig.modelOptions,
983
+ systemPrompts: postOnConfig.systemPrompts,
984
+ logger: this.logger,
985
+ threadId: this.threadId,
986
+ runId: this.runIdOverride,
987
+ parentRunId: this.parentRunIdOverride,
988
+ ...this.effectiveRequest ? { request: this.effectiveRequest } : {}
989
+ },
990
+ outputSchema: pinnedSchema
991
+ };
992
+ let fallbackAdapterError = void 0;
993
+ const providerStream = this.adapter.structuredOutputStream ? this.adapter.structuredOutputStream(structuredCallOptions) : fallbackStructuredOutputStream(
994
+ this.adapter,
995
+ structuredCallOptions,
996
+ (err) => {
997
+ fallbackAdapterError = err;
998
+ }
999
+ );
1000
+ let startEmitted = false;
1001
+ let structuredMessageId = null;
1002
+ const extractMessageId = (c) => {
1003
+ if (c.type === EventType.TEXT_MESSAGE_START || c.type === EventType.TEXT_MESSAGE_CONTENT || c.type === EventType.TEXT_MESSAGE_END) {
1004
+ return typeof c.messageId === "string" && c.messageId !== "" ? c.messageId : null;
1005
+ }
1006
+ return null;
1007
+ };
1008
+ const buildSynthesizedStart = () => {
1009
+ const idForStart = structuredMessageId ?? generateMessageId();
1010
+ structuredMessageId = idForStart;
1011
+ return {
1012
+ type: EventType.CUSTOM,
1013
+ name: "structured-output.start",
1014
+ value: { messageId: idForStart },
1015
+ model: this.params.model,
1016
+ timestamp: Date.now(),
1017
+ threadId: this.threadId,
1018
+ ...this.runIdOverride ? { runId: this.runIdOverride } : {}
1019
+ };
1020
+ };
1021
+ const pipeThroughMiddleware = async (synthChunk) => this.middlewareRunner.runOnChunk(this.middlewareCtx, synthChunk);
1022
+ let runErrorYielded = false;
1023
+ for await (const chunk of providerStream) {
1024
+ if (this.isCancelled()) {
1025
+ break;
1026
+ }
1027
+ if (!startEmitted && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") {
1028
+ startEmitted = true;
1029
+ }
1030
+ if (!structuredMessageId) {
1031
+ const extracted = extractMessageId(chunk);
1032
+ if (extracted) structuredMessageId = extracted;
1033
+ }
1034
+ if (this.finalStructuredOutput.yieldChunks) {
1035
+ if (!startEmitted && (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT || chunk.type === EventType.TEXT_MESSAGE_END)) {
1036
+ startEmitted = true;
1037
+ const synthStart = buildSynthesizedStart();
1038
+ const synthOutputs = await pipeThroughMiddleware(synthStart);
1039
+ for (const outputChunk of synthOutputs) {
1040
+ yield outputChunk;
1041
+ this.middlewareCtx.chunkIndex++;
1042
+ }
1043
+ }
1044
+ if (!startEmitted && chunk.type === EventType.RUN_ERROR) {
1045
+ startEmitted = true;
1046
+ const synthStart = buildSynthesizedStart();
1047
+ const synthOutputs = await pipeThroughMiddleware(synthStart);
1048
+ for (const outputChunk of synthOutputs) {
1049
+ yield outputChunk;
1050
+ this.middlewareCtx.chunkIndex++;
1051
+ }
1052
+ }
1053
+ }
1054
+ if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
1055
+ const parsed = readStructuredOutputCompleteValue(chunk.value);
1056
+ if (parsed) {
1057
+ this.structuredOutputResult = {
1058
+ data: parsed.object,
1059
+ rawText: parsed.raw
1060
+ };
1061
+ }
1062
+ }
1063
+ if (chunk.type === EventType.RUN_FINISHED && chunk.usage) {
1064
+ await this.middlewareRunner.runOnUsage(this.middlewareCtx, chunk.usage);
1065
+ }
1066
+ if (chunk.type === EventType.RUN_ERROR) {
1067
+ this.finalizationError = {
1068
+ message: chunk.message,
1069
+ ...chunk.code ? { code: chunk.code } : {},
1070
+ ...fallbackAdapterError !== void 0 ? { cause: fallbackAdapterError } : {}
1071
+ };
1072
+ }
1073
+ const outputChunks = await this.middlewareRunner.runOnChunk(
1074
+ this.middlewareCtx,
1075
+ chunk
1076
+ );
1077
+ if (this.finalStructuredOutput.yieldChunks) {
1078
+ for (const outputChunk of outputChunks) {
1079
+ if (outputChunk.type === EventType.RUN_ERROR) {
1080
+ runErrorYielded = true;
1081
+ }
1082
+ yield outputChunk;
1083
+ this.middlewareCtx.chunkIndex++;
1084
+ }
1085
+ }
1086
+ if (this.finalizationError) {
1087
+ break;
1088
+ }
1089
+ }
1090
+ if (this.isCancelled()) {
1091
+ return;
1092
+ }
1093
+ if (!this.structuredOutputResult && !this.finalizationError) {
1094
+ this.finalizationError = {
1095
+ message: "missing structured result",
1096
+ code: "structured-output-missing-result"
1097
+ };
1098
+ }
1099
+ if (this.structuredOutputResult && !this.finalizationError && this.finalStructuredOutput.validate) {
1100
+ try {
1101
+ const validated = this.finalStructuredOutput.validate(
1102
+ this.structuredOutputResult.data
1103
+ );
1104
+ this.validatedStructuredOutput = validated;
1105
+ this.hasValidatedStructuredOutput = true;
1106
+ } catch (err) {
1107
+ const message = err instanceof Error ? err.message : String(err);
1108
+ this.finalizationError = {
1109
+ message,
1110
+ code: "structured-output-validation-failed",
1111
+ cause: err
1112
+ };
1113
+ }
1114
+ }
1115
+ if (this.finalizationError && this.finalStructuredOutput.yieldChunks && !runErrorYielded) {
1116
+ if (!startEmitted) {
1117
+ const synthStart = buildSynthesizedStart();
1118
+ const startOutputs = await pipeThroughMiddleware(synthStart);
1119
+ for (const outputChunk of startOutputs) {
1120
+ yield outputChunk;
1121
+ this.middlewareCtx.chunkIndex++;
1122
+ }
1123
+ startEmitted = true;
1124
+ }
1125
+ const errChunk = {
1126
+ type: EventType.RUN_ERROR,
1127
+ runId: this.runIdOverride ?? this.requestId,
1128
+ model: this.params.model,
1129
+ timestamp: Date.now(),
1130
+ threadId: this.threadId,
1131
+ message: this.finalizationError.message,
1132
+ ...this.finalizationError.code ? { code: this.finalizationError.code } : {},
1133
+ error: {
1134
+ message: this.finalizationError.message,
1135
+ ...this.finalizationError.code ? { code: this.finalizationError.code } : {}
1136
+ }
1137
+ };
1138
+ const outputChunks = await this.middlewareRunner.runOnChunk(
1139
+ this.middlewareCtx,
1140
+ errChunk
1141
+ );
1142
+ for (const outputChunk of outputChunks) {
1143
+ yield outputChunk;
1144
+ this.middlewareCtx.chunkIndex++;
1145
+ }
1146
+ }
1147
+ }
888
1148
  buildMiddlewareConfig() {
889
1149
  return {
890
1150
  messages: this.messages,
@@ -1016,58 +1276,72 @@ async function runAgenticStructuredOutput(options) {
1016
1276
  if (!outputSchema) {
1017
1277
  throw new Error("outputSchema is required for structured output");
1018
1278
  }
1279
+ const jsonSchema = convertSchemaToJsonSchema(outputSchema, {
1280
+ forStructuredOutput: true
1281
+ });
1282
+ if (!jsonSchema) {
1283
+ throw new Error("Failed to convert output schema to JSON Schema");
1284
+ }
1285
+ const validate = isStandardSchema(outputSchema) ? (data) => parseWithStandardSchema(outputSchema, data) : void 0;
1019
1286
  const engine = new TextEngine(
1020
1287
  {
1021
1288
  adapter,
1022
1289
  params: { ...textOptions, model, logger },
1023
1290
  middleware,
1024
- context
1291
+ context,
1292
+ finalStructuredOutput: {
1293
+ jsonSchema,
1294
+ yieldChunks: false,
1295
+ ...validate ? { validate } : {}
1296
+ }
1025
1297
  },
1026
1298
  logger
1027
1299
  );
1028
1300
  for await (const _chunk of engine.run()) {
1029
1301
  }
1030
- const finalMessages = engine.getMessages();
1031
- const {
1032
- tools: _tools,
1033
- agentLoopStrategy: _als,
1034
- ...structuredTextOptions
1035
- } = textOptions;
1036
- const jsonSchema = convertSchemaToJsonSchema(outputSchema);
1037
- if (!jsonSchema) {
1038
- throw new Error("Failed to convert output schema to JSON Schema");
1039
- }
1040
- const providerName = adapter.provider ?? adapter.name;
1041
- logger.request(
1042
- `activity=chat-structured provider=${providerName} model=${model} messages=${finalMessages.length}`,
1043
- {
1044
- provider: providerName,
1045
- model,
1046
- messageCount: finalMessages.length
1047
- }
1048
- );
1049
- const result = await adapter.structuredOutput({
1050
- chatOptions: {
1051
- ...structuredTextOptions,
1052
- model,
1053
- messages: finalMessages,
1054
- logger
1055
- },
1056
- outputSchema: jsonSchema
1057
- });
1058
- if (isStandardSchema(outputSchema)) {
1059
- return parseWithStandardSchema(
1060
- outputSchema,
1061
- result.data
1302
+ const finalizationError = engine.getFinalizationError();
1303
+ if (finalizationError) {
1304
+ const err = new Error(
1305
+ finalizationError.message,
1306
+ finalizationError.cause !== void 0 ? { cause: finalizationError.cause } : void 0
1062
1307
  );
1308
+ if (finalizationError.code !== void 0) {
1309
+ Object.defineProperty(err, "code", {
1310
+ value: finalizationError.code,
1311
+ enumerable: true
1312
+ });
1313
+ }
1314
+ throw err;
1315
+ }
1316
+ const validated = engine.getValidatedStructuredOutput();
1317
+ if (validated) {
1318
+ return validated.value;
1319
+ }
1320
+ const result = engine.getStructuredOutputResult();
1321
+ if (!result) {
1322
+ throw new Error("structured output finalization produced no result");
1063
1323
  }
1064
1324
  return result.data;
1065
1325
  }
1066
- async function* fallbackStructuredOutputStream(adapter, options) {
1326
+ function readStructuredOutputCompleteValue(value) {
1327
+ if (typeof value !== "object" || value === null) return null;
1328
+ if (!("object" in value) || !("raw" in value)) return null;
1329
+ const raw = value.raw;
1330
+ if (typeof raw !== "string") return null;
1331
+ const reasoningField = value.reasoning;
1332
+ const reasoning = typeof reasoningField === "string" ? reasoningField : void 0;
1333
+ return {
1334
+ object: value.object,
1335
+ raw,
1336
+ ...reasoning !== void 0 ? { reasoning } : {}
1337
+ };
1338
+ }
1339
+ async function* fallbackStructuredOutputStream(adapter, options, onAdapterError) {
1067
1340
  const { chatOptions } = options;
1068
- const runId = chatOptions.runId ?? `mock-${Date.now()}`;
1069
- const threadId = chatOptions.threadId ?? `mock-${Date.now()}`;
1070
- const messageId = `mock-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1341
+ const fallbackRand = Math.random().toString(36).slice(2);
1342
+ const runId = chatOptions.runId ?? `fallback-${Date.now()}-${fallbackRand}`;
1343
+ const threadId = chatOptions.threadId ?? `fallback-${Date.now()}-${fallbackRand}`;
1344
+ const messageId = `fallback-${Date.now()}-${fallbackRand}`;
1071
1345
  const model = chatOptions.model;
1072
1346
  const timestamp = Date.now();
1073
1347
  yield {
@@ -1081,10 +1355,12 @@ async function* fallbackStructuredOutputStream(adapter, options) {
1081
1355
  try {
1082
1356
  result = await adapter.structuredOutput(options);
1083
1357
  } catch (error) {
1084
- const message = error instanceof Error ? error.message : "Unknown error";
1358
+ onAdapterError?.(error);
1359
+ const message = error instanceof Error ? error.message : String(error);
1085
1360
  yield {
1086
1361
  type: EventType.RUN_ERROR,
1087
1362
  runId,
1363
+ threadId,
1088
1364
  model,
1089
1365
  timestamp,
1090
1366
  message,
@@ -1148,194 +1424,17 @@ async function* runStreamingStructuredOutputImpl(options, jsonSchema) {
1148
1424
  const { adapter, outputSchema, middleware, context, debug, ...textOptions } = options;
1149
1425
  const model = adapter.model;
1150
1426
  const logger = resolveDebugOption(debug);
1151
- const runId = textOptions.runId;
1152
- let finalMessages = convertMessagesToModelMessages(textOptions.messages ?? []);
1153
- if (textOptions.tools?.length) {
1154
- const engine = new TextEngine(
1155
- {
1156
- adapter,
1157
- params: { ...textOptions, model, logger, messages: finalMessages },
1158
- middleware,
1159
- context
1160
- },
1161
- logger
1162
- );
1163
- let agentLoopErrored = false;
1164
- try {
1165
- for await (const chunk of engine.run()) {
1166
- if (chunk.type === "RUN_STARTED" || chunk.type === "RUN_FINISHED") {
1167
- continue;
1168
- }
1169
- if (chunk.type === "RUN_ERROR") {
1170
- agentLoopErrored = true;
1171
- yield chunk;
1172
- continue;
1173
- }
1174
- yield chunk;
1175
- }
1176
- } catch (engineError) {
1177
- const message = engineError.message || "Agent loop failed";
1178
- logger.errors("runStreamingStructuredOutput agent loop failed", {
1179
- error: engineError,
1180
- source: "runStreamingStructuredOutput"
1181
- });
1182
- yield {
1183
- type: EventType.RUN_ERROR,
1184
- runId,
1185
- model,
1186
- timestamp: Date.now(),
1187
- message,
1188
- code: "agent-loop-failed",
1189
- error: { message, code: "agent-loop-failed" }
1190
- };
1191
- return;
1192
- }
1193
- if (agentLoopErrored) {
1194
- return;
1195
- }
1196
- finalMessages = engine.getMessages();
1197
- }
1198
- const {
1199
- tools: _tools,
1200
- agentLoopStrategy: _als,
1201
- ...structuredTextOptions
1202
- } = textOptions;
1203
- logger.request(
1204
- `activity=chat-structured-stream provider=${adapter.name} model=${model} messages=${finalMessages.length}`,
1427
+ const engine = new TextEngine(
1205
1428
  {
1206
- provider: adapter.name,
1207
- model,
1208
- messageCount: finalMessages.length
1209
- }
1429
+ adapter,
1430
+ params: { ...textOptions, model, logger },
1431
+ middleware,
1432
+ context,
1433
+ finalStructuredOutput: { jsonSchema, yieldChunks: true }
1434
+ },
1435
+ logger
1210
1436
  );
1211
- const structuredChatOptions = {
1212
- ...structuredTextOptions,
1213
- model,
1214
- messages: finalMessages,
1215
- logger,
1216
- request: textOptions.abortController ? { signal: textOptions.abortController.signal } : void 0
1217
- };
1218
- const stream = adapter.structuredOutputStream ? adapter.structuredOutputStream({
1219
- chatOptions: structuredChatOptions,
1220
- outputSchema: jsonSchema
1221
- }) : fallbackStructuredOutputStream(adapter, {
1222
- chatOptions: structuredChatOptions,
1223
- outputSchema: jsonSchema
1224
- });
1225
- let structuredMessageId = null;
1226
- let startEmitted = false;
1227
- const extractMessageId = (c) => {
1228
- const id = c.messageId;
1229
- return typeof id === "string" && id !== "" ? id : null;
1230
- };
1231
- const emitStartIfNeeded = function* (referenceChunk) {
1232
- if (startEmitted) return;
1233
- const idForStart = structuredMessageId ?? generateMessageId();
1234
- structuredMessageId = idForStart;
1235
- startEmitted = true;
1236
- yield {
1237
- type: EventType.CUSTOM,
1238
- name: "structured-output.start",
1239
- value: { messageId: idForStart },
1240
- model: "model" in referenceChunk ? referenceChunk.model ?? model : model,
1241
- timestamp: "timestamp" in referenceChunk ? referenceChunk.timestamp ?? Date.now() : Date.now(),
1242
- runId
1243
- };
1244
- };
1245
- for await (const chunk of stream) {
1246
- if (!structuredMessageId) {
1247
- if (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT) {
1248
- structuredMessageId = extractMessageId(chunk);
1249
- }
1250
- }
1251
- if (chunk.type === EventType.RUN_ERROR && !startEmitted) {
1252
- yield* emitStartIfNeeded(chunk);
1253
- }
1254
- if (!structuredMessageId && chunk.type === EventType.TEXT_MESSAGE_CONTENT) {
1255
- yield {
1256
- type: EventType.RUN_ERROR,
1257
- runId,
1258
- model,
1259
- timestamp: Date.now(),
1260
- message: "Structured-output stream produced text content without a messageId; adapter is not honoring the AG-UI contract.",
1261
- code: "structured-output-missing-message-id"
1262
- };
1263
- return;
1264
- }
1265
- if (!startEmitted && structuredMessageId && (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT)) {
1266
- startEmitted = true;
1267
- yield {
1268
- type: EventType.CUSTOM,
1269
- name: "structured-output.start",
1270
- value: { messageId: structuredMessageId },
1271
- model: "model" in chunk ? chunk.model ?? model : model,
1272
- timestamp: "timestamp" in chunk ? chunk.timestamp ?? Date.now() : Date.now(),
1273
- runId
1274
- };
1275
- }
1276
- if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
1277
- const value = chunk.value;
1278
- if (isStandardSchema(outputSchema)) {
1279
- try {
1280
- const validated = parseWithStandardSchema(
1281
- outputSchema,
1282
- value.object
1283
- );
1284
- yield {
1285
- ...chunk,
1286
- // Forward `reasoning` through schema validation so consumers that
1287
- // only listen for the terminal event don't lose chain-of-thought.
1288
- // Tag with messageId so the client processor can snap the right
1289
- // assistant message's structured-output part.
1290
- value: {
1291
- object: validated,
1292
- raw: value.raw,
1293
- ...value.reasoning ? { reasoning: value.reasoning } : {},
1294
- ...structuredMessageId ? { messageId: structuredMessageId } : {}
1295
- }
1296
- };
1297
- continue;
1298
- } catch (err) {
1299
- const message = err.message || "Schema validation failed";
1300
- logger.errors(
1301
- "runStreamingStructuredOutput schema validation failed",
1302
- {
1303
- error: err,
1304
- source: "runStreamingStructuredOutput",
1305
- // Include reasoning in error meta so post-mortems can recover
1306
- // what the model thought through before producing invalid JSON.
1307
- ...value.reasoning ? { reasoning: value.reasoning } : {}
1308
- }
1309
- );
1310
- yield {
1311
- type: EventType.RUN_ERROR,
1312
- runId,
1313
- model: chunk.model ?? model,
1314
- timestamp: chunk.timestamp ?? Date.now(),
1315
- message,
1316
- code: "schema-validation",
1317
- error: {
1318
- message,
1319
- code: "schema-validation",
1320
- ...value.reasoning ? { reasoning: value.reasoning } : {}
1321
- }
1322
- };
1323
- return;
1324
- }
1325
- }
1326
- if (structuredMessageId) {
1327
- yield {
1328
- ...chunk,
1329
- value: {
1330
- ...chunk.value,
1331
- messageId: structuredMessageId
1332
- }
1333
- };
1334
- continue;
1335
- }
1336
- yield chunk;
1337
- continue;
1338
- }
1437
+ for await (const chunk of engine.run()) {
1339
1438
  yield chunk;
1340
1439
  }
1341
1440
  }