ai 7.0.68 → 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 +20 -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/03-ai-sdk-harnesses/02-harness-agent.mdx +51 -4
- 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`
|
|
@@ -180,6 +180,11 @@ End every session explicitly:
|
|
|
180
180
|
Use `destroy()` for one-off scripts and tests. Use `detach()` or `stop()` for
|
|
181
181
|
HTTP routes that need multi-turn continuity.
|
|
182
182
|
|
|
183
|
+
When you pass `sandboxSession` to `agent.createSession()`, the caller retains
|
|
184
|
+
ownership of that sandbox. `session.stop()` and `session.destroy()` still end
|
|
185
|
+
the harness runtime but do not stop or destroy the supplied sandbox session.
|
|
186
|
+
In this case, the agent does not need a `sandbox` provider in its constructor.
|
|
187
|
+
|
|
183
188
|
```ts
|
|
184
189
|
const session = await agent.createSession({ sessionId: chatId });
|
|
185
190
|
|
|
@@ -401,10 +406,7 @@ const sandboxFromSnapshot = await Sandbox.create({
|
|
|
401
406
|
|
|
402
407
|
const agent = new HarnessAgent({
|
|
403
408
|
harness: claudeCode,
|
|
404
|
-
sandbox: createVercelSandbox({
|
|
405
|
-
sandbox: sandboxFromSnapshot,
|
|
406
|
-
bridgePorts: [4000],
|
|
407
|
-
}),
|
|
409
|
+
sandbox: createVercelSandbox({ sandbox: sandboxFromSnapshot }),
|
|
408
410
|
sandboxConfig,
|
|
409
411
|
});
|
|
410
412
|
|
|
@@ -437,6 +439,51 @@ console.log(preparation.identity);
|
|
|
437
439
|
Adapter-specific settings belong on the adapter factory, for example
|
|
438
440
|
`createCodex({ reasoningEffort: 'high' })`.
|
|
439
441
|
|
|
442
|
+
## Custom Sandbox Orchestration
|
|
443
|
+
|
|
444
|
+
When your application creates and manages sandboxes itself, prepare the network
|
|
445
|
+
sandbox session first and then pass that same session to `agent.createSession()`.
|
|
446
|
+
The agent does not need a sandbox provider and does not stop or destroy the
|
|
447
|
+
caller-owned sandbox.
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
import { HarnessAgent, prepareSandboxForHarness } from '@ai-sdk/harness/agent';
|
|
451
|
+
import { claudeCode } from '@ai-sdk/harness-claude-code';
|
|
452
|
+
import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
|
|
453
|
+
import { Sandbox } from '@vercel/sandbox';
|
|
454
|
+
|
|
455
|
+
const sandbox = await Sandbox.create({
|
|
456
|
+
runtime: 'node24',
|
|
457
|
+
ports: [4000],
|
|
458
|
+
});
|
|
459
|
+
const sandboxProvider = createVercelSandbox({ sandbox });
|
|
460
|
+
const sandboxSession = await sandboxProvider.createSession();
|
|
461
|
+
|
|
462
|
+
await prepareSandboxForHarness({
|
|
463
|
+
session: sandboxSession.restricted(),
|
|
464
|
+
harnesses: [claudeCode],
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const agent = new HarnessAgent({ harness: claudeCode });
|
|
468
|
+
const session = await agent.createSession({ sandboxSession });
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
const result = await agent.stream({
|
|
472
|
+
session,
|
|
473
|
+
prompt: 'Create a short TODO.md for this repository.',
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
for await (const part of result.stream) {
|
|
477
|
+
if (part.type === 'text-delta') {
|
|
478
|
+
process.stdout.write(part.text);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
} finally {
|
|
482
|
+
await session.destroy();
|
|
483
|
+
await sandbox.stop();
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
440
487
|
## Next Steps
|
|
441
488
|
|
|
442
489
|
- [Tools](/docs/ai-sdk-harnesses/tools) for built-in and host-executed tools.
|
|
@@ -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;
|