ai 7.0.90 → 7.0.92
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/CHANGELOG.md +21 -0
- package/dist/index.d.ts +45 -12
- package/dist/index.js +412 -90
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +4 -2
- package/dist/internal/index.js +9 -7
- package/dist/internal/index.js.map +1 -1
- package/docs/02-foundations/02-providers-and-models.mdx +5 -0
- package/docs/03-agents/06-policy-tool-approvals.mdx +2 -1
- package/docs/03-agents/06-tool-approvals.mdx +7 -4
- package/docs/03-ai-sdk-core/50-error-handling.mdx +83 -6
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +24 -4
- package/docs/07-reference/01-ai-sdk-core/80-smooth-stream.mdx +1 -1
- package/package.json +6 -6
- package/src/generate-text/execute-tools-from-stream.ts +18 -4
- package/src/generate-text/generate-text-events.ts +1 -1
- package/src/generate-text/index.ts +2 -0
- package/src/generate-text/invoke-tool-callbacks-from-stream.ts +14 -2
- package/src/generate-text/smooth-stream.ts +19 -4
- package/src/generate-text/stream-retry-attempt-boundary.ts +29 -0
- package/src/generate-text/stream-text.ts +637 -162
- package/src/ui/call-completion-api.ts +1 -1
- package/src/ui/http-chat-transport.ts +2 -2
- package/src/util/async-iterable-stream.ts +1 -1
- package/src/util/prepare-retries.ts +9 -5
|
@@ -95,6 +95,7 @@ import {
|
|
|
95
95
|
type ActiveToolSubset,
|
|
96
96
|
} from './filter-active-tools';
|
|
97
97
|
import type {
|
|
98
|
+
GenerateTextAbortEvent,
|
|
98
99
|
GenerateTextEndEvent,
|
|
99
100
|
GenerateTextOnStartCallback,
|
|
100
101
|
GenerateTextOnStepEndCallback,
|
|
@@ -127,7 +128,15 @@ import {
|
|
|
127
128
|
isStopConditionMet,
|
|
128
129
|
type StopCondition,
|
|
129
130
|
} from './stop-condition';
|
|
130
|
-
import {
|
|
131
|
+
import {
|
|
132
|
+
streamLanguageModelCall,
|
|
133
|
+
type LanguageModelStreamPart,
|
|
134
|
+
} from './stream-language-model-call';
|
|
135
|
+
import {
|
|
136
|
+
createStreamRetryAttemptBoundaryPart,
|
|
137
|
+
isStreamRetryAttemptBoundaryPart,
|
|
138
|
+
type StreamRetryAttemptBoundaryPart,
|
|
139
|
+
} from './stream-retry-attempt-boundary';
|
|
131
140
|
import type {
|
|
132
141
|
ConsumeStreamOptions,
|
|
133
142
|
StreamTextResult,
|
|
@@ -193,9 +202,14 @@ const isOutputChunkType = {
|
|
|
193
202
|
'model-call-end': false,
|
|
194
203
|
error: false,
|
|
195
204
|
raw: false,
|
|
196
|
-
} as const satisfies Record<
|
|
197
|
-
|
|
198
|
-
|
|
205
|
+
} as const satisfies Record<
|
|
206
|
+
Exclude<ExecuteToolsStreamPart, StreamRetryAttemptBoundaryPart>['type'],
|
|
207
|
+
boolean
|
|
208
|
+
>;
|
|
209
|
+
|
|
210
|
+
function isOutputChunk(
|
|
211
|
+
chunk: Exclude<ExecuteToolsStreamPart, StreamRetryAttemptBoundaryPart>,
|
|
212
|
+
): boolean {
|
|
199
213
|
if (!isOutputChunkType[chunk.type]) {
|
|
200
214
|
return false;
|
|
201
215
|
}
|
|
@@ -257,14 +271,29 @@ export type StreamTextTransform<TOOLS extends ToolSet> = (options: {
|
|
|
257
271
|
stopStream: () => void;
|
|
258
272
|
}) => TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>>;
|
|
259
273
|
|
|
274
|
+
/** A result that requests recovery from a streamed provider error. */
|
|
275
|
+
export type StreamTextOnErrorResult = { retry: true };
|
|
276
|
+
|
|
260
277
|
/**
|
|
261
|
-
*
|
|
278
|
+
* Existing observer callback that is set using the `onError` option.
|
|
262
279
|
*
|
|
263
280
|
* @param event - The event that is passed to the callback.
|
|
264
281
|
*/
|
|
265
|
-
export type StreamTextOnErrorCallback = Callback<{
|
|
282
|
+
export type StreamTextOnErrorCallback = Callback<{ error: unknown }>;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Retry-capable callback that is set using the `onError` option.
|
|
286
|
+
*
|
|
287
|
+
* @param event - The event that is passed to the callback.
|
|
288
|
+
*/
|
|
289
|
+
export type StreamTextOnErrorRetryCallback = (event: {
|
|
266
290
|
error: unknown;
|
|
267
|
-
}
|
|
291
|
+
}) =>
|
|
292
|
+
| PromiseLike<void | StreamTextOnErrorResult>
|
|
293
|
+
| void
|
|
294
|
+
| StreamTextOnErrorResult;
|
|
295
|
+
|
|
296
|
+
type StreamTextOnErrorHandler = (event: { error: unknown }) => void;
|
|
268
297
|
|
|
269
298
|
/**
|
|
270
299
|
* Callback that is set using the `onChunk` option.
|
|
@@ -301,12 +330,7 @@ export type StreamTextOnEndCallback<
|
|
|
301
330
|
export type StreamTextOnAbortCallback<
|
|
302
331
|
TOOLS extends ToolSet,
|
|
303
332
|
RUNTIME_CONTEXT extends Context,
|
|
304
|
-
> = Callback<
|
|
305
|
-
/**
|
|
306
|
-
* Details for all previously finished steps.
|
|
307
|
-
*/
|
|
308
|
-
readonly steps: StepResult<TOOLS, RUNTIME_CONTEXT>[];
|
|
309
|
-
}>;
|
|
333
|
+
> = Callback<GenerateTextAbortEvent<TOOLS, RUNTIME_CONTEXT>>;
|
|
310
334
|
|
|
311
335
|
/**
|
|
312
336
|
* Generate a text and call tools for a given prompt using a language model.
|
|
@@ -344,6 +368,7 @@ export type StreamTextOnAbortCallback<
|
|
|
344
368
|
* If set and supported by the model, calls will generate deterministic results.
|
|
345
369
|
*
|
|
346
370
|
* @param maxRetries - Maximum number of retries. Set to 0 to disable retries. Default: 2.
|
|
371
|
+
* @param streamRetries - Maximum number of retries for provider errors received after streaming starts. Set to 0 to disable automatic stream retries while allowing `onError` to request retries. Omit to disable all stream retry behavior. Default: 0.
|
|
347
372
|
* @param abortSignal - An optional abort signal that can be used to cancel the call.
|
|
348
373
|
* @param timeout - An optional timeout in milliseconds. The call will be aborted if it takes longer than the specified timeout.
|
|
349
374
|
* @param headers - Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers.
|
|
@@ -388,6 +413,7 @@ export function streamText<
|
|
|
388
413
|
messages,
|
|
389
414
|
allowSystemInMessages,
|
|
390
415
|
maxRetries,
|
|
416
|
+
streamRetries,
|
|
391
417
|
abortSignal,
|
|
392
418
|
timeout,
|
|
393
419
|
headers,
|
|
@@ -410,9 +436,7 @@ export function streamText<
|
|
|
410
436
|
experimental_download: download,
|
|
411
437
|
includeRawChunks,
|
|
412
438
|
onChunk,
|
|
413
|
-
onError
|
|
414
|
-
console.error(error);
|
|
415
|
-
},
|
|
439
|
+
onError: onErrorArg,
|
|
416
440
|
onFinish,
|
|
417
441
|
onEnd = onFinish,
|
|
418
442
|
onAbort,
|
|
@@ -597,9 +621,35 @@ export function streamText<
|
|
|
597
621
|
/**
|
|
598
622
|
* Callback that is invoked when an error occurs during streaming.
|
|
599
623
|
* You can use it to log errors.
|
|
624
|
+
* Return `{ retry: true }` to retry the current model step after a provider
|
|
625
|
+
* error is received from the response stream when `streamRetries` is
|
|
626
|
+
* explicitly configured.
|
|
600
627
|
* The stream processing will pause until the callback promise is resolved.
|
|
601
628
|
*/
|
|
602
|
-
onError?:
|
|
629
|
+
onError?:
|
|
630
|
+
| StreamTextOnErrorCallback
|
|
631
|
+
| StreamTextOnErrorRetryCallback
|
|
632
|
+
| StreamTextOnErrorHandler;
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Maximum number of automatic retries for provider errors received after
|
|
636
|
+
* response streaming has started. Each retry reruns only the current model
|
|
637
|
+
* step. Completed earlier steps and their tool results are preserved.
|
|
638
|
+
*
|
|
639
|
+
* Partial output from a failed attempt that was already emitted cannot be
|
|
640
|
+
* retracted and remains in consumer-facing streams. It is excluded from
|
|
641
|
+
* the recovered step result, structured output parsing, response messages,
|
|
642
|
+
* and subsequent model steps.
|
|
643
|
+
*
|
|
644
|
+
* Set to `0` to disable automatic retries while allowing `onError` to
|
|
645
|
+
* request one retry. When automatic retries are configured, `onError` can
|
|
646
|
+
* request at most one additional retry after they are exhausted. Omit this
|
|
647
|
+
* option to disable all stream retry behavior and preserve incremental tool
|
|
648
|
+
* streaming for existing `onError` observers.
|
|
649
|
+
*
|
|
650
|
+
* @default 0 (stream retry behavior disabled when omitted)
|
|
651
|
+
*/
|
|
652
|
+
streamRetries?: number;
|
|
603
653
|
|
|
604
654
|
/**
|
|
605
655
|
* Callback that is called when the LLM response and all request tool executions
|
|
@@ -781,6 +831,11 @@ export function streamText<
|
|
|
781
831
|
firstChunkTimeoutMs != null ? new AbortController() : undefined;
|
|
782
832
|
const chunkAbortController =
|
|
783
833
|
chunkTimeoutMs != null ? new AbortController() : undefined;
|
|
834
|
+
const onError: StreamTextOnErrorHandler =
|
|
835
|
+
onErrorArg ??
|
|
836
|
+
(({ error }) => {
|
|
837
|
+
console.error(error);
|
|
838
|
+
});
|
|
784
839
|
const resolvedOnStart = onStart ?? experimental_onStart;
|
|
785
840
|
const resolvedOnStepStart = onStepStart ?? experimental_onStepStart;
|
|
786
841
|
const resolvedOnLanguageModelCallStart =
|
|
@@ -798,6 +853,7 @@ export function streamText<
|
|
|
798
853
|
headers,
|
|
799
854
|
settings,
|
|
800
855
|
maxRetries,
|
|
856
|
+
streamRetries,
|
|
801
857
|
abortSignal: mergeAbortSignals(
|
|
802
858
|
abortSignal,
|
|
803
859
|
totalTimeoutMs,
|
|
@@ -836,6 +892,7 @@ export function streamText<
|
|
|
836
892
|
timeout,
|
|
837
893
|
onChunk,
|
|
838
894
|
onError,
|
|
895
|
+
canRetryStreamViaOnError: streamRetries !== undefined && onErrorArg != null,
|
|
839
896
|
onEnd,
|
|
840
897
|
onAbort,
|
|
841
898
|
onStepFinish: resolvedOnStepEnd,
|
|
@@ -864,6 +921,31 @@ export type EnrichedStreamPart<TOOLS extends ToolSet, PARTIAL_OUTPUT> = {
|
|
|
864
921
|
partialOutput: PARTIAL_OUTPUT | undefined;
|
|
865
922
|
};
|
|
866
923
|
|
|
924
|
+
type StreamRetryBoundaryMetadata = {
|
|
925
|
+
request: LanguageModelRequestMetadata;
|
|
926
|
+
warnings: Array<CallWarning>;
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
const streamRetryBoundarySymbol = Symbol('streamRetryBoundary');
|
|
930
|
+
|
|
931
|
+
type StreamRetryBoundaryPart = {
|
|
932
|
+
[streamRetryBoundarySymbol]: StreamRetryBoundaryMetadata;
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
type InternalTextStreamPart<TOOLS extends ToolSet> =
|
|
936
|
+
| TextStreamPart<TOOLS>
|
|
937
|
+
| StreamRetryBoundaryPart;
|
|
938
|
+
|
|
939
|
+
type InternalEnrichedStreamPart<TOOLS extends ToolSet, PARTIAL_OUTPUT> =
|
|
940
|
+
| EnrichedStreamPart<TOOLS, PARTIAL_OUTPUT>
|
|
941
|
+
| StreamRetryBoundaryPart;
|
|
942
|
+
|
|
943
|
+
function isStreamRetryBoundaryPart(
|
|
944
|
+
part: object,
|
|
945
|
+
): part is StreamRetryBoundaryPart {
|
|
946
|
+
return streamRetryBoundarySymbol in part;
|
|
947
|
+
}
|
|
948
|
+
|
|
867
949
|
async function markPromiseAsHandled<T>(promise: Promise<T>): Promise<void> {
|
|
868
950
|
try {
|
|
869
951
|
await promise;
|
|
@@ -876,8 +958,8 @@ function createOutputTransformStream<
|
|
|
876
958
|
>(
|
|
877
959
|
output: OUTPUT,
|
|
878
960
|
): TransformStream<
|
|
879
|
-
|
|
880
|
-
|
|
961
|
+
InternalTextStreamPart<TOOLS>,
|
|
962
|
+
InternalEnrichedStreamPart<TOOLS, InferPartialOutput<OUTPUT>>
|
|
881
963
|
> {
|
|
882
964
|
let firstTextChunkId: string | undefined = undefined;
|
|
883
965
|
let text = '';
|
|
@@ -885,32 +967,61 @@ function createOutputTransformStream<
|
|
|
885
967
|
let textProviderMetadata: ProviderMetadata | undefined = undefined;
|
|
886
968
|
let lastPublishedValue = '';
|
|
887
969
|
|
|
970
|
+
function resetAttemptState() {
|
|
971
|
+
firstTextChunkId = undefined;
|
|
972
|
+
text = '';
|
|
973
|
+
textChunk = '';
|
|
974
|
+
textProviderMetadata = undefined;
|
|
975
|
+
lastPublishedValue = '';
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function enqueueChunk({
|
|
979
|
+
controller,
|
|
980
|
+
chunk,
|
|
981
|
+
}: {
|
|
982
|
+
controller: TransformStreamDefaultController<
|
|
983
|
+
InternalEnrichedStreamPart<TOOLS, InferPartialOutput<OUTPUT>>
|
|
984
|
+
>;
|
|
985
|
+
chunk: EnrichedStreamPart<TOOLS, InferPartialOutput<OUTPUT>>;
|
|
986
|
+
}) {
|
|
987
|
+
controller.enqueue(chunk);
|
|
988
|
+
}
|
|
989
|
+
|
|
888
990
|
function publishTextChunk({
|
|
889
991
|
controller,
|
|
890
992
|
partialOutput = undefined,
|
|
891
993
|
}: {
|
|
892
994
|
controller: TransformStreamDefaultController<
|
|
893
|
-
|
|
995
|
+
InternalEnrichedStreamPart<TOOLS, InferPartialOutput<OUTPUT>>
|
|
894
996
|
>;
|
|
895
997
|
partialOutput?: InferPartialOutput<OUTPUT>;
|
|
896
998
|
}) {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
999
|
+
enqueueChunk({
|
|
1000
|
+
controller,
|
|
1001
|
+
chunk: {
|
|
1002
|
+
part: {
|
|
1003
|
+
type: 'text-delta',
|
|
1004
|
+
id: firstTextChunkId!,
|
|
1005
|
+
text: textChunk,
|
|
1006
|
+
providerMetadata: textProviderMetadata,
|
|
1007
|
+
},
|
|
1008
|
+
partialOutput,
|
|
903
1009
|
},
|
|
904
|
-
partialOutput,
|
|
905
1010
|
});
|
|
906
1011
|
textChunk = '';
|
|
907
1012
|
}
|
|
908
1013
|
|
|
909
1014
|
return new TransformStream<
|
|
910
|
-
|
|
911
|
-
|
|
1015
|
+
InternalTextStreamPart<TOOLS>,
|
|
1016
|
+
InternalEnrichedStreamPart<TOOLS, InferPartialOutput<OUTPUT>>
|
|
912
1017
|
>({
|
|
913
1018
|
async transform(chunk, controller) {
|
|
1019
|
+
if (isStreamRetryBoundaryPart(chunk)) {
|
|
1020
|
+
resetAttemptState();
|
|
1021
|
+
controller.enqueue(chunk);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
914
1025
|
// ensure that we publish the last text chunk before the step finish:
|
|
915
1026
|
if (chunk.type === 'finish-step' && textChunk.length > 0) {
|
|
916
1027
|
publishTextChunk({ controller });
|
|
@@ -921,7 +1032,10 @@ function createOutputTransformStream<
|
|
|
921
1032
|
chunk.type !== 'text-start' &&
|
|
922
1033
|
chunk.type !== 'text-end'
|
|
923
1034
|
) {
|
|
924
|
-
|
|
1035
|
+
enqueueChunk({
|
|
1036
|
+
controller,
|
|
1037
|
+
chunk: { part: chunk, partialOutput: undefined },
|
|
1038
|
+
});
|
|
925
1039
|
return;
|
|
926
1040
|
}
|
|
927
1041
|
|
|
@@ -930,12 +1044,18 @@ function createOutputTransformStream<
|
|
|
930
1044
|
if (firstTextChunkId == null) {
|
|
931
1045
|
firstTextChunkId = chunk.id;
|
|
932
1046
|
} else if (chunk.id !== firstTextChunkId) {
|
|
933
|
-
|
|
1047
|
+
enqueueChunk({
|
|
1048
|
+
controller,
|
|
1049
|
+
chunk: { part: chunk, partialOutput: undefined },
|
|
1050
|
+
});
|
|
934
1051
|
return;
|
|
935
1052
|
}
|
|
936
1053
|
|
|
937
1054
|
if (chunk.type === 'text-start') {
|
|
938
|
-
|
|
1055
|
+
enqueueChunk({
|
|
1056
|
+
controller,
|
|
1057
|
+
chunk: { part: chunk, partialOutput: undefined },
|
|
1058
|
+
});
|
|
939
1059
|
return;
|
|
940
1060
|
}
|
|
941
1061
|
|
|
@@ -943,7 +1063,10 @@ function createOutputTransformStream<
|
|
|
943
1063
|
if (textChunk.length > 0) {
|
|
944
1064
|
publishTextChunk({ controller });
|
|
945
1065
|
}
|
|
946
|
-
|
|
1066
|
+
enqueueChunk({
|
|
1067
|
+
controller,
|
|
1068
|
+
chunk: { part: chunk, partialOutput: undefined },
|
|
1069
|
+
});
|
|
947
1070
|
return;
|
|
948
1071
|
}
|
|
949
1072
|
|
|
@@ -952,7 +1075,10 @@ function createOutputTransformStream<
|
|
|
952
1075
|
textProviderMetadata = chunk.providerMetadata ?? textProviderMetadata;
|
|
953
1076
|
|
|
954
1077
|
if (chunk.text.length === 0 && chunk.providerMetadata != null) {
|
|
955
|
-
|
|
1078
|
+
enqueueChunk({
|
|
1079
|
+
controller,
|
|
1080
|
+
chunk: { part: chunk, partialOutput: undefined },
|
|
1081
|
+
});
|
|
956
1082
|
return;
|
|
957
1083
|
}
|
|
958
1084
|
|
|
@@ -976,6 +1102,95 @@ function createOutputTransformStream<
|
|
|
976
1102
|
});
|
|
977
1103
|
}
|
|
978
1104
|
|
|
1105
|
+
function applyStreamTextTransforms<TOOLS extends ToolSet>({
|
|
1106
|
+
stream,
|
|
1107
|
+
transforms,
|
|
1108
|
+
tools,
|
|
1109
|
+
stopStream,
|
|
1110
|
+
}: {
|
|
1111
|
+
stream: ReadableStream<InternalTextStreamPart<TOOLS>>;
|
|
1112
|
+
transforms: Array<StreamTextTransform<TOOLS>>;
|
|
1113
|
+
tools: TOOLS;
|
|
1114
|
+
stopStream: () => void;
|
|
1115
|
+
}): ReadableStream<InternalTextStreamPart<TOOLS>> {
|
|
1116
|
+
const sourceReader = stream.getReader();
|
|
1117
|
+
let sourceDone = false;
|
|
1118
|
+
let pendingBoundary: StreamRetryBoundaryPart | undefined;
|
|
1119
|
+
let transformedSegmentReader:
|
|
1120
|
+
| ReadableStreamDefaultReader<TextStreamPart<TOOLS>>
|
|
1121
|
+
| undefined;
|
|
1122
|
+
|
|
1123
|
+
const createTransformedSegmentReader = () => {
|
|
1124
|
+
let segment = new ReadableStream<TextStreamPart<TOOLS>>(
|
|
1125
|
+
{
|
|
1126
|
+
async pull(controller) {
|
|
1127
|
+
const { done, value } = await sourceReader.read();
|
|
1128
|
+
|
|
1129
|
+
if (done) {
|
|
1130
|
+
sourceDone = true;
|
|
1131
|
+
controller.close();
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
if (isStreamRetryBoundaryPart(value)) {
|
|
1136
|
+
pendingBoundary = value;
|
|
1137
|
+
controller.close();
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
controller.enqueue(value);
|
|
1142
|
+
},
|
|
1143
|
+
cancel(reason) {
|
|
1144
|
+
return sourceReader.cancel(reason);
|
|
1145
|
+
},
|
|
1146
|
+
},
|
|
1147
|
+
// Do not prefetch the next source chunk. `stopStream` is invoked from a
|
|
1148
|
+
// user transform and must close the gate before another chunk enters it.
|
|
1149
|
+
{ highWaterMark: 0 },
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
for (const transform of transforms) {
|
|
1153
|
+
segment = segment.pipeThrough(
|
|
1154
|
+
transform({
|
|
1155
|
+
tools,
|
|
1156
|
+
stopStream,
|
|
1157
|
+
}),
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
return segment.getReader();
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
return new ReadableStream<InternalTextStreamPart<TOOLS>>({
|
|
1165
|
+
async pull(controller) {
|
|
1166
|
+
transformedSegmentReader ??= createTransformedSegmentReader();
|
|
1167
|
+
|
|
1168
|
+
const { done, value } = await transformedSegmentReader.read();
|
|
1169
|
+
|
|
1170
|
+
if (!done) {
|
|
1171
|
+
controller.enqueue(value);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
transformedSegmentReader = undefined;
|
|
1176
|
+
|
|
1177
|
+
if (pendingBoundary != null) {
|
|
1178
|
+
controller.enqueue(pendingBoundary);
|
|
1179
|
+
pendingBoundary = undefined;
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
if (sourceDone) {
|
|
1184
|
+
controller.close();
|
|
1185
|
+
}
|
|
1186
|
+
},
|
|
1187
|
+
async cancel(reason) {
|
|
1188
|
+
await transformedSegmentReader?.cancel(reason);
|
|
1189
|
+
await sourceReader.cancel(reason);
|
|
1190
|
+
},
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
|
|
979
1194
|
class DefaultStreamTextResult<
|
|
980
1195
|
TOOLS extends ToolSet,
|
|
981
1196
|
RUNTIME_CONTEXT extends Context,
|
|
@@ -1000,7 +1215,7 @@ class DefaultStreamTextResult<
|
|
|
1000
1215
|
private outputPromise: Promise<InferCompleteOutput<OUTPUT>> | undefined;
|
|
1001
1216
|
|
|
1002
1217
|
private readonly addStream: (
|
|
1003
|
-
stream: ReadableStream<
|
|
1218
|
+
stream: ReadableStream<InternalTextStreamPart<TOOLS>>,
|
|
1004
1219
|
callbacks?: {
|
|
1005
1220
|
onError?: (error: unknown) => void;
|
|
1006
1221
|
onCancel?: () => void;
|
|
@@ -1023,6 +1238,7 @@ class DefaultStreamTextResult<
|
|
|
1023
1238
|
headers,
|
|
1024
1239
|
settings,
|
|
1025
1240
|
maxRetries: maxRetriesArg,
|
|
1241
|
+
streamRetries: streamRetriesArg,
|
|
1026
1242
|
abortSignal,
|
|
1027
1243
|
stepTimeoutMs,
|
|
1028
1244
|
stepAbortController,
|
|
@@ -1056,6 +1272,7 @@ class DefaultStreamTextResult<
|
|
|
1056
1272
|
timeout,
|
|
1057
1273
|
onChunk,
|
|
1058
1274
|
onError,
|
|
1275
|
+
canRetryStreamViaOnError,
|
|
1059
1276
|
onEnd,
|
|
1060
1277
|
onAbort,
|
|
1061
1278
|
onStepFinish,
|
|
@@ -1075,6 +1292,7 @@ class DefaultStreamTextResult<
|
|
|
1075
1292
|
headers: Record<string, string | undefined> | undefined;
|
|
1076
1293
|
settings: LanguageModelCallOptions;
|
|
1077
1294
|
maxRetries: number | undefined;
|
|
1295
|
+
streamRetries: number | undefined;
|
|
1078
1296
|
abortSignal: AbortSignal | undefined;
|
|
1079
1297
|
stepTimeoutMs: number | undefined;
|
|
1080
1298
|
stepAbortController: AbortController | undefined;
|
|
@@ -1117,7 +1335,8 @@ class DefaultStreamTextResult<
|
|
|
1117
1335
|
|
|
1118
1336
|
// callbacks:
|
|
1119
1337
|
onChunk: undefined | StreamTextOnChunkCallback<TOOLS>;
|
|
1120
|
-
onError:
|
|
1338
|
+
onError: StreamTextOnErrorHandler;
|
|
1339
|
+
canRetryStreamViaOnError: boolean;
|
|
1121
1340
|
onEnd:
|
|
1122
1341
|
| undefined
|
|
1123
1342
|
| StreamTextOnEndCallback<
|
|
@@ -1241,20 +1460,36 @@ class DefaultStreamTextResult<
|
|
|
1241
1460
|
}
|
|
1242
1461
|
> = createIdMap();
|
|
1243
1462
|
let recordedNoOutputError: NoOutputGeneratedError | undefined;
|
|
1463
|
+
const errorsHandledForStreamRetry = new Set<unknown>();
|
|
1244
1464
|
|
|
1245
1465
|
const eventProcessor = new TransformStream<
|
|
1246
|
-
|
|
1466
|
+
InternalEnrichedStreamPart<TOOLS, InferPartialOutput<OUTPUT>>,
|
|
1247
1467
|
EnrichedStreamPart<TOOLS, InferPartialOutput<OUTPUT>>
|
|
1248
1468
|
>({
|
|
1249
1469
|
async transform(chunk, controller) {
|
|
1250
|
-
|
|
1470
|
+
if (isStreamRetryBoundaryPart(chunk)) {
|
|
1471
|
+
const retryBoundary = chunk[streamRetryBoundarySymbol];
|
|
1472
|
+
recordedContent = [];
|
|
1473
|
+
activeReasoningContent = createIdMap();
|
|
1474
|
+
activeTextContent = createIdMap();
|
|
1475
|
+
recordedRequest = retryBoundary.request;
|
|
1476
|
+
recordedRequestMessages = retryBoundary.request.messages ?? [];
|
|
1477
|
+
recordedWarnings = retryBoundary.warnings;
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1251
1480
|
|
|
1252
1481
|
const { part } = chunk;
|
|
1482
|
+
controller.enqueue(chunk); // forward the chunk to the next stream
|
|
1253
1483
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1484
|
+
const callbacksHandledForStreamRetry =
|
|
1485
|
+
part.type === 'error' && errorsHandledForStreamRetry.has(part.error);
|
|
1486
|
+
|
|
1487
|
+
if (!callbacksHandledForStreamRetry) {
|
|
1488
|
+
await notify({
|
|
1489
|
+
event: { chunk: part },
|
|
1490
|
+
callbacks: onChunk,
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1258
1493
|
|
|
1259
1494
|
if (part.type === 'error') {
|
|
1260
1495
|
const error = wrapGatewayError(part.error);
|
|
@@ -1263,10 +1498,16 @@ class DefaultStreamTextResult<
|
|
|
1263
1498
|
recordedNoOutputError = error;
|
|
1264
1499
|
}
|
|
1265
1500
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1501
|
+
if (callbacksHandledForStreamRetry) {
|
|
1502
|
+
errorsHandledForStreamRetry.delete(part.error);
|
|
1503
|
+
} else {
|
|
1504
|
+
await notify({
|
|
1505
|
+
event: { error },
|
|
1506
|
+
callbacks: async event => {
|
|
1507
|
+
await onError(event);
|
|
1508
|
+
},
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1270
1511
|
}
|
|
1271
1512
|
|
|
1272
1513
|
if (
|
|
@@ -1581,13 +1822,14 @@ class DefaultStreamTextResult<
|
|
|
1581
1822
|
});
|
|
1582
1823
|
|
|
1583
1824
|
// initialize the stitchable stream and the transformed stream:
|
|
1584
|
-
const stitchableStream =
|
|
1825
|
+
const stitchableStream =
|
|
1826
|
+
createStitchableStream<InternalTextStreamPart<TOOLS>>();
|
|
1585
1827
|
this.addStream = stitchableStream.addStream;
|
|
1586
1828
|
this.closeStream = stitchableStream.close;
|
|
1587
1829
|
|
|
1588
1830
|
// resilient stream that handles abort signals and errors:
|
|
1589
1831
|
const reader = stitchableStream.stream.getReader();
|
|
1590
|
-
let stream = new ReadableStream<
|
|
1832
|
+
let stream = new ReadableStream<InternalTextStreamPart<TOOLS>>({
|
|
1591
1833
|
async start(controller) {
|
|
1592
1834
|
// send start event:
|
|
1593
1835
|
controller.enqueue({ type: 'start' });
|
|
@@ -1659,19 +1901,18 @@ class DefaultStreamTextResult<
|
|
|
1659
1901
|
}),
|
|
1660
1902
|
);
|
|
1661
1903
|
|
|
1662
|
-
//
|
|
1663
|
-
//
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
}
|
|
1904
|
+
// Transform each retry attempt independently. The retry boundary is
|
|
1905
|
+
// intercepted outside user transforms, so transforms may filter or
|
|
1906
|
+
// reconstruct any public stream part without losing logical isolation.
|
|
1907
|
+
stream = applyStreamTextTransforms({
|
|
1908
|
+
stream,
|
|
1909
|
+
transforms,
|
|
1910
|
+
tools: tools as TOOLS,
|
|
1911
|
+
stopStream() {
|
|
1912
|
+
stitchableStream.terminate();
|
|
1913
|
+
isRunning = false;
|
|
1914
|
+
},
|
|
1915
|
+
});
|
|
1675
1916
|
|
|
1676
1917
|
this.baseStream = stream
|
|
1677
1918
|
.pipeThrough(createOutputTransformStream(output ?? text()))
|
|
@@ -1681,6 +1922,12 @@ class DefaultStreamTextResult<
|
|
|
1681
1922
|
maxRetries: maxRetriesArg,
|
|
1682
1923
|
abortSignal,
|
|
1683
1924
|
});
|
|
1925
|
+
const { maxRetries: streamRetries } = prepareRetries({
|
|
1926
|
+
maxRetries: streamRetriesArg,
|
|
1927
|
+
abortSignal,
|
|
1928
|
+
parameter: 'streamRetries',
|
|
1929
|
+
defaultMaxRetries: 0,
|
|
1930
|
+
});
|
|
1684
1931
|
|
|
1685
1932
|
const callSettings = prepareLanguageModelCallOptions(settings);
|
|
1686
1933
|
|
|
@@ -2128,79 +2375,262 @@ class DefaultStreamTextResult<
|
|
|
2128
2375
|
const stepStartTimestampMs = now();
|
|
2129
2376
|
|
|
2130
2377
|
const { retry } = prepareRetries({ maxRetries, abortSignal });
|
|
2378
|
+
let hasNotifiedStepStart = false;
|
|
2379
|
+
|
|
2380
|
+
const callLanguageModel = () =>
|
|
2381
|
+
runInStepTracingChannelContext(() =>
|
|
2382
|
+
retry(async () =>
|
|
2383
|
+
streamLanguageModelCall({
|
|
2384
|
+
model: prepareStepResult?.model ?? model,
|
|
2385
|
+
tools: stepModelTools as TOOLS,
|
|
2386
|
+
toolOrder: stepToolOrder,
|
|
2387
|
+
toolChoice: prepareStepResult?.toolChoice ?? toolChoice,
|
|
2388
|
+
instructions: stepInstructions,
|
|
2389
|
+
messages: stepMessages,
|
|
2390
|
+
allowSystemInMessages,
|
|
2391
|
+
repairToolCall,
|
|
2392
|
+
refineToolInput,
|
|
2393
|
+
abortSignal,
|
|
2394
|
+
headers,
|
|
2395
|
+
includeRawChunks: include.rawChunks,
|
|
2396
|
+
providerOptions: stepProviderOptions,
|
|
2397
|
+
download,
|
|
2398
|
+
output,
|
|
2399
|
+
callId,
|
|
2400
|
+
executeLanguageModelCallInTelemetryContext:
|
|
2401
|
+
telemetryDispatcher.executeLanguageModelCall,
|
|
2402
|
+
toolsContext,
|
|
2403
|
+
experimental_sandbox: stepSandbox,
|
|
2404
|
+
onLanguageModelCallStart: filterNullable(
|
|
2405
|
+
onLanguageModelCallStart,
|
|
2406
|
+
telemetryDispatcher.onLanguageModelCallStart as
|
|
2407
|
+
| undefined
|
|
2408
|
+
| OnLanguageModelCallStartCallback,
|
|
2409
|
+
),
|
|
2410
|
+
onLanguageModelCallEnd: filterNullable(
|
|
2411
|
+
onLanguageModelCallEnd,
|
|
2412
|
+
telemetryDispatcher.onLanguageModelCallEnd as
|
|
2413
|
+
| undefined
|
|
2414
|
+
| OnLanguageModelCallEndCallback<TOOLS>,
|
|
2415
|
+
),
|
|
2416
|
+
onStart: async ({ promptMessages }) => {
|
|
2417
|
+
if (hasNotifiedStepStart) {
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
hasNotifiedStepStart = true;
|
|
2421
|
+
|
|
2422
|
+
await notify({
|
|
2423
|
+
event: {
|
|
2424
|
+
callId,
|
|
2425
|
+
provider: stepModel.provider,
|
|
2426
|
+
modelId: stepModel.modelId,
|
|
2427
|
+
stepNumber: recordedSteps.length,
|
|
2428
|
+
instructions: stepInstructions,
|
|
2429
|
+
messages: stepMessages,
|
|
2430
|
+
tools,
|
|
2431
|
+
toolChoice: prepareStepResult?.toolChoice ?? toolChoice,
|
|
2432
|
+
activeTools:
|
|
2433
|
+
prepareStepResult?.activeTools ?? activeTools,
|
|
2434
|
+
toolOrder: stepToolOrder,
|
|
2435
|
+
steps: [...recordedSteps],
|
|
2436
|
+
providerOptions: stepProviderOptions,
|
|
2437
|
+
runtimeContext,
|
|
2438
|
+
toolsContext,
|
|
2439
|
+
output,
|
|
2440
|
+
promptMessages,
|
|
2441
|
+
stepTools,
|
|
2442
|
+
stepToolChoice,
|
|
2443
|
+
},
|
|
2444
|
+
callbacks: [onStepStart, telemetryDispatcher.onStepStart],
|
|
2445
|
+
});
|
|
2446
|
+
},
|
|
2447
|
+
_internal: {
|
|
2448
|
+
now,
|
|
2449
|
+
},
|
|
2450
|
+
...stepCallSettings,
|
|
2451
|
+
}),
|
|
2452
|
+
),
|
|
2453
|
+
);
|
|
2454
|
+
|
|
2455
|
+
const initialLanguageModelCall = await callLanguageModel();
|
|
2456
|
+
let request = initialLanguageModelCall.request;
|
|
2457
|
+
let response = initialLanguageModelCall.response;
|
|
2458
|
+
let languageModelStreamReader =
|
|
2459
|
+
initialLanguageModelCall.stream.getReader();
|
|
2460
|
+
let automaticStreamRetryCount = 0;
|
|
2461
|
+
let callbackStreamRetryCount = 0;
|
|
2462
|
+
let bufferedAttemptParts: LanguageModelStreamPart<TOOLS>[] = [];
|
|
2463
|
+
const outputChunksHandledBeforeBuffering = new WeakSet<object>();
|
|
2464
|
+
const openTextParts = new Set<string>();
|
|
2465
|
+
const openReasoningParts = new Set<string>();
|
|
2466
|
+
let enqueueStreamRetryAttemptBoundary = false;
|
|
2467
|
+
const shouldBufferToolParts =
|
|
2468
|
+
streamRetries > 0 || canRetryStreamViaOnError;
|
|
2469
|
+
|
|
2470
|
+
const languageModelStream = new ReadableStream<
|
|
2471
|
+
LanguageModelStreamPart<TOOLS> | StreamRetryAttemptBoundaryPart
|
|
2472
|
+
>({
|
|
2473
|
+
async pull(controller) {
|
|
2474
|
+
const enqueueAttemptPart = (
|
|
2475
|
+
part: LanguageModelStreamPart<TOOLS>,
|
|
2476
|
+
) => {
|
|
2477
|
+
switch (part.type) {
|
|
2478
|
+
case 'text-start':
|
|
2479
|
+
openTextParts.add(part.id);
|
|
2480
|
+
break;
|
|
2481
|
+
case 'text-end':
|
|
2482
|
+
openTextParts.delete(part.id);
|
|
2483
|
+
break;
|
|
2484
|
+
case 'reasoning-start':
|
|
2485
|
+
openReasoningParts.add(part.id);
|
|
2486
|
+
break;
|
|
2487
|
+
case 'reasoning-end':
|
|
2488
|
+
openReasoningParts.delete(part.id);
|
|
2489
|
+
break;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
controller.enqueue(part);
|
|
2493
|
+
};
|
|
2494
|
+
|
|
2495
|
+
const flushBufferedAttemptParts = () => {
|
|
2496
|
+
for (const part of bufferedAttemptParts) {
|
|
2497
|
+
enqueueAttemptPart(part);
|
|
2498
|
+
}
|
|
2499
|
+
bufferedAttemptParts = [];
|
|
2500
|
+
};
|
|
2501
|
+
|
|
2502
|
+
const closeOpenAttemptParts = () => {
|
|
2503
|
+
for (const id of openTextParts) {
|
|
2504
|
+
controller.enqueue({ type: 'text-end', id });
|
|
2505
|
+
}
|
|
2506
|
+
openTextParts.clear();
|
|
2507
|
+
|
|
2508
|
+
for (const id of openReasoningParts) {
|
|
2509
|
+
controller.enqueue({ type: 'reasoning-end', id });
|
|
2510
|
+
}
|
|
2511
|
+
openReasoningParts.clear();
|
|
2512
|
+
};
|
|
2513
|
+
|
|
2514
|
+
while (true) {
|
|
2515
|
+
const { done, value } = await languageModelStreamReader.read();
|
|
2516
|
+
|
|
2517
|
+
if (enqueueStreamRetryAttemptBoundary) {
|
|
2518
|
+
controller.enqueue(
|
|
2519
|
+
createStreamRetryAttemptBoundaryPart({
|
|
2520
|
+
warnings:
|
|
2521
|
+
!done && value.type === 'model-call-start'
|
|
2522
|
+
? value.warnings
|
|
2523
|
+
: [],
|
|
2524
|
+
}),
|
|
2525
|
+
);
|
|
2526
|
+
enqueueStreamRetryAttemptBoundary = false;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
if (done) {
|
|
2530
|
+
flushBufferedAttemptParts();
|
|
2531
|
+
controller.close();
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
const isToolPart =
|
|
2536
|
+
value.type === 'tool-input-start' ||
|
|
2537
|
+
value.type === 'tool-input-delta' ||
|
|
2538
|
+
value.type === 'tool-input-end' ||
|
|
2539
|
+
value.type === 'tool-call' ||
|
|
2540
|
+
value.type === 'tool-approval-request' ||
|
|
2541
|
+
value.type === 'tool-approval-response' ||
|
|
2542
|
+
value.type === 'tool-result' ||
|
|
2543
|
+
value.type === 'tool-error';
|
|
2544
|
+
|
|
2545
|
+
if (value.type === 'model-call-end') {
|
|
2546
|
+
flushBufferedAttemptParts();
|
|
2547
|
+
enqueueAttemptPart(value);
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
if (
|
|
2552
|
+
shouldBufferToolParts &&
|
|
2553
|
+
value.type !== 'error' &&
|
|
2554
|
+
(isToolPart || bufferedAttemptParts.length > 0)
|
|
2555
|
+
) {
|
|
2556
|
+
if (isOutputChunk(value)) {
|
|
2557
|
+
clearFirstChunkTimeout();
|
|
2558
|
+
resetChunkTimeout();
|
|
2559
|
+
outputChunksHandledBeforeBuffering.add(value);
|
|
2560
|
+
}
|
|
2131
2561
|
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
)
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
output,
|
|
2190
|
-
promptMessages,
|
|
2191
|
-
stepTools,
|
|
2192
|
-
stepToolChoice,
|
|
2193
|
-
},
|
|
2194
|
-
callbacks: [onStepStart, telemetryDispatcher.onStepStart],
|
|
2562
|
+
bufferedAttemptParts.push(value);
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
if (value.type !== 'error') {
|
|
2567
|
+
enqueueAttemptPart(value);
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
await notify({
|
|
2572
|
+
event: { chunk: value },
|
|
2573
|
+
callbacks: onChunk,
|
|
2574
|
+
});
|
|
2575
|
+
const error = wrapGatewayError(value.error);
|
|
2576
|
+
let onErrorResult: unknown;
|
|
2577
|
+
try {
|
|
2578
|
+
onErrorResult = await onError({ error });
|
|
2579
|
+
} catch {}
|
|
2580
|
+
const callbackRequestedRetry =
|
|
2581
|
+
canRetryStreamViaOnError &&
|
|
2582
|
+
typeof onErrorResult === 'object' &&
|
|
2583
|
+
onErrorResult != null &&
|
|
2584
|
+
'retry' in onErrorResult &&
|
|
2585
|
+
onErrorResult.retry === true;
|
|
2586
|
+
const automaticRetry =
|
|
2587
|
+
automaticStreamRetryCount < streamRetries;
|
|
2588
|
+
const callbackRetry =
|
|
2589
|
+
!automaticRetry &&
|
|
2590
|
+
callbackRequestedRetry &&
|
|
2591
|
+
callbackStreamRetryCount < 1;
|
|
2592
|
+
|
|
2593
|
+
if (!automaticRetry && !callbackRetry) {
|
|
2594
|
+
flushBufferedAttemptParts();
|
|
2595
|
+
errorsHandledForStreamRetry.add(value.error);
|
|
2596
|
+
controller.enqueue(value);
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
if (automaticRetry) {
|
|
2601
|
+
automaticStreamRetryCount++;
|
|
2602
|
+
} else {
|
|
2603
|
+
callbackStreamRetryCount++;
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
await languageModelStreamReader.cancel(error);
|
|
2607
|
+
bufferedAttemptParts = [];
|
|
2608
|
+
closeOpenAttemptParts();
|
|
2609
|
+
|
|
2610
|
+
let retryLanguageModelCall: Awaited<
|
|
2611
|
+
ReturnType<typeof callLanguageModel>
|
|
2612
|
+
>;
|
|
2613
|
+
try {
|
|
2614
|
+
retryLanguageModelCall = await callLanguageModel();
|
|
2615
|
+
} catch (retryError) {
|
|
2616
|
+
controller.enqueue({
|
|
2617
|
+
type: 'error',
|
|
2618
|
+
error: retryError,
|
|
2195
2619
|
});
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2620
|
+
controller.close();
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
request = retryLanguageModelCall.request;
|
|
2624
|
+
response = retryLanguageModelCall.response;
|
|
2625
|
+
languageModelStreamReader =
|
|
2626
|
+
retryLanguageModelCall.stream.getReader();
|
|
2627
|
+
enqueueStreamRetryAttemptBoundary = true;
|
|
2628
|
+
}
|
|
2629
|
+
},
|
|
2630
|
+
cancel(reason) {
|
|
2631
|
+
return languageModelStreamReader.cancel(reason);
|
|
2632
|
+
},
|
|
2633
|
+
});
|
|
2204
2634
|
|
|
2205
2635
|
startFirstChunkTimeout();
|
|
2206
2636
|
|
|
@@ -2259,14 +2689,14 @@ class DefaultStreamTextResult<
|
|
|
2259
2689
|
|
|
2260
2690
|
// Conditionally include request.body based on include settings.
|
|
2261
2691
|
// Large payloads (e.g., base64-encoded images) can cause memory issues.
|
|
2262
|
-
const
|
|
2692
|
+
const getStepRequest = (): LanguageModelRequestMetadata => ({
|
|
2263
2693
|
...request,
|
|
2264
2694
|
body: include.requestBody ? request?.body : undefined,
|
|
2265
2695
|
messages: include.requestMessages
|
|
2266
2696
|
? cloneModelMessages(stepMessages)
|
|
2267
2697
|
: undefined,
|
|
2268
|
-
};
|
|
2269
|
-
recordedRequestMessages =
|
|
2698
|
+
});
|
|
2699
|
+
recordedRequestMessages = getStepRequest().messages ?? [];
|
|
2270
2700
|
|
|
2271
2701
|
const stepToolCalls: TypedToolCall<TOOLS>[] = [];
|
|
2272
2702
|
const stepToolOutputs: ToolOutput<TOOLS>[] = [];
|
|
@@ -2287,10 +2717,10 @@ class DefaultStreamTextResult<
|
|
|
2287
2717
|
let stepUsage: LanguageModelUsage = createNullLanguageModelUsage();
|
|
2288
2718
|
let stepProviderMetadata: ProviderMetadata | undefined;
|
|
2289
2719
|
let stepFirstChunk = true;
|
|
2290
|
-
|
|
2720
|
+
const createModelCallPerformance = (): Omit<
|
|
2291
2721
|
StepResultPerformance,
|
|
2292
2722
|
'stepTimeMs' | 'toolExecutionMs'
|
|
2293
|
-
>
|
|
2723
|
+
> => ({
|
|
2294
2724
|
responseTimeMs: 0,
|
|
2295
2725
|
effectiveOutputTokensPerSecond: 0,
|
|
2296
2726
|
outputTokensPerSecond: undefined,
|
|
@@ -2298,26 +2728,63 @@ class DefaultStreamTextResult<
|
|
|
2298
2728
|
effectiveTotalTokensPerSecond: 0,
|
|
2299
2729
|
timeToFirstOutputMs: undefined,
|
|
2300
2730
|
timeBetweenOutputChunksMs: undefined,
|
|
2301
|
-
};
|
|
2731
|
+
});
|
|
2732
|
+
let modelCallPerformance = createModelCallPerformance();
|
|
2302
2733
|
const toolExecutionMs: Record<string, number> = {};
|
|
2303
|
-
|
|
2734
|
+
const createStepResponse = () => ({
|
|
2304
2735
|
id: generateId(),
|
|
2305
2736
|
timestamp: new Date(),
|
|
2306
2737
|
modelId: model.modelId,
|
|
2307
|
-
};
|
|
2738
|
+
});
|
|
2739
|
+
let stepResponse: {
|
|
2740
|
+
id: string;
|
|
2741
|
+
timestamp: Date;
|
|
2742
|
+
modelId: string;
|
|
2743
|
+
} = createStepResponse();
|
|
2308
2744
|
|
|
2309
2745
|
// maps provider-assigned IDs to stream-unique IDs for the text and
|
|
2310
2746
|
// reasoning parts that are active in this step
|
|
2311
2747
|
const textPartIds = new Map<string, string>();
|
|
2312
2748
|
const reasoningPartIds = new Map<string, string>();
|
|
2313
2749
|
|
|
2750
|
+
const enqueueStepPart = (
|
|
2751
|
+
controller: TransformStreamDefaultController<
|
|
2752
|
+
InternalTextStreamPart<TOOLS>
|
|
2753
|
+
>,
|
|
2754
|
+
part: TextStreamPart<TOOLS>,
|
|
2755
|
+
) => {
|
|
2756
|
+
controller.enqueue(part);
|
|
2757
|
+
};
|
|
2758
|
+
|
|
2314
2759
|
self.addStream(
|
|
2315
2760
|
streamWithToolResults.pipeThrough(
|
|
2316
2761
|
new TransformStream<
|
|
2317
2762
|
ExecuteToolsStreamPart<TOOLS>,
|
|
2318
|
-
|
|
2763
|
+
InternalTextStreamPart<TOOLS>
|
|
2319
2764
|
>({
|
|
2320
2765
|
async transform(chunk, controller): Promise<void> {
|
|
2766
|
+
if (isStreamRetryAttemptBoundaryPart(chunk)) {
|
|
2767
|
+
warnings = chunk.warnings;
|
|
2768
|
+
stepFinishReason = 'other';
|
|
2769
|
+
stepRawFinishReason = undefined;
|
|
2770
|
+
hasReceivedTerminalChunk = false;
|
|
2771
|
+
hasReceivedOutputChunk = false;
|
|
2772
|
+
stepUsage = createNullLanguageModelUsage();
|
|
2773
|
+
stepProviderMetadata = undefined;
|
|
2774
|
+
modelCallPerformance = createModelCallPerformance();
|
|
2775
|
+
stepResponse = createStepResponse();
|
|
2776
|
+
textPartIds.clear();
|
|
2777
|
+
reasoningPartIds.clear();
|
|
2778
|
+
|
|
2779
|
+
controller.enqueue({
|
|
2780
|
+
[streamRetryBoundarySymbol]: {
|
|
2781
|
+
request: getStepRequest(),
|
|
2782
|
+
warnings,
|
|
2783
|
+
} satisfies StreamRetryBoundaryMetadata,
|
|
2784
|
+
});
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2321
2788
|
if (chunk.type === 'model-call-start') {
|
|
2322
2789
|
warnings = chunk.warnings;
|
|
2323
2790
|
return; // stream start chunks are sent immediately and do not count as first chunk
|
|
@@ -2327,9 +2794,9 @@ class DefaultStreamTextResult<
|
|
|
2327
2794
|
stepFirstChunk = false;
|
|
2328
2795
|
|
|
2329
2796
|
// Step start:
|
|
2330
|
-
controller
|
|
2797
|
+
enqueueStepPart(controller, {
|
|
2331
2798
|
type: 'start-step',
|
|
2332
|
-
request:
|
|
2799
|
+
request: getStepRequest(),
|
|
2333
2800
|
warnings: warnings ?? [],
|
|
2334
2801
|
});
|
|
2335
2802
|
}
|
|
@@ -2337,13 +2804,21 @@ class DefaultStreamTextResult<
|
|
|
2337
2804
|
const chunkType = chunk.type;
|
|
2338
2805
|
|
|
2339
2806
|
if (isOutputChunk(chunk)) {
|
|
2340
|
-
|
|
2807
|
+
const timeoutHandledBeforeBuffering =
|
|
2808
|
+
outputChunksHandledBeforeBuffering.has(chunk);
|
|
2809
|
+
|
|
2810
|
+
if (
|
|
2811
|
+
!hasReceivedOutputChunk &&
|
|
2812
|
+
!timeoutHandledBeforeBuffering
|
|
2813
|
+
) {
|
|
2341
2814
|
// Clear before forwarding the first output so a timeout
|
|
2342
2815
|
// cannot race with already-visible generated content.
|
|
2343
2816
|
clearFirstChunkTimeout();
|
|
2344
2817
|
}
|
|
2345
2818
|
hasReceivedOutputChunk = true;
|
|
2346
|
-
|
|
2819
|
+
if (!timeoutHandledBeforeBuffering) {
|
|
2820
|
+
resetChunkTimeout();
|
|
2821
|
+
}
|
|
2347
2822
|
}
|
|
2348
2823
|
|
|
2349
2824
|
switch (chunkType) {
|
|
@@ -2355,14 +2830,14 @@ class DefaultStreamTextResult<
|
|
|
2355
2830
|
case 'tool-input-end':
|
|
2356
2831
|
case 'tool-input-delta':
|
|
2357
2832
|
case 'tool-approval-request': {
|
|
2358
|
-
controller
|
|
2833
|
+
enqueueStepPart(controller, chunk);
|
|
2359
2834
|
break;
|
|
2360
2835
|
}
|
|
2361
2836
|
|
|
2362
2837
|
case 'text-start': {
|
|
2363
2838
|
const id = reserveTextPartId(chunk.id);
|
|
2364
2839
|
textPartIds.set(chunk.id, id);
|
|
2365
|
-
controller
|
|
2840
|
+
enqueueStepPart(controller, { ...chunk, id });
|
|
2366
2841
|
break;
|
|
2367
2842
|
}
|
|
2368
2843
|
|
|
@@ -2371,7 +2846,7 @@ class DefaultStreamTextResult<
|
|
|
2371
2846
|
chunk.text.length > 0 ||
|
|
2372
2847
|
chunk.providerMetadata != null
|
|
2373
2848
|
) {
|
|
2374
|
-
controller
|
|
2849
|
+
enqueueStepPart(controller, {
|
|
2375
2850
|
...chunk,
|
|
2376
2851
|
id: textPartIds.get(chunk.id) ?? chunk.id,
|
|
2377
2852
|
});
|
|
@@ -2380,7 +2855,7 @@ class DefaultStreamTextResult<
|
|
|
2380
2855
|
}
|
|
2381
2856
|
|
|
2382
2857
|
case 'text-end': {
|
|
2383
|
-
controller
|
|
2858
|
+
enqueueStepPart(controller, {
|
|
2384
2859
|
...chunk,
|
|
2385
2860
|
id: textPartIds.get(chunk.id) ?? chunk.id,
|
|
2386
2861
|
});
|
|
@@ -2391,12 +2866,12 @@ class DefaultStreamTextResult<
|
|
|
2391
2866
|
case 'reasoning-start': {
|
|
2392
2867
|
const id = reserveReasoningPartId(chunk.id);
|
|
2393
2868
|
reasoningPartIds.set(chunk.id, id);
|
|
2394
|
-
controller
|
|
2869
|
+
enqueueStepPart(controller, { ...chunk, id });
|
|
2395
2870
|
break;
|
|
2396
2871
|
}
|
|
2397
2872
|
|
|
2398
2873
|
case 'reasoning-delta': {
|
|
2399
|
-
controller
|
|
2874
|
+
enqueueStepPart(controller, {
|
|
2400
2875
|
...chunk,
|
|
2401
2876
|
id: reasoningPartIds.get(chunk.id) ?? chunk.id,
|
|
2402
2877
|
});
|
|
@@ -2404,7 +2879,7 @@ class DefaultStreamTextResult<
|
|
|
2404
2879
|
}
|
|
2405
2880
|
|
|
2406
2881
|
case 'reasoning-end': {
|
|
2407
|
-
controller
|
|
2882
|
+
enqueueStepPart(controller, {
|
|
2408
2883
|
...chunk,
|
|
2409
2884
|
id: reasoningPartIds.get(chunk.id) ?? chunk.id,
|
|
2410
2885
|
});
|
|
@@ -2413,20 +2888,20 @@ class DefaultStreamTextResult<
|
|
|
2413
2888
|
}
|
|
2414
2889
|
|
|
2415
2890
|
case 'tool-call': {
|
|
2416
|
-
controller
|
|
2891
|
+
enqueueStepPart(controller, chunk);
|
|
2417
2892
|
// store tool calls for onEnd callback and toolCalls promise:
|
|
2418
2893
|
stepToolCalls.push(chunk);
|
|
2419
2894
|
break;
|
|
2420
2895
|
}
|
|
2421
2896
|
|
|
2422
2897
|
case 'tool-approval-response': {
|
|
2423
|
-
controller
|
|
2898
|
+
enqueueStepPart(controller, chunk);
|
|
2424
2899
|
stepToolApprovalResponses.push(chunk);
|
|
2425
2900
|
break;
|
|
2426
2901
|
}
|
|
2427
2902
|
|
|
2428
2903
|
case 'tool-result': {
|
|
2429
|
-
controller
|
|
2904
|
+
enqueueStepPart(controller, chunk);
|
|
2430
2905
|
|
|
2431
2906
|
if (!chunk.preliminary) {
|
|
2432
2907
|
stepToolOutputs.push(chunk);
|
|
@@ -2436,7 +2911,7 @@ class DefaultStreamTextResult<
|
|
|
2436
2911
|
}
|
|
2437
2912
|
|
|
2438
2913
|
case 'tool-error': {
|
|
2439
|
-
controller
|
|
2914
|
+
enqueueStepPart(controller, chunk);
|
|
2440
2915
|
stepToolOutputs.push(chunk);
|
|
2441
2916
|
break;
|
|
2442
2917
|
}
|
|
@@ -2471,14 +2946,14 @@ class DefaultStreamTextResult<
|
|
|
2471
2946
|
|
|
2472
2947
|
case 'error': {
|
|
2473
2948
|
hasReceivedTerminalChunk = true;
|
|
2474
|
-
controller
|
|
2949
|
+
enqueueStepPart(controller, chunk);
|
|
2475
2950
|
stepFinishReason = 'error';
|
|
2476
2951
|
break;
|
|
2477
2952
|
}
|
|
2478
2953
|
|
|
2479
2954
|
case 'raw': {
|
|
2480
2955
|
if (include.rawChunks) {
|
|
2481
|
-
controller
|
|
2956
|
+
enqueueStepPart(controller, chunk);
|
|
2482
2957
|
}
|
|
2483
2958
|
break;
|
|
2484
2959
|
}
|
|
@@ -2496,7 +2971,7 @@ class DefaultStreamTextResult<
|
|
|
2496
2971
|
// output instead of recording an empty step. incomplete
|
|
2497
2972
|
// streams with partial output retain the partial result:
|
|
2498
2973
|
if (!hasReceivedTerminalChunk && !hasReceivedOutputChunk) {
|
|
2499
|
-
controller
|
|
2974
|
+
enqueueStepPart(controller, {
|
|
2500
2975
|
type: 'error',
|
|
2501
2976
|
error: new NoOutputGeneratedError({
|
|
2502
2977
|
message:
|
|
@@ -2528,7 +3003,7 @@ class DefaultStreamTextResult<
|
|
|
2528
3003
|
},
|
|
2529
3004
|
};
|
|
2530
3005
|
|
|
2531
|
-
controller
|
|
3006
|
+
enqueueStepPart(controller, finishStepPart);
|
|
2532
3007
|
|
|
2533
3008
|
const combinedUsage = addLanguageModelUsage(usage, stepUsage);
|
|
2534
3009
|
|
|
@@ -2609,7 +3084,7 @@ class DefaultStreamTextResult<
|
|
|
2609
3084
|
}),
|
|
2610
3085
|
);
|
|
2611
3086
|
} catch (error) {
|
|
2612
|
-
controller
|
|
3087
|
+
enqueueStepPart(controller, {
|
|
2613
3088
|
type: 'error',
|
|
2614
3089
|
error,
|
|
2615
3090
|
});
|
|
@@ -2617,7 +3092,7 @@ class DefaultStreamTextResult<
|
|
|
2617
3092
|
self.closeStream();
|
|
2618
3093
|
}
|
|
2619
3094
|
} else {
|
|
2620
|
-
controller
|
|
3095
|
+
enqueueStepPart(controller, {
|
|
2621
3096
|
type: 'finish',
|
|
2622
3097
|
finishReason: stepFinishReason,
|
|
2623
3098
|
rawFinishReason: stepRawFinishReason,
|