@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.
- package/dist/esm/activities/chat/index.d.ts +1 -1
- package/dist/esm/activities/chat/index.js +357 -258
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +10 -1
- package/dist/esm/activities/chat/middleware/compose.js +55 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/index.d.ts +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +35 -3
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +12 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +13 -4
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/extend-adapter.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -1
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/otel.js +1 -4
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/utilities/ag-ui-wire.js +4 -1
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/package.json +2 -2
- package/skills/ai-core/middleware/SKILL.md +124 -18
- package/skills/ai-core/structured-outputs/SKILL.md +13 -0
- package/src/activities/chat/index.ts +653 -370
- package/src/activities/chat/messages.ts +1 -1
- package/src/activities/chat/middleware/compose.ts +65 -5
- package/src/activities/chat/middleware/index.ts +1 -0
- package/src/activities/chat/middleware/types.ts +53 -2
- package/src/activities/chat/stream/processor.ts +2 -2
- package/src/activities/chat/tools/schema-converter.ts +22 -7
- package/src/activities/generateImage/index.ts +3 -3
- package/src/extend-adapter.ts +1 -1
- package/src/index.ts +5 -1
- package/src/middlewares/content-guard.ts +1 -1
- package/src/middlewares/otel.ts +7 -10
- package/src/strip-to-spec-middleware.ts +1 -4
- 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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
this.
|
|
171
|
-
this.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 (
|
|
188
|
-
this.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
-
|
|
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
|
|
1069
|
-
const
|
|
1070
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1207
|
-
model,
|
|
1208
|
-
|
|
1209
|
-
|
|
1429
|
+
adapter,
|
|
1430
|
+
params: { ...textOptions, model, logger },
|
|
1431
|
+
middleware,
|
|
1432
|
+
context,
|
|
1433
|
+
finalStructuredOutput: { jsonSchema, yieldChunks: true }
|
|
1434
|
+
},
|
|
1435
|
+
logger
|
|
1210
1436
|
);
|
|
1211
|
-
const
|
|
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
|
}
|