ai 7.0.37 → 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 +22 -0
- package/dist/index.d.ts +283 -41
- package/dist/index.js +688 -425
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/internal/index.js.map +1 -1
- package/dist/test/index.d.ts +14 -2
- package/dist/test/index.js +15 -0
- package/dist/test/index.js.map +1 -1
- package/docs/03-agents/07-workflow-agent.mdx +3 -2
- package/docs/03-ai-sdk-core/36-transcription.mdx +40 -35
- package/docs/03-ai-sdk-core/36-translation.mdx +209 -0
- package/docs/03-ai-sdk-core/index.mdx +5 -0
- package/docs/03-ai-sdk-harnesses/06-workflow-utilities.mdx +225 -122
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/11-stream-translate.mdx +176 -0
- package/docs/07-reference/01-ai-sdk-core/index.mdx +5 -0
- package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +11 -4
- package/docs/07-reference/05-ai-sdk-errors/ai-no-translation-generated-error.mdx +26 -0
- package/docs/07-reference/05-ai-sdk-errors/index.mdx +1 -0
- package/package.json +4 -4
- package/src/error/index.ts +1 -0
- package/src/error/no-translation-generated-error.ts +28 -0
- package/src/generate-object/generate-object.ts +12 -2
- package/src/generate-object/stream-object.ts +14 -1
- package/src/generate-text/generate-text.ts +4 -1
- package/src/generate-text/stream-language-model-call.ts +14 -12
- package/src/generate-text/stream-text-result.ts +3 -2
- package/src/index.ts +1 -0
- package/src/model/resolve-model.ts +35 -0
- package/src/test/mock-speech-translation-model-v4.ts +24 -0
- package/src/transcribe/stream-transcribe.ts +2 -11
- package/src/translate/index.ts +5 -0
- package/src/translate/stream-translate-result.ts +146 -0
- package/src/translate/stream-translate.ts +374 -0
- package/src/types/speech-translation-model-response-metadata.ts +22 -0
- package/src/types/speech-translation-model.ts +11 -0
|
@@ -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
|
|
|
@@ -3210,7 +3210,7 @@ To see `streamText` in action, check out [these examples](#examples).
|
|
|
3210
3210
|
name: 'textStream',
|
|
3211
3211
|
type: 'AsyncIterableStream<string>',
|
|
3212
3212
|
description:
|
|
3213
|
-
'A text stream that returns only the generated text deltas. You can use it as either an AsyncIterable or a ReadableStream.
|
|
3213
|
+
'A text stream that returns only the generated text deltas. You can use it as either an AsyncIterable or a ReadableStream. Error parts are not surfaced in this stream. Use the `onError` callback or `stream` to observe them.',
|
|
3214
3214
|
},
|
|
3215
3215
|
{
|
|
3216
3216
|
name: 'finalStep',
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: experimental_streamTranslate
|
|
3
|
+
description: API Reference for experimental_streamTranslate.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `experimental_streamTranslate()`
|
|
7
|
+
|
|
8
|
+
<Note type="warning">
|
|
9
|
+
`experimental_streamTranslate` is an experimental feature.
|
|
10
|
+
</Note>
|
|
11
|
+
|
|
12
|
+
Streams a speech-to-speech translation from live raw audio. Models translate
|
|
13
|
+
live source audio into target-language audio and text.
|
|
14
|
+
|
|
15
|
+
`experimental_streamTranslate` is built on the speech translation model
|
|
16
|
+
specification (`Experimental_SpeechTranslationModelV4`). Provider
|
|
17
|
+
implementations of the specification ship separately — see your provider's
|
|
18
|
+
documentation for available translation models.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { experimental_streamTranslate as streamTranslate } from 'ai';
|
|
22
|
+
|
|
23
|
+
const result = streamTranslate({
|
|
24
|
+
// any provider model instance that implements the experimental
|
|
25
|
+
// speech translation model specification (Experimental_SpeechTranslationModelV4):
|
|
26
|
+
model: translationModel,
|
|
27
|
+
audio: audioStream, // ReadableStream<Uint8Array | string>
|
|
28
|
+
inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
|
|
29
|
+
targetLanguage: 'es',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
for await (const part of result.fullStream) {
|
|
33
|
+
if (part.type === 'output-text-delta') {
|
|
34
|
+
process.stdout.write(part.delta);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(await result.translationText);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Import
|
|
42
|
+
|
|
43
|
+
<Snippet
|
|
44
|
+
text={`import { experimental_streamTranslate as streamTranslate } from "ai"`}
|
|
45
|
+
prompt={false}
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
## API Signature
|
|
49
|
+
|
|
50
|
+
### Parameters
|
|
51
|
+
|
|
52
|
+
<PropertiesTable
|
|
53
|
+
content={[
|
|
54
|
+
{
|
|
55
|
+
name: 'model',
|
|
56
|
+
type: 'Experimental_SpeechTranslationModelV4',
|
|
57
|
+
description:
|
|
58
|
+
'The speech translation model to use. Translation is a streaming-only modality. Pass a provider model instance that implements the experimental speech translation model specification. String model IDs resolve through the global provider (AI Gateway by default) when it supports speech translation models.',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'audio',
|
|
62
|
+
type: 'ReadableStream<Uint8Array | string>',
|
|
63
|
+
description:
|
|
64
|
+
'Raw audio chunks to translate. `Uint8Array` chunks contain raw audio bytes; `string` chunks contain base64-encoded raw audio bytes.',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'inputAudioFormat',
|
|
68
|
+
type: '{ type: string; rate?: number }',
|
|
69
|
+
description:
|
|
70
|
+
'The input audio format for the raw audio chunks, e.g. `{ type: "audio/pcm", rate: 24000 }`. Supported types are provider-specific (e.g. `audio/pcm`, `audio/pcmu`, `audio/pcma`).',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'targetLanguage',
|
|
74
|
+
type: 'string',
|
|
75
|
+
description:
|
|
76
|
+
'The language to translate the audio into, as a BCP-47-style language tag (e.g. `en`, `es`, `fr-CA`). Supported values are provider-specific and validated by the provider.',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'sourceLanguage',
|
|
80
|
+
type: 'string',
|
|
81
|
+
isOptional: true,
|
|
82
|
+
description:
|
|
83
|
+
'The language of the source audio, as a BCP-47-style language tag. When absent, providers auto-detect the source language.',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'outputAudioFormat',
|
|
87
|
+
type: '{ type: string; rate?: number }',
|
|
88
|
+
isOptional: true,
|
|
89
|
+
description:
|
|
90
|
+
'The desired audio format for translated audio chunks. When absent, the provider default output format is used.',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'providerOptions',
|
|
94
|
+
type: 'Record<string, JSONObject>',
|
|
95
|
+
isOptional: true,
|
|
96
|
+
description: 'Additional provider-specific options.',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'abortSignal',
|
|
100
|
+
type: 'AbortSignal',
|
|
101
|
+
isOptional: true,
|
|
102
|
+
description: 'An optional abort signal to cancel the call.',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'headers',
|
|
106
|
+
type: 'Record<string, string>',
|
|
107
|
+
isOptional: true,
|
|
108
|
+
description:
|
|
109
|
+
'Additional HTTP/WebSocket headers, if supported by the provider.',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'includeRawChunks',
|
|
113
|
+
type: 'boolean',
|
|
114
|
+
isOptional: true,
|
|
115
|
+
description:
|
|
116
|
+
'When true, the provider includes raw provider chunks in the stream as `raw` parts.',
|
|
117
|
+
},
|
|
118
|
+
]}
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
### Returns
|
|
122
|
+
|
|
123
|
+
<PropertiesTable
|
|
124
|
+
content={[
|
|
125
|
+
{
|
|
126
|
+
name: 'fullStream',
|
|
127
|
+
type: 'AsyncIterableStream<TranslationStreamPart>',
|
|
128
|
+
description:
|
|
129
|
+
'A single-consumer live stream of translation parts: `audio`, `output-text-delta`, `output-text-final`, `source-transcript-delta`, `source-transcript-partial`, `source-transcript-final`, `raw`, and `error`. Access it once, before any result promise, when both stream parts and final results are needed. Accessing a result promise first consumes the stream internally and makes `fullStream` unavailable.',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'sourceText',
|
|
133
|
+
type: 'Promise<string>',
|
|
134
|
+
description: 'The final source-language transcript of the input audio.',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'translationText',
|
|
138
|
+
type: 'Promise<string>',
|
|
139
|
+
description:
|
|
140
|
+
'The final translated text in the target language. May resolve to an empty string for providers that produce only audio output.',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'durationInSeconds',
|
|
144
|
+
type: 'Promise<number | undefined>',
|
|
145
|
+
description: 'The duration of the source audio in seconds, if available.',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'usage',
|
|
149
|
+
type: 'Promise<Experimental_SpeechTranslationModelV4Usage | undefined>',
|
|
150
|
+
description:
|
|
151
|
+
'Usage information for the translation call, if reported by the provider: `inputAudioSeconds`, `inputAudioTokens`, `outputAudioTokens`, `inputTextTokens`, `outputTextTokens` (all optional numbers).',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'warnings',
|
|
155
|
+
type: 'Promise<Warning[]>',
|
|
156
|
+
description:
|
|
157
|
+
'Warnings for the call, e.g. unsupported settings. Resolves at stream start, or with an empty array at finish if no stream-start was emitted.',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'response',
|
|
161
|
+
type: 'Promise<SpeechTranslationModelResponseMetadata>',
|
|
162
|
+
description: 'Response metadata (timestamp, model ID, headers).',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'providerMetadata',
|
|
166
|
+
type: 'Promise<Record<string, JSONObject>>',
|
|
167
|
+
description: 'Additional provider-specific metadata.',
|
|
168
|
+
},
|
|
169
|
+
]}
|
|
170
|
+
/>
|
|
171
|
+
|
|
172
|
+
<Note>
|
|
173
|
+
The result promises settle as the stream is consumed. If you stop consuming
|
|
174
|
+
`fullStream` early (e.g. `break` out of the loop), the underlying provider
|
|
175
|
+
connection is closed and pending result promises reject.
|
|
176
|
+
</Note>
|