@tanstack/ai 0.20.0 → 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/adapter.js +3 -1
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +4 -4
- package/dist/esm/activities/chat/index.js +403 -276
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +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 +57 -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 +40 -8
- package/dist/esm/activities/chat/stream/processor.d.ts +8 -8
- package/dist/esm/activities/chat/stream/processor.js +29 -21
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/strategies.d.ts +3 -3
- package/dist/esm/activities/chat/stream/strategies.js +4 -4
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +5 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +13 -2
- package/dist/esm/activities/chat/tools/schema-converter.js +47 -37
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +2 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +17 -9
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.js +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/generateAudio/adapter.js +3 -1
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +5 -1
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.js +3 -1
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js +5 -0
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +3 -1
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +3 -1
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.js +3 -1
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.js +6 -2
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.js +3 -1
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +5 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/extend-adapter.js.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +4 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger/internal-logger.js +2 -0
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/middlewares/content-guard.js +5 -4
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/otel.js +25 -18
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +1 -1
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/tools/provider-tool.d.ts +9 -0
- package/dist/esm/tools/provider-tool.js +7 -0
- package/dist/esm/tools/provider-tool.js.map +1 -0
- package/dist/esm/types.d.ts +6 -6
- package/dist/esm/utilities/ag-ui-wire.js +4 -1
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.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 +685 -394
- package/src/activities/chat/messages.ts +3 -2
- 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 +64 -11
- package/src/activities/chat/stream/processor.ts +21 -21
- package/src/activities/chat/stream/strategies.ts +3 -3
- package/src/activities/chat/tools/schema-converter.ts +98 -58
- package/src/activities/chat/tools/tool-calls.ts +18 -11
- package/src/activities/chat/tools/tool-definition.ts +6 -1
- package/src/activities/generateAudio/index.ts +5 -4
- package/src/activities/generateImage/index.ts +8 -3
- package/src/activities/stream-generation-result.ts +11 -2
- package/src/activities/summarize/chat-stream-summarize.ts +5 -2
- package/src/activities/summarize/index.ts +2 -2
- package/src/extend-adapter.ts +1 -1
- package/src/index.ts +6 -1
- package/src/middlewares/content-guard.ts +12 -4
- package/src/middlewares/otel.ts +32 -24
- package/src/realtime/index.ts +1 -1
- package/src/strip-to-spec-middleware.ts +1 -4
- package/src/tools/provider-tool.ts +14 -0
- package/src/types.ts +12 -8
- package/src/utilities/ag-ui-wire.ts +4 -3
- package/src/utilities/chat-params.ts +2 -2
|
@@ -14,24 +14,62 @@ function createChatOptions(options) {
|
|
|
14
14
|
return options;
|
|
15
15
|
}
|
|
16
16
|
class TextEngine {
|
|
17
|
+
adapter;
|
|
18
|
+
params;
|
|
19
|
+
systemPrompts;
|
|
20
|
+
tools;
|
|
21
|
+
loopStrategy;
|
|
22
|
+
toolCallManager;
|
|
23
|
+
lazyToolManager;
|
|
24
|
+
initialMessageCount;
|
|
25
|
+
requestId;
|
|
26
|
+
streamId;
|
|
27
|
+
effectiveRequest;
|
|
28
|
+
effectiveSignal;
|
|
29
|
+
messages;
|
|
30
|
+
iterationCount = 0;
|
|
31
|
+
lastFinishReason = null;
|
|
32
|
+
streamStartTime = 0;
|
|
33
|
+
totalChunkCount = 0;
|
|
34
|
+
currentMessageId = null;
|
|
35
|
+
accumulatedContent = "";
|
|
36
|
+
accumulatedThinking = [];
|
|
37
|
+
currentThinkingContent = "";
|
|
38
|
+
currentThinkingSignature = "";
|
|
39
|
+
eventOptions;
|
|
40
|
+
eventToolNames;
|
|
41
|
+
finishedEvent = null;
|
|
42
|
+
earlyTermination = false;
|
|
43
|
+
toolPhase = "continue";
|
|
44
|
+
cyclePhase = "processText";
|
|
45
|
+
// Client state extracted from initial messages (before conversion to ModelMessage)
|
|
46
|
+
initialApprovals;
|
|
47
|
+
initialClientToolResults;
|
|
48
|
+
// AG-UI protocol IDs
|
|
49
|
+
threadId;
|
|
50
|
+
runIdOverride;
|
|
51
|
+
parentRunIdOverride;
|
|
52
|
+
// Middleware support
|
|
53
|
+
middlewareRunner;
|
|
54
|
+
middlewareCtx;
|
|
55
|
+
deferredPromises = [];
|
|
56
|
+
abortReason;
|
|
57
|
+
middlewareAbortController;
|
|
58
|
+
terminalHookCalled = false;
|
|
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;
|
|
17
69
|
constructor(config, logger) {
|
|
18
|
-
this.iterationCount = 0;
|
|
19
|
-
this.lastFinishReason = null;
|
|
20
|
-
this.streamStartTime = 0;
|
|
21
|
-
this.totalChunkCount = 0;
|
|
22
|
-
this.currentMessageId = null;
|
|
23
|
-
this.accumulatedContent = "";
|
|
24
|
-
this.accumulatedThinking = [];
|
|
25
|
-
this.currentThinkingContent = "";
|
|
26
|
-
this.currentThinkingSignature = "";
|
|
27
|
-
this.finishedEvent = null;
|
|
28
|
-
this.earlyTermination = false;
|
|
29
|
-
this.toolPhase = "continue";
|
|
30
|
-
this.cyclePhase = "processText";
|
|
31
|
-
this.deferredPromises = [];
|
|
32
|
-
this.terminalHookCalled = false;
|
|
33
70
|
this.logger = logger;
|
|
34
71
|
this.adapter = config.adapter;
|
|
72
|
+
this.finalStructuredOutput = config.finalStructuredOutput;
|
|
35
73
|
this.params = config.params;
|
|
36
74
|
this.systemPrompts = config.params.systemPrompts || [];
|
|
37
75
|
this.loopStrategy = config.params.agentLoopStrategy || maxIterations(5);
|
|
@@ -41,9 +79,7 @@ class TextEngine {
|
|
|
41
79
|
);
|
|
42
80
|
this.initialApprovals = approvals;
|
|
43
81
|
this.initialClientToolResults = clientToolResults;
|
|
44
|
-
this.messages = convertMessagesToModelMessages(
|
|
45
|
-
config.params.messages
|
|
46
|
-
);
|
|
82
|
+
this.messages = convertMessagesToModelMessages(config.params.messages);
|
|
47
83
|
this.lazyToolManager = new LazyToolManager(
|
|
48
84
|
config.params.tools || [],
|
|
49
85
|
this.messages
|
|
@@ -112,6 +148,24 @@ class TextEngine {
|
|
|
112
148
|
getMessages() {
|
|
113
149
|
return this.messages;
|
|
114
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
|
+
}
|
|
115
169
|
async *run() {
|
|
116
170
|
this.beforeRun();
|
|
117
171
|
this.logger.agentLoop("run started", {
|
|
@@ -130,40 +184,64 @@ class TextEngine {
|
|
|
130
184
|
if (pendingPhase === "wait") {
|
|
131
185
|
return;
|
|
132
186
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.
|
|
143
|
-
this.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
+
}
|
|
156
213
|
this.logger.agentLoop("run finished", {
|
|
157
214
|
finishReason: this.lastFinishReason
|
|
158
215
|
});
|
|
159
|
-
if (
|
|
160
|
-
this.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
}
|
|
167
245
|
}
|
|
168
246
|
} catch (error) {
|
|
169
247
|
if (!this.terminalHookCalled) {
|
|
@@ -285,7 +363,11 @@ class TextEngine {
|
|
|
285
363
|
this.middlewareCtx,
|
|
286
364
|
chunk
|
|
287
365
|
);
|
|
366
|
+
const suppressAgentLifecycle = !!this.finalStructuredOutput && this.finalStructuredOutput.yieldChunks;
|
|
288
367
|
for (const outputChunk of outputChunks) {
|
|
368
|
+
if (suppressAgentLifecycle && (outputChunk.type === EventType.RUN_STARTED || outputChunk.type === EventType.RUN_FINISHED)) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
289
371
|
this.logger.output(`type=${outputChunk.type}`, { chunk: outputChunk });
|
|
290
372
|
yield outputChunk;
|
|
291
373
|
this.middlewareCtx.chunkIndex++;
|
|
@@ -527,11 +609,10 @@ class TextEngine {
|
|
|
527
609
|
}
|
|
528
610
|
return true;
|
|
529
611
|
});
|
|
530
|
-
if (undiscoveredLazyResults.length > 0) {
|
|
531
|
-
const finishEvt = this.finishedEvent;
|
|
612
|
+
if (undiscoveredLazyResults.length > 0 && this.finishedEvent) {
|
|
532
613
|
for (const chunk of this.buildToolResultChunks(
|
|
533
614
|
undiscoveredLazyResults,
|
|
534
|
-
|
|
615
|
+
this.finishedEvent
|
|
535
616
|
)) {
|
|
536
617
|
yield* this.pipeThroughMiddleware(chunk);
|
|
537
618
|
}
|
|
@@ -858,6 +939,212 @@ class TextEngine {
|
|
|
858
939
|
isCancelled() {
|
|
859
940
|
return this.isAborted() || this.isMiddlewareAborted();
|
|
860
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
|
+
}
|
|
861
1148
|
buildMiddlewareConfig() {
|
|
862
1149
|
return {
|
|
863
1150
|
messages: this.messages,
|
|
@@ -977,6 +1264,7 @@ async function* runStreamingText(options) {
|
|
|
977
1264
|
}
|
|
978
1265
|
function runNonStreamingText(options) {
|
|
979
1266
|
const stream = runStreamingText(
|
|
1267
|
+
// eslint-disable-next-line no-restricted-syntax -- generic-stream remap: caller is non-streaming (false), but runStreamingText is invoked internally to collect text; concrete `false`→`true` literals don't structurally overlap.
|
|
980
1268
|
options
|
|
981
1269
|
);
|
|
982
1270
|
return streamToText(stream);
|
|
@@ -988,58 +1276,72 @@ async function runAgenticStructuredOutput(options) {
|
|
|
988
1276
|
if (!outputSchema) {
|
|
989
1277
|
throw new Error("outputSchema is required for structured output");
|
|
990
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;
|
|
991
1286
|
const engine = new TextEngine(
|
|
992
1287
|
{
|
|
993
1288
|
adapter,
|
|
994
1289
|
params: { ...textOptions, model, logger },
|
|
995
1290
|
middleware,
|
|
996
|
-
context
|
|
1291
|
+
context,
|
|
1292
|
+
finalStructuredOutput: {
|
|
1293
|
+
jsonSchema,
|
|
1294
|
+
yieldChunks: false,
|
|
1295
|
+
...validate ? { validate } : {}
|
|
1296
|
+
}
|
|
997
1297
|
},
|
|
998
1298
|
logger
|
|
999
1299
|
);
|
|
1000
1300
|
for await (const _chunk of engine.run()) {
|
|
1001
1301
|
}
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
} = textOptions;
|
|
1008
|
-
const jsonSchema = convertSchemaToJsonSchema(outputSchema);
|
|
1009
|
-
if (!jsonSchema) {
|
|
1010
|
-
throw new Error("Failed to convert output schema to JSON Schema");
|
|
1011
|
-
}
|
|
1012
|
-
const providerName = adapter.provider ?? adapter.name;
|
|
1013
|
-
logger.request(
|
|
1014
|
-
`activity=chat-structured provider=${providerName} model=${model} messages=${finalMessages.length}`,
|
|
1015
|
-
{
|
|
1016
|
-
provider: providerName,
|
|
1017
|
-
model,
|
|
1018
|
-
messageCount: finalMessages.length
|
|
1019
|
-
}
|
|
1020
|
-
);
|
|
1021
|
-
const result = await adapter.structuredOutput({
|
|
1022
|
-
chatOptions: {
|
|
1023
|
-
...structuredTextOptions,
|
|
1024
|
-
model,
|
|
1025
|
-
messages: finalMessages,
|
|
1026
|
-
logger
|
|
1027
|
-
},
|
|
1028
|
-
outputSchema: jsonSchema
|
|
1029
|
-
});
|
|
1030
|
-
if (isStandardSchema(outputSchema)) {
|
|
1031
|
-
return parseWithStandardSchema(
|
|
1032
|
-
outputSchema,
|
|
1033
|
-
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
|
|
1034
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");
|
|
1035
1323
|
}
|
|
1036
1324
|
return result.data;
|
|
1037
1325
|
}
|
|
1038
|
-
|
|
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) {
|
|
1039
1340
|
const { chatOptions } = options;
|
|
1040
|
-
const
|
|
1041
|
-
const
|
|
1042
|
-
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}`;
|
|
1043
1345
|
const model = chatOptions.model;
|
|
1044
1346
|
const timestamp = Date.now();
|
|
1045
1347
|
yield {
|
|
@@ -1053,10 +1355,12 @@ async function* fallbackStructuredOutputStream(adapter, options) {
|
|
|
1053
1355
|
try {
|
|
1054
1356
|
result = await adapter.structuredOutput(options);
|
|
1055
1357
|
} catch (error) {
|
|
1056
|
-
|
|
1358
|
+
onAdapterError?.(error);
|
|
1359
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1057
1360
|
yield {
|
|
1058
1361
|
type: EventType.RUN_ERROR,
|
|
1059
1362
|
runId,
|
|
1363
|
+
threadId,
|
|
1060
1364
|
model,
|
|
1061
1365
|
timestamp,
|
|
1062
1366
|
message,
|
|
@@ -1120,194 +1424,17 @@ async function* runStreamingStructuredOutputImpl(options, jsonSchema) {
|
|
|
1120
1424
|
const { adapter, outputSchema, middleware, context, debug, ...textOptions } = options;
|
|
1121
1425
|
const model = adapter.model;
|
|
1122
1426
|
const logger = resolveDebugOption(debug);
|
|
1123
|
-
const
|
|
1124
|
-
let finalMessages = convertMessagesToModelMessages(textOptions.messages ?? []);
|
|
1125
|
-
if (textOptions.tools?.length) {
|
|
1126
|
-
const engine = new TextEngine(
|
|
1127
|
-
{
|
|
1128
|
-
adapter,
|
|
1129
|
-
params: { ...textOptions, model, logger, messages: finalMessages },
|
|
1130
|
-
middleware,
|
|
1131
|
-
context
|
|
1132
|
-
},
|
|
1133
|
-
logger
|
|
1134
|
-
);
|
|
1135
|
-
let agentLoopErrored = false;
|
|
1136
|
-
try {
|
|
1137
|
-
for await (const chunk of engine.run()) {
|
|
1138
|
-
if (chunk.type === "RUN_STARTED" || chunk.type === "RUN_FINISHED") {
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
if (chunk.type === "RUN_ERROR") {
|
|
1142
|
-
agentLoopErrored = true;
|
|
1143
|
-
yield chunk;
|
|
1144
|
-
continue;
|
|
1145
|
-
}
|
|
1146
|
-
yield chunk;
|
|
1147
|
-
}
|
|
1148
|
-
} catch (engineError) {
|
|
1149
|
-
const message = engineError.message || "Agent loop failed";
|
|
1150
|
-
logger.errors("runStreamingStructuredOutput agent loop failed", {
|
|
1151
|
-
error: engineError,
|
|
1152
|
-
source: "runStreamingStructuredOutput"
|
|
1153
|
-
});
|
|
1154
|
-
yield {
|
|
1155
|
-
type: EventType.RUN_ERROR,
|
|
1156
|
-
runId,
|
|
1157
|
-
model,
|
|
1158
|
-
timestamp: Date.now(),
|
|
1159
|
-
message,
|
|
1160
|
-
code: "agent-loop-failed",
|
|
1161
|
-
error: { message, code: "agent-loop-failed" }
|
|
1162
|
-
};
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
if (agentLoopErrored) {
|
|
1166
|
-
return;
|
|
1167
|
-
}
|
|
1168
|
-
finalMessages = engine.getMessages();
|
|
1169
|
-
}
|
|
1170
|
-
const {
|
|
1171
|
-
tools: _tools,
|
|
1172
|
-
agentLoopStrategy: _als,
|
|
1173
|
-
...structuredTextOptions
|
|
1174
|
-
} = textOptions;
|
|
1175
|
-
logger.request(
|
|
1176
|
-
`activity=chat-structured-stream provider=${adapter.name} model=${model} messages=${finalMessages.length}`,
|
|
1427
|
+
const engine = new TextEngine(
|
|
1177
1428
|
{
|
|
1178
|
-
|
|
1179
|
-
model,
|
|
1180
|
-
|
|
1181
|
-
|
|
1429
|
+
adapter,
|
|
1430
|
+
params: { ...textOptions, model, logger },
|
|
1431
|
+
middleware,
|
|
1432
|
+
context,
|
|
1433
|
+
finalStructuredOutput: { jsonSchema, yieldChunks: true }
|
|
1434
|
+
},
|
|
1435
|
+
logger
|
|
1182
1436
|
);
|
|
1183
|
-
const
|
|
1184
|
-
...structuredTextOptions,
|
|
1185
|
-
model,
|
|
1186
|
-
messages: finalMessages,
|
|
1187
|
-
logger,
|
|
1188
|
-
request: textOptions.abortController ? { signal: textOptions.abortController.signal } : void 0
|
|
1189
|
-
};
|
|
1190
|
-
const stream = adapter.structuredOutputStream ? adapter.structuredOutputStream({
|
|
1191
|
-
chatOptions: structuredChatOptions,
|
|
1192
|
-
outputSchema: jsonSchema
|
|
1193
|
-
}) : fallbackStructuredOutputStream(adapter, {
|
|
1194
|
-
chatOptions: structuredChatOptions,
|
|
1195
|
-
outputSchema: jsonSchema
|
|
1196
|
-
});
|
|
1197
|
-
let structuredMessageId = null;
|
|
1198
|
-
let startEmitted = false;
|
|
1199
|
-
const extractMessageId = (c) => {
|
|
1200
|
-
const id = c.messageId;
|
|
1201
|
-
return typeof id === "string" && id !== "" ? id : null;
|
|
1202
|
-
};
|
|
1203
|
-
const emitStartIfNeeded = function* (referenceChunk) {
|
|
1204
|
-
if (startEmitted) return;
|
|
1205
|
-
const idForStart = structuredMessageId ?? generateMessageId();
|
|
1206
|
-
structuredMessageId = idForStart;
|
|
1207
|
-
startEmitted = true;
|
|
1208
|
-
yield {
|
|
1209
|
-
type: EventType.CUSTOM,
|
|
1210
|
-
name: "structured-output.start",
|
|
1211
|
-
value: { messageId: idForStart },
|
|
1212
|
-
model: "model" in referenceChunk ? referenceChunk.model ?? model : model,
|
|
1213
|
-
timestamp: "timestamp" in referenceChunk ? referenceChunk.timestamp ?? Date.now() : Date.now(),
|
|
1214
|
-
runId
|
|
1215
|
-
};
|
|
1216
|
-
};
|
|
1217
|
-
for await (const chunk of stream) {
|
|
1218
|
-
if (!structuredMessageId) {
|
|
1219
|
-
if (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT) {
|
|
1220
|
-
structuredMessageId = extractMessageId(chunk);
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
if (chunk.type === EventType.RUN_ERROR && !startEmitted) {
|
|
1224
|
-
yield* emitStartIfNeeded(chunk);
|
|
1225
|
-
}
|
|
1226
|
-
if (!structuredMessageId && chunk.type === EventType.TEXT_MESSAGE_CONTENT) {
|
|
1227
|
-
yield {
|
|
1228
|
-
type: EventType.RUN_ERROR,
|
|
1229
|
-
runId,
|
|
1230
|
-
model,
|
|
1231
|
-
timestamp: Date.now(),
|
|
1232
|
-
message: "Structured-output stream produced text content without a messageId; adapter is not honoring the AG-UI contract.",
|
|
1233
|
-
code: "structured-output-missing-message-id"
|
|
1234
|
-
};
|
|
1235
|
-
return;
|
|
1236
|
-
}
|
|
1237
|
-
if (!startEmitted && structuredMessageId && (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT)) {
|
|
1238
|
-
startEmitted = true;
|
|
1239
|
-
yield {
|
|
1240
|
-
type: EventType.CUSTOM,
|
|
1241
|
-
name: "structured-output.start",
|
|
1242
|
-
value: { messageId: structuredMessageId },
|
|
1243
|
-
model: "model" in chunk ? chunk.model ?? model : model,
|
|
1244
|
-
timestamp: "timestamp" in chunk ? chunk.timestamp ?? Date.now() : Date.now(),
|
|
1245
|
-
runId
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
|
|
1249
|
-
const value = chunk.value;
|
|
1250
|
-
if (isStandardSchema(outputSchema)) {
|
|
1251
|
-
try {
|
|
1252
|
-
const validated = parseWithStandardSchema(
|
|
1253
|
-
outputSchema,
|
|
1254
|
-
value.object
|
|
1255
|
-
);
|
|
1256
|
-
yield {
|
|
1257
|
-
...chunk,
|
|
1258
|
-
// Forward `reasoning` through schema validation so consumers that
|
|
1259
|
-
// only listen for the terminal event don't lose chain-of-thought.
|
|
1260
|
-
// Tag with messageId so the client processor can snap the right
|
|
1261
|
-
// assistant message's structured-output part.
|
|
1262
|
-
value: {
|
|
1263
|
-
object: validated,
|
|
1264
|
-
raw: value.raw,
|
|
1265
|
-
...value.reasoning ? { reasoning: value.reasoning } : {},
|
|
1266
|
-
...structuredMessageId ? { messageId: structuredMessageId } : {}
|
|
1267
|
-
}
|
|
1268
|
-
};
|
|
1269
|
-
continue;
|
|
1270
|
-
} catch (err) {
|
|
1271
|
-
const message = err.message || "Schema validation failed";
|
|
1272
|
-
logger.errors(
|
|
1273
|
-
"runStreamingStructuredOutput schema validation failed",
|
|
1274
|
-
{
|
|
1275
|
-
error: err,
|
|
1276
|
-
source: "runStreamingStructuredOutput",
|
|
1277
|
-
// Include reasoning in error meta so post-mortems can recover
|
|
1278
|
-
// what the model thought through before producing invalid JSON.
|
|
1279
|
-
...value.reasoning ? { reasoning: value.reasoning } : {}
|
|
1280
|
-
}
|
|
1281
|
-
);
|
|
1282
|
-
yield {
|
|
1283
|
-
type: EventType.RUN_ERROR,
|
|
1284
|
-
runId,
|
|
1285
|
-
model: chunk.model ?? model,
|
|
1286
|
-
timestamp: chunk.timestamp ?? Date.now(),
|
|
1287
|
-
message,
|
|
1288
|
-
code: "schema-validation",
|
|
1289
|
-
error: {
|
|
1290
|
-
message,
|
|
1291
|
-
code: "schema-validation",
|
|
1292
|
-
...value.reasoning ? { reasoning: value.reasoning } : {}
|
|
1293
|
-
}
|
|
1294
|
-
};
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
if (structuredMessageId) {
|
|
1299
|
-
yield {
|
|
1300
|
-
...chunk,
|
|
1301
|
-
value: {
|
|
1302
|
-
...chunk.value,
|
|
1303
|
-
messageId: structuredMessageId
|
|
1304
|
-
}
|
|
1305
|
-
};
|
|
1306
|
-
continue;
|
|
1307
|
-
}
|
|
1308
|
-
yield chunk;
|
|
1309
|
-
continue;
|
|
1310
|
-
}
|
|
1437
|
+
for await (const chunk of engine.run()) {
|
|
1311
1438
|
yield chunk;
|
|
1312
1439
|
}
|
|
1313
1440
|
}
|