ai 7.0.38 → 7.0.39
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 +10 -0
- package/dist/index.d.ts +20 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +1 -1
- package/docs/03-agents/07-workflow-agent.mdx +3 -2
- package/docs/03-ai-sdk-core/36-translation.mdx +24 -11
- package/docs/03-ai-sdk-harnesses/06-workflow-utilities.mdx +225 -122
- package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +11 -4
- package/package.json +3 -3
- package/src/generate-object/generate-object.ts +12 -2
- package/src/generate-object/stream-object.ts +14 -1
package/dist/internal/index.js
CHANGED
|
@@ -447,8 +447,9 @@ This differs from `ToolLoopAgent`, which runs in memory and can carry richer Jav
|
|
|
447
447
|
### experimental_sandbox
|
|
448
448
|
|
|
449
449
|
Pass a sandbox session when tools need an execution environment. The sandbox is
|
|
450
|
-
available to tool `execute` functions as
|
|
451
|
-
`prepareStep`, where you can override it for the
|
|
450
|
+
available to tool descriptions and `execute` functions as
|
|
451
|
+
`experimental_sandbox`, and to `prepareStep`, where you can override it for the
|
|
452
|
+
current step:
|
|
452
453
|
|
|
453
454
|
```ts
|
|
454
455
|
const agent = new WorkflowAgent({
|
|
@@ -24,12 +24,11 @@ specification (`Experimental_SpeechTranslationModelV4`).
|
|
|
24
24
|
</Note>
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
|
+
import { openai } from '@ai-sdk/openai';
|
|
27
28
|
import { experimental_streamTranslate as streamTranslate } from 'ai';
|
|
28
29
|
|
|
29
30
|
const result = streamTranslate({
|
|
30
|
-
|
|
31
|
-
// speech translation model specification (Experimental_SpeechTranslationModelV4):
|
|
32
|
-
model: translationModel,
|
|
31
|
+
model: openai.translation('gpt-realtime-translate'),
|
|
33
32
|
audio: audioStream, // ReadableStream<Uint8Array | string>
|
|
34
33
|
inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
|
|
35
34
|
targetLanguage: 'es',
|
|
@@ -108,11 +107,12 @@ part for output text by design for now.
|
|
|
108
107
|
Use `outputAudioFormat` to request a specific audio format for translated
|
|
109
108
|
audio chunks. When absent, the provider default output format is used.
|
|
110
109
|
|
|
111
|
-
```ts highlight="
|
|
110
|
+
```ts highlight="8"
|
|
111
|
+
import { openai } from '@ai-sdk/openai';
|
|
112
112
|
import { experimental_streamTranslate as streamTranslate } from 'ai';
|
|
113
113
|
|
|
114
114
|
const result = streamTranslate({
|
|
115
|
-
model:
|
|
115
|
+
model: openai.translation('gpt-realtime-translate'),
|
|
116
116
|
audio: audioStream,
|
|
117
117
|
inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
|
|
118
118
|
outputAudioFormat: { type: 'audio/pcm', rate: 24000 },
|
|
@@ -125,16 +125,17 @@ const result = streamTranslate({
|
|
|
125
125
|
Translation models often have provider or model-specific settings which you can
|
|
126
126
|
set using the `providerOptions` parameter.
|
|
127
127
|
|
|
128
|
-
```ts highlight="
|
|
128
|
+
```ts highlight="9-13"
|
|
129
|
+
import { openai } from '@ai-sdk/openai';
|
|
129
130
|
import { experimental_streamTranslate as streamTranslate } from 'ai';
|
|
130
131
|
|
|
131
132
|
const result = streamTranslate({
|
|
132
|
-
model:
|
|
133
|
+
model: openai.translation('gpt-realtime-translate'),
|
|
133
134
|
audio: audioStream,
|
|
134
135
|
inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
|
|
135
136
|
targetLanguage: 'es',
|
|
136
137
|
providerOptions: {
|
|
137
|
-
|
|
138
|
+
openai: {
|
|
138
139
|
// provider-specific options
|
|
139
140
|
},
|
|
140
141
|
},
|
|
@@ -145,11 +146,12 @@ const result = streamTranslate({
|
|
|
145
146
|
|
|
146
147
|
Pass an `abortSignal` to cancel the translation:
|
|
147
148
|
|
|
148
|
-
```ts highlight="
|
|
149
|
+
```ts highlight="9"
|
|
150
|
+
import { openai } from '@ai-sdk/openai';
|
|
149
151
|
import { experimental_streamTranslate as streamTranslate } from 'ai';
|
|
150
152
|
|
|
151
153
|
const result = streamTranslate({
|
|
152
|
-
model:
|
|
154
|
+
model: openai.translation('gpt-realtime-translate'),
|
|
153
155
|
audio: audioStream,
|
|
154
156
|
inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
|
|
155
157
|
targetLanguage: 'es',
|
|
@@ -172,6 +174,7 @@ The error preserves the following information to help you log the issue:
|
|
|
172
174
|
handling.
|
|
173
175
|
|
|
174
176
|
```ts
|
|
177
|
+
import { openai } from '@ai-sdk/openai';
|
|
175
178
|
import {
|
|
176
179
|
experimental_streamTranslate as streamTranslate,
|
|
177
180
|
NoTranslationGeneratedError,
|
|
@@ -179,7 +182,7 @@ import {
|
|
|
179
182
|
|
|
180
183
|
try {
|
|
181
184
|
const result = streamTranslate({
|
|
182
|
-
model:
|
|
185
|
+
model: openai.translation('gpt-realtime-translate'),
|
|
183
186
|
audio: audioStream,
|
|
184
187
|
inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
|
|
185
188
|
targetLanguage: 'es',
|
|
@@ -194,3 +197,13 @@ try {
|
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
199
|
```
|
|
200
|
+
|
|
201
|
+
## Translation Models
|
|
202
|
+
|
|
203
|
+
| Provider | Model |
|
|
204
|
+
| --------------------------------------------------------------- | ----------------------------------- |
|
|
205
|
+
| [OpenAI](/providers/ai-sdk-providers/openai#translation-models) | `gpt-realtime-translate` |
|
|
206
|
+
| [Google](/providers/ai-sdk-providers/google#translation-models) | `gemini-3.5-live-translate-preview` |
|
|
207
|
+
|
|
208
|
+
Above are a small subset of the translation models supported by the AI SDK
|
|
209
|
+
providers. For more, see the respective provider documentation.
|
|
@@ -8,29 +8,35 @@ description: Run HarnessAgent turns as durable Workflow DevKit workflows.
|
|
|
8
8
|
`@ai-sdk/workflow-harness` provides helpers for running `HarnessAgent` turns
|
|
9
9
|
inside a [workflow](https://vercel.com/docs/workflow).
|
|
10
10
|
|
|
11
|
-
The package provides a serializable state machine and
|
|
12
|
-
|
|
11
|
+
The package provides a serializable state machine and runners for time-sliced
|
|
12
|
+
and semantic agent step turns. You call the appropriate runner from your own
|
|
13
|
+
`'use workflow'` and `'use step'` functions.
|
|
13
14
|
|
|
14
|
-
The core harness and workflow files are framework-independent. The HTTP
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
The core harness and workflow files are framework-independent. The HTTP handlers
|
|
16
|
+
shown below use Next.js as one example; adapt them to your runtime and Workflow
|
|
17
|
+
SDK integration. If you use Next.js, ensure you have
|
|
18
|
+
[configured your project for Workflow](https://workflow-sdk.dev/docs/getting-started/next)
|
|
19
|
+
before following the examples.
|
|
18
20
|
|
|
19
21
|
## Installation
|
|
20
22
|
|
|
21
23
|
<InstallPackages packages="@ai-sdk/workflow-harness workflow" />
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
shown in
|
|
25
|
+
In addition to the workflow specific packages, install the core harness package,
|
|
26
|
+
a harness adapter, and a sandbox provider as shown in
|
|
27
|
+
[HarnessAgent](/docs/ai-sdk-harnesses/harness-agent).
|
|
25
28
|
|
|
26
|
-
##
|
|
29
|
+
## Configuring the Harness Agent
|
|
27
30
|
|
|
28
|
-
The agent can be configured in the usual way.
|
|
31
|
+
The agent can be configured in the usual way, for the most part. When using
|
|
32
|
+
semantic agent steps, set `stopWhen` to `isStepCount(1)` so one call to
|
|
33
|
+
`stream()` completes one agent step. Omit it when using time slices.
|
|
29
34
|
|
|
30
|
-
```ts filename='harness-workflow/agent.ts'
|
|
35
|
+
```ts filename='harness-workflow/agent.ts' highlight="13-17"
|
|
31
36
|
import { HarnessAgent } from '@ai-sdk/harness/agent';
|
|
32
37
|
import { claudeCode } from '@ai-sdk/harness-claude-code';
|
|
33
38
|
import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
|
|
39
|
+
import { isStepCount } from 'ai';
|
|
34
40
|
|
|
35
41
|
export const agent = new HarnessAgent({
|
|
36
42
|
harness: claudeCode,
|
|
@@ -39,92 +45,210 @@ export const agent = new HarnessAgent({
|
|
|
39
45
|
ports: [4000],
|
|
40
46
|
}),
|
|
41
47
|
instructions: 'You are a helpful coding assistant.',
|
|
48
|
+
/*
|
|
49
|
+
* Only needed for semantic agent-step workflows.
|
|
50
|
+
* Omit this for time-sliced workflows.
|
|
51
|
+
*/
|
|
52
|
+
stopWhen: isStepCount(1),
|
|
42
53
|
});
|
|
43
54
|
```
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
pieces: a slice step that runs one time-boxed part of the harness turn, and a
|
|
47
|
-
workflow that calls that step until the turn finishes.
|
|
56
|
+
## Using Semantic Agent Steps
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
Semantic agent steps persist the harness turn after each agent step. Configure
|
|
59
|
+
the shared agent with the highlighted `stopWhen` option shown above, then call
|
|
60
|
+
`runHarnessAgentStep()` from a Workflow step.
|
|
61
|
+
|
|
62
|
+
### Defining the Agent Step
|
|
63
|
+
|
|
64
|
+
Keep the Workflow step in its own module and import the agent dynamically inside
|
|
65
|
+
the step body. This keeps the agent, sandbox provider, and other Node.js
|
|
66
|
+
dependencies out of the workflow bundle.
|
|
67
|
+
|
|
68
|
+
```ts filename='harness-workflow/agent-step.ts'
|
|
50
69
|
import {
|
|
51
|
-
|
|
70
|
+
runHarnessAgentStep,
|
|
52
71
|
type HarnessWorkflowState,
|
|
53
72
|
} from '@ai-sdk/workflow-harness';
|
|
54
73
|
|
|
55
|
-
export async function
|
|
74
|
+
export async function agentStep(
|
|
56
75
|
state: HarnessWorkflowState,
|
|
57
76
|
): Promise<HarnessWorkflowState> {
|
|
58
77
|
'use step';
|
|
59
78
|
|
|
60
79
|
const { agent } = await import('./agent');
|
|
61
80
|
|
|
62
|
-
return
|
|
81
|
+
return runHarnessAgentStep({
|
|
63
82
|
agent,
|
|
64
83
|
state,
|
|
65
84
|
});
|
|
66
85
|
}
|
|
67
86
|
```
|
|
68
87
|
|
|
88
|
+
### Defining the Semantic Agent Step Workflow
|
|
89
|
+
|
|
90
|
+
Create the workflow state and keep scheduling `agentStep()` while the agent has
|
|
91
|
+
more work.
|
|
92
|
+
|
|
69
93
|
```ts filename='harness-workflow/workflow.ts'
|
|
70
|
-
import {
|
|
94
|
+
import { agentStep } from './agent-step';
|
|
71
95
|
import {
|
|
72
96
|
createHarnessWorkflowState,
|
|
73
97
|
finalizeHarnessWorkflow,
|
|
74
98
|
type HarnessWorkflowInput,
|
|
75
99
|
} from '@ai-sdk/workflow-harness';
|
|
76
100
|
|
|
77
|
-
export async function
|
|
78
|
-
|
|
79
|
-
|
|
101
|
+
export async function agentWorkflow(input: {
|
|
102
|
+
messages: NonNullable<HarnessWorkflowInput['messages']>;
|
|
103
|
+
sessionId: string;
|
|
104
|
+
}) {
|
|
80
105
|
'use workflow';
|
|
81
106
|
|
|
82
107
|
let state = createHarnessWorkflowState(input);
|
|
83
108
|
|
|
84
|
-
|
|
85
|
-
state = await
|
|
86
|
-
}
|
|
109
|
+
do {
|
|
110
|
+
state = await agentStep(state);
|
|
111
|
+
} while (state.status === 'ready_for_next_step');
|
|
87
112
|
|
|
88
113
|
return finalizeHarnessWorkflow(state);
|
|
89
114
|
}
|
|
90
115
|
```
|
|
91
116
|
|
|
92
|
-
|
|
117
|
+
<Note>
|
|
118
|
+
This example covers the workflow execution foundation only. For multi-turn
|
|
119
|
+
conversations, you must persist the harness session's `resumeFrom` state
|
|
120
|
+
between workflow runs. See [Resume Persistence](#resume-persistence).
|
|
121
|
+
</Note>
|
|
122
|
+
|
|
123
|
+
Each `ready_for_next_step` result carries `continueFrom`, which lets the next
|
|
124
|
+
Workflow step continue the same unfinished turn. When the turn finishes,
|
|
125
|
+
`finalizeHarnessWorkflow()` returns its result or throws if the workflow failed.
|
|
93
126
|
|
|
94
|
-
|
|
95
|
-
HTTP endpoint, consume it directly, or expose it through your framework's
|
|
96
|
-
streaming response primitive.
|
|
127
|
+
### Starting the Semantic Agent Step Workflow
|
|
97
128
|
|
|
98
|
-
|
|
99
|
-
|
|
129
|
+
Start the workflow from server-side code. This Next.js route converts the AI SDK
|
|
130
|
+
UI messages, starts `agentWorkflow()`, and returns the workflow's AI SDK UI
|
|
131
|
+
message stream. Passing the converted messages lets `HarnessAgent` distinguish a
|
|
132
|
+
new user turn from tool approval and tool result continuations.
|
|
133
|
+
|
|
134
|
+
```ts filename='app/api/harness-workflow/route.ts'
|
|
135
|
+
import { agentWorkflow } from '../../../harness-workflow/workflow';
|
|
136
|
+
import {
|
|
137
|
+
convertToModelMessages,
|
|
138
|
+
createUIMessageStreamResponse,
|
|
139
|
+
type UIMessage,
|
|
140
|
+
type UIMessageChunk,
|
|
141
|
+
} from 'ai';
|
|
100
142
|
import { start } from 'workflow/api';
|
|
101
143
|
|
|
102
|
-
export async function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
144
|
+
export async function POST(request: Request) {
|
|
145
|
+
const body: {
|
|
146
|
+
id?: string;
|
|
147
|
+
messages: UIMessage[];
|
|
148
|
+
} = await request.json();
|
|
149
|
+
|
|
150
|
+
if (!body.id) {
|
|
151
|
+
return new Response('Missing chat ID', { status: 400 });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const messages = await convertToModelMessages(body.messages);
|
|
155
|
+
const run = await start(agentWorkflow, [
|
|
156
|
+
{
|
|
157
|
+
messages,
|
|
158
|
+
sessionId: body.id,
|
|
159
|
+
},
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
return createUIMessageStreamResponse({
|
|
163
|
+
stream: run.readable as ReadableStream<UIMessageChunk>,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The `sessionId` gives the sandbox a stable identity across workflow runs. Keep
|
|
169
|
+
`agent.ts`, `agent-step.ts`, `workflow.ts`, and the route in separate modules so
|
|
170
|
+
the workflow bundle does not include Node-heavy agent, sandbox, or framework
|
|
171
|
+
dependencies.
|
|
172
|
+
|
|
173
|
+
## Using Time Slices
|
|
174
|
+
|
|
175
|
+
Time slices persist a long-running harness turn at wall-clock boundaries. Omit
|
|
176
|
+
the highlighted `stopWhen` option from the shared agent, then call
|
|
177
|
+
`runHarnessAgentTimeSlice()` from a Workflow step. It uses a 750-second budget
|
|
178
|
+
by default; pass `timeSliceSeconds` to choose a different budget.
|
|
179
|
+
|
|
180
|
+
### Defining the Time Slice Step
|
|
181
|
+
|
|
182
|
+
As with semantic agent steps, keep the Workflow step in its own module and
|
|
183
|
+
import the agent dynamically inside the step body.
|
|
184
|
+
|
|
185
|
+
```ts filename='harness-workflow/time-slice-step.ts'
|
|
186
|
+
import {
|
|
187
|
+
runHarnessAgentTimeSlice,
|
|
188
|
+
type HarnessWorkflowState,
|
|
189
|
+
} from '@ai-sdk/workflow-harness';
|
|
190
|
+
|
|
191
|
+
export async function timeSliceStep(
|
|
192
|
+
state: HarnessWorkflowState,
|
|
193
|
+
): Promise<HarnessWorkflowState> {
|
|
194
|
+
'use step';
|
|
195
|
+
|
|
196
|
+
const { agent } = await import('./agent');
|
|
197
|
+
|
|
198
|
+
return runHarnessAgentTimeSlice({
|
|
199
|
+
agent,
|
|
200
|
+
state,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Defining the Time-Sliced Workflow
|
|
206
|
+
|
|
207
|
+
Create the workflow state and keep scheduling `timeSliceStep()` while the agent
|
|
208
|
+
has more work.
|
|
209
|
+
|
|
210
|
+
```ts filename='harness-workflow/workflow.ts'
|
|
211
|
+
import { timeSliceStep } from './time-slice-step';
|
|
212
|
+
import {
|
|
213
|
+
createHarnessWorkflowState,
|
|
214
|
+
finalizeHarnessWorkflow,
|
|
215
|
+
type HarnessWorkflowInput,
|
|
216
|
+
} from '@ai-sdk/workflow-harness';
|
|
217
|
+
|
|
218
|
+
export async function timeSliceWorkflow(input: {
|
|
219
|
+
messages: NonNullable<HarnessWorkflowInput['messages']>;
|
|
107
220
|
sessionId: string;
|
|
108
221
|
}) {
|
|
109
|
-
|
|
222
|
+
'use workflow';
|
|
223
|
+
|
|
224
|
+
let state = createHarnessWorkflowState(input);
|
|
225
|
+
|
|
226
|
+
do {
|
|
227
|
+
state = await timeSliceStep(state);
|
|
228
|
+
} while (state.status === 'ready_for_next_step');
|
|
110
229
|
|
|
111
|
-
return
|
|
230
|
+
return finalizeHarnessWorkflow(state);
|
|
112
231
|
}
|
|
113
232
|
```
|
|
114
233
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
234
|
+
<Note>
|
|
235
|
+
This example covers the workflow execution foundation only. For multi-turn
|
|
236
|
+
conversations, you must persist the harness session's `resumeFrom` state
|
|
237
|
+
between workflow runs. See [Resume Persistence](#resume-persistence).
|
|
238
|
+
</Note>
|
|
239
|
+
|
|
240
|
+
Each `ready_for_next_step` result carries `continueFrom`, which lets the next
|
|
241
|
+
Workflow step continue the same unfinished turn. When the turn finishes,
|
|
242
|
+
`finalizeHarnessWorkflow()` returns its result or throws if the workflow failed.
|
|
118
243
|
|
|
119
|
-
###
|
|
244
|
+
### Starting the Time-Sliced Workflow
|
|
120
245
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
readable stream.
|
|
246
|
+
Start the workflow from server-side code. As with semantic agent steps, pass the
|
|
247
|
+
converted messages so `HarnessAgent` can distinguish a new user turn from tool
|
|
248
|
+
approval and tool result continuations.
|
|
125
249
|
|
|
126
250
|
```ts filename='app/api/harness-workflow/route.ts'
|
|
127
|
-
import {
|
|
251
|
+
import { timeSliceWorkflow } from '../../../harness-workflow/workflow';
|
|
128
252
|
import {
|
|
129
253
|
convertToModelMessages,
|
|
130
254
|
createUIMessageStreamResponse,
|
|
@@ -133,15 +257,6 @@ import {
|
|
|
133
257
|
} from 'ai';
|
|
134
258
|
import { start } from 'workflow/api';
|
|
135
259
|
|
|
136
|
-
function latestUserMessage(
|
|
137
|
-
messages: Awaited<ReturnType<typeof convertToModelMessages>>,
|
|
138
|
-
) {
|
|
139
|
-
for (let index = messages.length - 1; index >= 0; index--) {
|
|
140
|
-
const message = messages[index];
|
|
141
|
-
if (message.role === 'user') return message;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
260
|
export async function POST(request: Request) {
|
|
146
261
|
const body: {
|
|
147
262
|
id?: string;
|
|
@@ -152,14 +267,10 @@ export async function POST(request: Request) {
|
|
|
152
267
|
return new Response('Missing chat ID', { status: 400 });
|
|
153
268
|
}
|
|
154
269
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
return new Response('No user message to run', { status: 400 });
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const run = await start(codingWorkflow, [
|
|
270
|
+
const messages = await convertToModelMessages(body.messages);
|
|
271
|
+
const run = await start(timeSliceWorkflow, [
|
|
161
272
|
{
|
|
162
|
-
|
|
273
|
+
messages,
|
|
163
274
|
sessionId: body.id,
|
|
164
275
|
},
|
|
165
276
|
]);
|
|
@@ -170,23 +281,21 @@ export async function POST(request: Request) {
|
|
|
170
281
|
}
|
|
171
282
|
```
|
|
172
283
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
```js filename='next.config.js'
|
|
178
|
-
const { withWorkflow } = require('workflow/next');
|
|
179
|
-
|
|
180
|
-
const nextConfig = {};
|
|
181
|
-
|
|
182
|
-
module.exports = withWorkflow(nextConfig, {});
|
|
183
|
-
```
|
|
284
|
+
The `sessionId` gives the sandbox a stable identity across workflow runs. Keep
|
|
285
|
+
`agent.ts`, `time-slice-step.ts`, `workflow.ts`, and the route in separate
|
|
286
|
+
modules so the workflow bundle does not include Node-heavy agent, sandbox, or
|
|
287
|
+
framework dependencies.
|
|
184
288
|
|
|
185
289
|
## Resume Persistence
|
|
186
290
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
291
|
+
Workflow automatically persists the `HarnessWorkflowState` returned by each
|
|
292
|
+
step, including `continueFrom`, for the duration of the current workflow run.
|
|
293
|
+
To continue the native harness session across separate user-turn workflow runs,
|
|
294
|
+
persist the opaque `resumeFrom` state by `sessionId`.
|
|
295
|
+
|
|
296
|
+
The storage implementation is the same for semantic agent steps and time
|
|
297
|
+
slices. This example uses Workflow steps because filesystem access must stay out
|
|
298
|
+
of the workflow function itself. Use durable storage instead of local files in
|
|
190
299
|
production.
|
|
191
300
|
|
|
192
301
|
```ts filename='harness-workflow/resume-store.ts'
|
|
@@ -244,29 +353,33 @@ export async function persistResumeStep({
|
|
|
244
353
|
}
|
|
245
354
|
```
|
|
246
355
|
|
|
247
|
-
Load the
|
|
248
|
-
|
|
356
|
+
Load the previous `resumeFrom` state before creating the workflow state, then
|
|
357
|
+
persist the updated value after the execution loop. The integration points are
|
|
358
|
+
the same for both workflow approaches.
|
|
359
|
+
|
|
360
|
+
### Semantic Agent Step Workflow
|
|
249
361
|
|
|
250
362
|
```ts filename='harness-workflow/workflow.ts'
|
|
363
|
+
import { agentStep } from './agent-step';
|
|
251
364
|
import { loadResumeStep, persistResumeStep } from './resume-store';
|
|
252
|
-
import { runSlice } from './run-slice-step';
|
|
253
365
|
import {
|
|
254
366
|
createHarnessWorkflowState,
|
|
255
367
|
finalizeHarnessWorkflow,
|
|
256
368
|
type HarnessWorkflowInput,
|
|
257
369
|
} from '@ai-sdk/workflow-harness';
|
|
258
370
|
|
|
259
|
-
export async function
|
|
260
|
-
|
|
261
|
-
|
|
371
|
+
export async function agentWorkflow(input: {
|
|
372
|
+
messages: NonNullable<HarnessWorkflowInput['messages']>;
|
|
373
|
+
sessionId: string;
|
|
374
|
+
}) {
|
|
262
375
|
'use workflow';
|
|
263
376
|
|
|
264
377
|
const resumeFrom = await loadResumeStep(input.sessionId);
|
|
265
378
|
let state = createHarnessWorkflowState({ ...input, resumeFrom });
|
|
266
379
|
|
|
267
|
-
|
|
268
|
-
state = await
|
|
269
|
-
}
|
|
380
|
+
do {
|
|
381
|
+
state = await agentStep(state);
|
|
382
|
+
} while (state.status === 'ready_for_next_step');
|
|
270
383
|
|
|
271
384
|
await persistResumeStep({
|
|
272
385
|
sessionId: state.sessionId,
|
|
@@ -277,48 +390,38 @@ export async function codingWorkflow(
|
|
|
277
390
|
}
|
|
278
391
|
```
|
|
279
392
|
|
|
280
|
-
|
|
281
|
-
and lets the workflow load the previous turn's resume state before sending the
|
|
282
|
-
next user message.
|
|
283
|
-
|
|
284
|
-
A harness session owns its native conversation history, so the route sends only
|
|
285
|
-
the newest user message. Do not replay the full UI message history into the
|
|
286
|
-
harness.
|
|
393
|
+
### Time-Sliced Workflow
|
|
287
394
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
- If the turn finishes, the helper closes the workflow output stream and returns
|
|
297
|
-
`resumeFrom` for the next user turn.
|
|
298
|
-
- `finalizeHarnessWorkflow()` returns the final result or throws when the
|
|
299
|
-
workflow failed.
|
|
395
|
+
```ts filename='harness-workflow/workflow.ts'
|
|
396
|
+
import { loadResumeStep, persistResumeStep } from './resume-store';
|
|
397
|
+
import { timeSliceStep } from './time-slice-step';
|
|
398
|
+
import {
|
|
399
|
+
createHarnessWorkflowState,
|
|
400
|
+
finalizeHarnessWorkflow,
|
|
401
|
+
type HarnessWorkflowInput,
|
|
402
|
+
} from '@ai-sdk/workflow-harness';
|
|
300
403
|
|
|
301
|
-
|
|
302
|
-
|
|
404
|
+
export async function timeSliceWorkflow(input: {
|
|
405
|
+
messages: NonNullable<HarnessWorkflowInput['messages']>;
|
|
406
|
+
sessionId: string;
|
|
407
|
+
}) {
|
|
408
|
+
'use workflow';
|
|
303
409
|
|
|
304
|
-
|
|
410
|
+
const resumeFrom = await loadResumeStep(input.sessionId);
|
|
411
|
+
let state = createHarnessWorkflowState({ ...input, resumeFrom });
|
|
305
412
|
|
|
306
|
-
|
|
413
|
+
do {
|
|
414
|
+
state = await timeSliceStep(state);
|
|
415
|
+
} while (state.status === 'ready_for_next_step');
|
|
307
416
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
workflow-safe helpers and step modules.
|
|
313
|
-
- your route or server entrypoint starts the workflow. It can import `ai`
|
|
314
|
-
helpers such as `convertToModelMessages()` and
|
|
315
|
-
`createUIMessageStreamResponse()`.
|
|
417
|
+
await persistResumeStep({
|
|
418
|
+
sessionId: state.sessionId,
|
|
419
|
+
resumeState: state.resumeFrom,
|
|
420
|
+
});
|
|
316
421
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
Node-heavy agent, sandbox, and framework dependencies from being pulled into the
|
|
321
|
-
workflow bundle.
|
|
422
|
+
return finalizeHarnessWorkflow(state);
|
|
423
|
+
}
|
|
424
|
+
```
|
|
322
425
|
|
|
323
426
|
## Related
|
|
324
427
|
|
|
@@ -134,7 +134,7 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
|
|
|
134
134
|
type: 'Experimental_SandboxSession',
|
|
135
135
|
isOptional: true,
|
|
136
136
|
description:
|
|
137
|
-
'Default sandbox session passed to tool execution as `experimental_sandbox
|
|
137
|
+
'Default sandbox session passed to tool descriptions and execution as `experimental_sandbox`, and exposed to `prepareStep`. Per-stream values override this default.',
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
name: 'prepareStep',
|
|
@@ -471,11 +471,17 @@ const result = await agent.stream({
|
|
|
471
471
|
description:
|
|
472
472
|
'A writable stream that receives raw model stream parts in real-time. Convert to UI message chunks at the response boundary using `createModelCallToUIChunkTransform()`.',
|
|
473
473
|
},
|
|
474
|
+
{
|
|
475
|
+
name: 'instructions',
|
|
476
|
+
type: 'Instructions',
|
|
477
|
+
isOptional: true,
|
|
478
|
+
description: 'Override the agent instructions for this call.',
|
|
479
|
+
},
|
|
474
480
|
{
|
|
475
481
|
name: 'system',
|
|
476
482
|
type: 'string',
|
|
477
483
|
isOptional: true,
|
|
478
|
-
description: '
|
|
484
|
+
description: 'Deprecated. Use `instructions` instead.',
|
|
479
485
|
},
|
|
480
486
|
{
|
|
481
487
|
name: 'stopWhen',
|
|
@@ -550,7 +556,7 @@ const result = await agent.stream({
|
|
|
550
556
|
type: 'Experimental_SandboxSession',
|
|
551
557
|
isOptional: true,
|
|
552
558
|
description:
|
|
553
|
-
'Sandbox session passed to tool execution as `experimental_sandbox
|
|
559
|
+
'Sandbox session passed to tool descriptions and execution as `experimental_sandbox`, and exposed to `prepareStep`. Overrides the constructor default.',
|
|
554
560
|
},
|
|
555
561
|
{
|
|
556
562
|
name: 'telemetry',
|
|
@@ -576,7 +582,8 @@ const result = await agent.stream({
|
|
|
576
582
|
name: 'prepareStep',
|
|
577
583
|
type: 'PrepareStepCallback',
|
|
578
584
|
isOptional: true,
|
|
579
|
-
description:
|
|
585
|
+
description:
|
|
586
|
+
'Per-call prepareStep override. Receives the initial instructions and messages alongside the current step state.',
|
|
580
587
|
},
|
|
581
588
|
{
|
|
582
589
|
name: 'experimental_onStart',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.39",
|
|
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.30",
|
|
46
46
|
"@ai-sdk/provider": "4.0.4",
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.14"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@edge-runtime/vm": "^5.0.0",
|