ai 7.0.69 → 7.0.70
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 +13 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +88 -14
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +1 -1
- package/docs/03-agents/07-workflow-agent.mdx +8 -3
- package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +16 -0
- package/package.json +3 -3
- package/src/generate-object/stream-object.ts +63 -3
- package/src/generate-text/execute-tools-from-stream.ts +5 -0
- package/src/generate-text/generate-text.ts +12 -8
- package/src/generate-text/is-tool-execution-allowed-finish-reason.ts +7 -0
- package/src/generate-text/stream-text.ts +6 -7
- package/src/ui/convert-to-model-messages.ts +1 -1
- package/src/ui/process-ui-message-stream.ts +17 -0
- package/src/ui-message-stream/ui-message-chunks.ts +9 -0
package/dist/internal/index.js
CHANGED
|
@@ -36,10 +36,10 @@ For simpler use cases that don't need durability, use [`ToolLoopAgent`](/docs/ag
|
|
|
36
36
|
## Installation
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npm install @ai-sdk/workflow workflow
|
|
39
|
+
npm install @ai-sdk/workflow workflow@beta
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
`@ai-sdk/workflow` requires the `ai` package and `zod`
|
|
42
|
+
`@ai-sdk/workflow` requires Workflow 5, which is currently available under the `beta` tag, as well as the `ai` package and `zod` peer dependencies. The `workflow` package provides the Workflow DevKit runtime (`getWritable`, `'use workflow'`, `'use step'`).
|
|
43
43
|
|
|
44
44
|
## Creating a WorkflowAgent
|
|
45
45
|
|
|
@@ -185,6 +185,11 @@ return createUIMessageStreamResponse({
|
|
|
185
185
|
});
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
+
The transform also forwards `reset-step` events emitted by `WorkflowAgent` on
|
|
189
|
+
retries.
|
|
190
|
+
Clients remove partial parts from the failed model-call step before processing
|
|
191
|
+
the retried output.
|
|
192
|
+
|
|
188
193
|
## Resumable Streaming with WorkflowChatTransport
|
|
189
194
|
|
|
190
195
|
Workflow functions can time out or be interrupted by network failures. `WorkflowChatTransport` is a [`ChatTransport`](/docs/ai-sdk-ui/transport) implementation that handles these interruptions automatically — it detects when a stream ends without a `finish` event and reconnects to resume from where it left off.
|
|
@@ -589,7 +594,7 @@ export type MyAgentUIMessage = InferWorkflowAgentUIMessage<typeof myAgent>;
|
|
|
589
594
|
Install the new package alongside `workflow`:
|
|
590
595
|
|
|
591
596
|
```bash
|
|
592
|
-
npm install @ai-sdk/workflow
|
|
597
|
+
npm install @ai-sdk/workflow workflow@beta
|
|
593
598
|
```
|
|
594
599
|
|
|
595
600
|
### Write `ModelCallStreamPart`, not `UIMessageChunk`
|
|
@@ -442,6 +442,22 @@ data: {"type":"finish-step"}
|
|
|
442
442
|
|
|
443
443
|
```
|
|
444
444
|
|
|
445
|
+
### Reset Step Part
|
|
446
|
+
|
|
447
|
+
Removes all message parts received since the most recent `start-step` part. If
|
|
448
|
+
there is no step boundary, it removes all parts from the current message. This
|
|
449
|
+
is useful when a streamed step is retried and partial output from the failed
|
|
450
|
+
attempt must be invalidated before replacement output is sent.
|
|
451
|
+
|
|
452
|
+
Format: Server-Sent Event with JSON object
|
|
453
|
+
|
|
454
|
+
Example:
|
|
455
|
+
|
|
456
|
+
```
|
|
457
|
+
data: {"type":"reset-step"}
|
|
458
|
+
|
|
459
|
+
```
|
|
460
|
+
|
|
445
461
|
### Finish Message Part
|
|
446
462
|
|
|
447
463
|
A part indicating the completion of a message.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.70",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.56",
|
|
46
46
|
"@ai-sdk/provider": "4.0.7",
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.28"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@edge-runtime/vm": "^5.0.0",
|
|
@@ -68,6 +68,12 @@ import { validateObjectGenerationInput } from './validate-object-generation-inpu
|
|
|
68
68
|
|
|
69
69
|
const originalGenerateId = createIdGenerator({ prefix: 'aiobj', size: 24 });
|
|
70
70
|
|
|
71
|
+
async function markPromiseAsHandled<T>(promise: Promise<T>): Promise<void> {
|
|
72
|
+
try {
|
|
73
|
+
await promise;
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
/**
|
|
72
78
|
* Callback that is set using the `onError` option.
|
|
73
79
|
*
|
|
@@ -656,6 +662,7 @@ class DefaultStreamObjectResult<
|
|
|
656
662
|
let providerMetadata: ProviderMetadata | undefined;
|
|
657
663
|
let object: RESULT | undefined;
|
|
658
664
|
let error: unknown | undefined;
|
|
665
|
+
let terminalError: { error: unknown } | undefined;
|
|
659
666
|
let msToFirstChunk: number | undefined = undefined;
|
|
660
667
|
|
|
661
668
|
let accumulatedText = '';
|
|
@@ -751,19 +758,35 @@ class DefaultStreamObjectResult<
|
|
|
751
758
|
break;
|
|
752
759
|
}
|
|
753
760
|
|
|
761
|
+
case 'error': {
|
|
762
|
+
if (terminalError === undefined) {
|
|
763
|
+
const wrappedError = wrapGatewayError(chunk.error);
|
|
764
|
+
terminalError = { error: wrappedError };
|
|
765
|
+
error = wrappedError;
|
|
766
|
+
finishReason = 'error';
|
|
767
|
+
self.rejectResultPromises(wrappedError);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
controller.enqueue(chunk);
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
|
|
754
774
|
case 'finish': {
|
|
755
775
|
if (textDelta !== '') {
|
|
756
776
|
controller.enqueue({ type: 'text-delta', textDelta });
|
|
757
777
|
}
|
|
758
778
|
|
|
759
|
-
finishReason =
|
|
779
|
+
finishReason =
|
|
780
|
+
terminalError === undefined
|
|
781
|
+
? chunk.finishReason.unified
|
|
782
|
+
: 'error';
|
|
760
783
|
|
|
761
784
|
usage = asLanguageModelUsage(chunk.usage);
|
|
762
785
|
providerMetadata = chunk.providerMetadata;
|
|
763
786
|
|
|
764
787
|
controller.enqueue({
|
|
765
788
|
...chunk,
|
|
766
|
-
finishReason
|
|
789
|
+
finishReason,
|
|
767
790
|
usage,
|
|
768
791
|
response: fullResponse,
|
|
769
792
|
});
|
|
@@ -774,6 +797,10 @@ class DefaultStreamObjectResult<
|
|
|
774
797
|
model: model.modelId,
|
|
775
798
|
});
|
|
776
799
|
|
|
800
|
+
if (terminalError !== undefined) {
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
|
|
777
804
|
self._usage.resolve(usage);
|
|
778
805
|
self._providerMetadata.resolve(providerMetadata);
|
|
779
806
|
self._warnings.resolve(warnings);
|
|
@@ -867,9 +894,19 @@ class DefaultStreamObjectResult<
|
|
|
867
894
|
}),
|
|
868
895
|
);
|
|
869
896
|
|
|
870
|
-
stitchableStream.addStream(transformedStream
|
|
897
|
+
stitchableStream.addStream(transformedStream, {
|
|
898
|
+
onError(error) {
|
|
899
|
+
const wrappedError = wrapGatewayError(error);
|
|
900
|
+
self.rejectResultPromises(wrappedError);
|
|
901
|
+
void notify({
|
|
902
|
+
event: { error: wrappedError },
|
|
903
|
+
callbacks: onError,
|
|
904
|
+
});
|
|
905
|
+
},
|
|
906
|
+
});
|
|
871
907
|
})()
|
|
872
908
|
.catch(async error => {
|
|
909
|
+
self.rejectResultPromises(error);
|
|
873
910
|
await telemetryDispatcher.onError?.({ callId, error });
|
|
874
911
|
|
|
875
912
|
stitchableStream.addStream(
|
|
@@ -888,6 +925,29 @@ class DefaultStreamObjectResult<
|
|
|
888
925
|
this.outputStrategy = outputStrategy;
|
|
889
926
|
}
|
|
890
927
|
|
|
928
|
+
private rejectResultPromises(error: unknown) {
|
|
929
|
+
this.rejectResultPromise({ delayedPromise: this._object, error });
|
|
930
|
+
this.rejectResultPromise({ delayedPromise: this._usage, error });
|
|
931
|
+
this.rejectResultPromise({ delayedPromise: this._providerMetadata, error });
|
|
932
|
+
this.rejectResultPromise({ delayedPromise: this._warnings, error });
|
|
933
|
+
this.rejectResultPromise({ delayedPromise: this._request, error });
|
|
934
|
+
this.rejectResultPromise({ delayedPromise: this._response, error });
|
|
935
|
+
this.rejectResultPromise({ delayedPromise: this._finishReason, error });
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
private rejectResultPromise<T>({
|
|
939
|
+
delayedPromise,
|
|
940
|
+
error,
|
|
941
|
+
}: {
|
|
942
|
+
delayedPromise: DelayedPromise<T>;
|
|
943
|
+
error: unknown;
|
|
944
|
+
}) {
|
|
945
|
+
if (delayedPromise.isPending()) {
|
|
946
|
+
delayedPromise.reject(error);
|
|
947
|
+
markPromiseAsHandled(delayedPromise.promise);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
891
951
|
get object() {
|
|
892
952
|
return this._object.promise;
|
|
893
953
|
}
|
|
@@ -11,6 +11,7 @@ import type { TimeoutConfiguration } from '../prompt/request-options';
|
|
|
11
11
|
import type { Telemetry, TelemetryDispatcher } from '../telemetry/telemetry';
|
|
12
12
|
import { getOwn } from '../util/get-own';
|
|
13
13
|
import { executeToolCall } from './execute-tool-call';
|
|
14
|
+
import { isToolExecutionAllowedFinishReason } from './is-tool-execution-allowed-finish-reason';
|
|
14
15
|
import { resolveToolApproval } from './resolve-tool-approval';
|
|
15
16
|
import type { LanguageModelStreamPart } from './stream-language-model-call';
|
|
16
17
|
import { maybeSignApproval } from './tool-approval-signature';
|
|
@@ -197,6 +198,10 @@ export function executeToolsFromStream<
|
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
case 'model-call-end': {
|
|
201
|
+
if (!isToolExecutionAllowedFinishReason(chunk.finishReason)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
200
205
|
await Promise.all(
|
|
201
206
|
toolCallsToExecute.map(async toolCall => {
|
|
202
207
|
try {
|
|
@@ -79,6 +79,7 @@ import type {
|
|
|
79
79
|
} from './generate-text-events';
|
|
80
80
|
import type { GenerateTextResult } from './generate-text-result';
|
|
81
81
|
import { DefaultGeneratedFile } from './generated-file';
|
|
82
|
+
import { isToolExecutionAllowedFinishReason } from './is-tool-execution-allowed-finish-reason';
|
|
82
83
|
import type {
|
|
83
84
|
OnLanguageModelCallEndCallback,
|
|
84
85
|
OnLanguageModelCallStartCallback,
|
|
@@ -1259,7 +1260,12 @@ export async function generateText<
|
|
|
1259
1260
|
);
|
|
1260
1261
|
const toolExecutionMs: Record<string, number> = {};
|
|
1261
1262
|
|
|
1262
|
-
if (
|
|
1263
|
+
if (
|
|
1264
|
+
stepExecutionTools != null &&
|
|
1265
|
+
isToolExecutionAllowedFinishReason(
|
|
1266
|
+
currentModelResponse.finishReason.unified,
|
|
1267
|
+
)
|
|
1268
|
+
) {
|
|
1263
1269
|
const toolExecutionResults = await executeTools({
|
|
1264
1270
|
toolCalls: clientToolCalls.filter(
|
|
1265
1271
|
toolCall =>
|
|
@@ -1432,13 +1438,11 @@ export async function generateText<
|
|
|
1432
1438
|
}
|
|
1433
1439
|
}
|
|
1434
1440
|
} while (
|
|
1435
|
-
// Continue
|
|
1436
|
-
//
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
clientToolCalls.length) ||
|
|
1441
|
-
pendingDeferredToolCalls.size > 0) &&
|
|
1441
|
+
// Continue only after all client tool calls have been executed or denied,
|
|
1442
|
+
// and if there are client results or pending deferred provider results.
|
|
1443
|
+
clientToolOutputs.length + deniedToolApprovalResponses.length ===
|
|
1444
|
+
clientToolCalls.length &&
|
|
1445
|
+
(clientToolCalls.length > 0 || pendingDeferredToolCalls.size > 0) &&
|
|
1442
1446
|
// continue until a stop condition is met:
|
|
1443
1447
|
!(await isStopConditionMet({ stopConditions, steps }))
|
|
1444
1448
|
);
|
|
@@ -2422,13 +2422,12 @@ class DefaultStreamTextResult<
|
|
|
2422
2422
|
cleanupStepTimeouts();
|
|
2423
2423
|
|
|
2424
2424
|
if (
|
|
2425
|
-
// Continue
|
|
2426
|
-
//
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
deniedToolApprovalResponses.length) ||
|
|
2425
|
+
// Continue only after all client tool calls have been executed or denied,
|
|
2426
|
+
// and if there are client results or pending deferred provider results.
|
|
2427
|
+
clientToolCalls.length ===
|
|
2428
|
+
clientToolOutputs.length +
|
|
2429
|
+
deniedToolApprovalResponses.length &&
|
|
2430
|
+
(clientToolCalls.length > 0 ||
|
|
2432
2431
|
pendingDeferredToolCalls.size > 0) &&
|
|
2433
2432
|
// continue until a stop condition is met:
|
|
2434
2433
|
!(await isStopConditionMet({
|
|
@@ -63,7 +63,7 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
63
63
|
part =>
|
|
64
64
|
!isToolUIPart(part) ||
|
|
65
65
|
part.state === 'approval-responded' ||
|
|
66
|
-
part.state === 'output-available' ||
|
|
66
|
+
(part.state === 'output-available' && part.preliminary !== true) ||
|
|
67
67
|
part.state === 'output-error' ||
|
|
68
68
|
part.state === 'output-denied',
|
|
69
69
|
),
|
|
@@ -887,6 +887,23 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
887
887
|
break;
|
|
888
888
|
}
|
|
889
889
|
|
|
890
|
+
case 'reset-step': {
|
|
891
|
+
const currentStepParts = getCurrentStepParts();
|
|
892
|
+
|
|
893
|
+
state.activeTextParts = createIdMap();
|
|
894
|
+
state.activeReasoningParts = createIdMap();
|
|
895
|
+
state.partialToolCalls = createIdMap();
|
|
896
|
+
|
|
897
|
+
if (currentStepParts.length > 0) {
|
|
898
|
+
state.message.parts.splice(
|
|
899
|
+
state.message.parts.length - currentStepParts.length,
|
|
900
|
+
currentStepParts.length,
|
|
901
|
+
);
|
|
902
|
+
write();
|
|
903
|
+
}
|
|
904
|
+
break;
|
|
905
|
+
}
|
|
906
|
+
|
|
890
907
|
case 'start': {
|
|
891
908
|
if (chunk.messageId != null) {
|
|
892
909
|
state.message.id = chunk.messageId;
|
|
@@ -183,6 +183,9 @@ export const uiMessageChunkSchema = lazySchema(() =>
|
|
|
183
183
|
z.looseObject({
|
|
184
184
|
type: z.literal('finish-step'),
|
|
185
185
|
}),
|
|
186
|
+
z.looseObject({
|
|
187
|
+
type: z.literal('reset-step'),
|
|
188
|
+
}),
|
|
186
189
|
z.looseObject({
|
|
187
190
|
type: z.literal('start'),
|
|
188
191
|
messageId: z.string().optional(),
|
|
@@ -378,6 +381,12 @@ export type UIMessageChunk<
|
|
|
378
381
|
| {
|
|
379
382
|
type: 'finish-step';
|
|
380
383
|
}
|
|
384
|
+
| {
|
|
385
|
+
/**
|
|
386
|
+
* Removes all message parts added during the current step.
|
|
387
|
+
*/
|
|
388
|
+
type: 'reset-step';
|
|
389
|
+
}
|
|
381
390
|
| {
|
|
382
391
|
type: 'start';
|
|
383
392
|
messageId?: string;
|