@ai-sdk/workflow 1.0.0-beta.1 → 1.0.0-beta.100
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 +839 -0
- package/dist/index.d.mts +301 -127
- package/dist/index.mjs +926 -379
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -11
- package/src/create-language-model-tool-result-output.ts +85 -0
- package/src/do-stream-step.ts +52 -31
- package/src/index.ts +9 -6
- package/src/providers/mock.ts +79 -92
- package/src/serializable-schema.ts +5 -1
- package/src/stream-text-iterator.ts +157 -74
- package/src/test/agent-e2e-workflows.ts +89 -9
- package/src/to-ui-message-chunk.ts +13 -10
- package/src/workflow-agent.ts +1525 -804
- package/src/workflow-chat-transport.ts +17 -15
- package/src/telemetry.ts +0 -199
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/workflow",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.100",
|
|
4
4
|
"description": "WorkflowAgent for building AI agents with AI SDK",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,17 +26,17 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"ajv": "^8.
|
|
30
|
-
"@ai-sdk/provider": "4.0.0-beta.
|
|
31
|
-
"@ai-sdk/provider-utils": "5.0.0-beta.
|
|
32
|
-
"ai": "7.0.0-beta.
|
|
29
|
+
"ajv": "^8.20.0",
|
|
30
|
+
"@ai-sdk/provider": "4.0.0-beta.19",
|
|
31
|
+
"@ai-sdk/provider-utils": "5.0.0-beta.49",
|
|
32
|
+
"ai": "7.0.0-beta.182"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@types/node": "
|
|
35
|
+
"@types/node": "22.19.19",
|
|
36
36
|
"@workflow/vitest": "4.0.1",
|
|
37
|
-
"tsup": "^8",
|
|
37
|
+
"tsup": "^8.5.1",
|
|
38
38
|
"typescript": "5.8.3",
|
|
39
|
-
"workflow": "4.2.
|
|
39
|
+
"workflow": "4.2.4",
|
|
40
40
|
"zod": "3.25.76",
|
|
41
41
|
"@vercel/ai-tsconfig": "0.0.0"
|
|
42
42
|
},
|
|
@@ -44,15 +44,17 @@
|
|
|
44
44
|
"zod": "^3.25.76 || ^4.1.8"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
|
-
"node": ">=
|
|
47
|
+
"node": ">=22"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
|
-
"access": "public"
|
|
50
|
+
"access": "public",
|
|
51
|
+
"provenance": true
|
|
51
52
|
},
|
|
52
53
|
"homepage": "https://ai-sdk.dev/docs",
|
|
53
54
|
"repository": {
|
|
54
55
|
"type": "git",
|
|
55
|
-
"url": "
|
|
56
|
+
"url": "https://github.com/vercel/ai",
|
|
57
|
+
"directory": "packages/workflow"
|
|
56
58
|
},
|
|
57
59
|
"bugs": {
|
|
58
60
|
"url": "https://github.com/vercel/ai/issues"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { LanguageModelV4ToolResultOutput } from '@ai-sdk/provider';
|
|
2
|
+
import type { Tool } from '@ai-sdk/provider-utils';
|
|
3
|
+
import type { ModelMessage } from 'ai';
|
|
4
|
+
import {
|
|
5
|
+
createDefaultDownloadFunction,
|
|
6
|
+
createToolModelOutput,
|
|
7
|
+
downloadAssets,
|
|
8
|
+
mapToolResultOutput,
|
|
9
|
+
type DownloadFunction,
|
|
10
|
+
} from 'ai/internal';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Converts a single tool result into a provider-level
|
|
14
|
+
* `LanguageModelV4ToolResultOutput`, honoring the tool's optional
|
|
15
|
+
* `toModelOutput` hook.
|
|
16
|
+
*
|
|
17
|
+
* Unlike `generateText`/`streamText`, `WorkflowAgent` assembles the
|
|
18
|
+
* `LanguageModelV4` prompt incrementally — appending one tool result at a time
|
|
19
|
+
* — instead of building AI-level `ModelMessage`s and converting the whole
|
|
20
|
+
* prompt once via `convertToLanguageModelPrompt`. This helper performs the
|
|
21
|
+
* equivalent per-result conversion using the shared `ai/internal` primitives:
|
|
22
|
+
*
|
|
23
|
+
* 1. `createToolModelOutput` — applies `tool.toModelOutput` (or the
|
|
24
|
+
* text/json/error fallback).
|
|
25
|
+
* 2. `downloadAssets` — for `content`-type outputs, downloads any file/image
|
|
26
|
+
* assets so URLs become bytes the provider can consume.
|
|
27
|
+
* 3. `mapToolResultOutput` — maps the AI-level `ToolResultOutput` to the
|
|
28
|
+
* provider-level output and converts legacy file types.
|
|
29
|
+
*/
|
|
30
|
+
export async function createLanguageModelToolResultOutput({
|
|
31
|
+
toolCallId,
|
|
32
|
+
toolName,
|
|
33
|
+
input,
|
|
34
|
+
output,
|
|
35
|
+
tool,
|
|
36
|
+
errorMode,
|
|
37
|
+
supportedUrls,
|
|
38
|
+
download = createDefaultDownloadFunction(),
|
|
39
|
+
provider,
|
|
40
|
+
}: {
|
|
41
|
+
toolCallId: string;
|
|
42
|
+
toolName: string;
|
|
43
|
+
input: unknown;
|
|
44
|
+
output: unknown;
|
|
45
|
+
tool: Tool | undefined;
|
|
46
|
+
errorMode: 'none' | 'text' | 'json';
|
|
47
|
+
supportedUrls: Record<string, RegExp[]>;
|
|
48
|
+
download?: DownloadFunction;
|
|
49
|
+
provider?: string;
|
|
50
|
+
}): Promise<LanguageModelV4ToolResultOutput> {
|
|
51
|
+
const modelOutput = await createToolModelOutput({
|
|
52
|
+
toolCallId,
|
|
53
|
+
input,
|
|
54
|
+
output,
|
|
55
|
+
tool,
|
|
56
|
+
errorMode,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const downloadedAssets =
|
|
60
|
+
modelOutput.type === 'content'
|
|
61
|
+
? await downloadAssets(
|
|
62
|
+
[
|
|
63
|
+
{
|
|
64
|
+
role: 'tool',
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: 'tool-result',
|
|
68
|
+
toolCallId,
|
|
69
|
+
toolName,
|
|
70
|
+
output: modelOutput,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
} satisfies ModelMessage,
|
|
74
|
+
],
|
|
75
|
+
download,
|
|
76
|
+
supportedUrls,
|
|
77
|
+
)
|
|
78
|
+
: {};
|
|
79
|
+
|
|
80
|
+
return mapToolResultOutput({
|
|
81
|
+
output: modelOutput,
|
|
82
|
+
provider,
|
|
83
|
+
downloadedAssets,
|
|
84
|
+
});
|
|
85
|
+
}
|
package/src/do-stream-step.ts
CHANGED
|
@@ -2,9 +2,11 @@ import type {
|
|
|
2
2
|
LanguageModelV4CallOptions,
|
|
3
3
|
LanguageModelV4Prompt,
|
|
4
4
|
} from '@ai-sdk/provider';
|
|
5
|
+
import type { Context } from '@ai-sdk/provider-utils';
|
|
5
6
|
import {
|
|
6
|
-
type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
|
|
7
7
|
experimental_streamLanguageModelCall as streamModelCall,
|
|
8
|
+
gateway,
|
|
9
|
+
type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
|
|
8
10
|
type FinishReason,
|
|
9
11
|
type LanguageModel,
|
|
10
12
|
type LanguageModelUsage,
|
|
@@ -15,14 +17,11 @@ import {
|
|
|
15
17
|
type ToolChoice,
|
|
16
18
|
type ToolSet,
|
|
17
19
|
} from 'ai';
|
|
18
|
-
import {
|
|
19
|
-
import type { ProviderOptions, TelemetrySettings } from './workflow-agent.js';
|
|
20
|
+
import type { ProviderOptions } from './workflow-agent.js';
|
|
20
21
|
import {
|
|
21
22
|
resolveSerializableTools,
|
|
22
23
|
type SerializableToolDef,
|
|
23
24
|
} from './serializable-schema.js';
|
|
24
|
-
import type { CompatibleLanguageModel } from './types.js';
|
|
25
|
-
|
|
26
25
|
export type { Experimental_LanguageModelStreamPart as ModelCallStreamPart } from 'ai';
|
|
27
26
|
|
|
28
27
|
export type ModelStopCondition = StopCondition<NoInfer<ToolSet>, any>;
|
|
@@ -55,9 +54,15 @@ export interface DoStreamStepOptions {
|
|
|
55
54
|
providerOptions?: ProviderOptions;
|
|
56
55
|
toolChoice?: ToolChoice<ToolSet>;
|
|
57
56
|
includeRawChunks?: boolean;
|
|
58
|
-
experimental_telemetry?: TelemetrySettings;
|
|
59
57
|
repairToolCall?: ToolCallRepairFunction<ToolSet>;
|
|
60
58
|
responseFormat?: LanguageModelV4CallOptions['responseFormat'];
|
|
59
|
+
runtimeContext?: Context;
|
|
60
|
+
toolsContext?: Record<string, Context | undefined>;
|
|
61
|
+
/**
|
|
62
|
+
* The step number for the returned StepResult. Defaults to 0 for direct
|
|
63
|
+
* callers; stream iterators pass their current index. See #15151.
|
|
64
|
+
*/
|
|
65
|
+
stepNumber?: number;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
/**
|
|
@@ -87,33 +92,24 @@ export interface StreamFinish {
|
|
|
87
92
|
|
|
88
93
|
export async function doStreamStep(
|
|
89
94
|
conversationPrompt: LanguageModelV4Prompt,
|
|
90
|
-
modelInit:
|
|
91
|
-
| string
|
|
92
|
-
| CompatibleLanguageModel
|
|
93
|
-
| (() => Promise<CompatibleLanguageModel>),
|
|
95
|
+
modelInit: LanguageModel,
|
|
94
96
|
writable?: WritableStream<ModelCallStreamPart<ToolSet>>,
|
|
95
97
|
serializedTools?: Record<string, SerializableToolDef>,
|
|
96
98
|
options?: DoStreamStepOptions,
|
|
97
|
-
) {
|
|
99
|
+
): Promise<{
|
|
100
|
+
toolCalls: ParsedToolCall[];
|
|
101
|
+
finish: StreamFinish | undefined;
|
|
102
|
+
step: StepResult<ToolSet, any>;
|
|
103
|
+
chunks?: unknown[];
|
|
104
|
+
providerExecutedToolResults: Map<string, ProviderExecutedToolResult>;
|
|
105
|
+
}> {
|
|
98
106
|
'use step';
|
|
99
107
|
|
|
100
108
|
// Resolve model inside step (must happen here for serialization boundary)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
model = await modelInit();
|
|
106
|
-
} else if (
|
|
107
|
-
typeof modelInit === 'object' &&
|
|
108
|
-
modelInit !== null &&
|
|
109
|
-
'modelId' in modelInit
|
|
110
|
-
) {
|
|
111
|
-
model = modelInit;
|
|
112
|
-
} else {
|
|
113
|
-
throw new Error(
|
|
114
|
-
'Invalid "model initialization" argument. Must be a string, a LanguageModel instance, or a function that returns a LanguageModel instance.',
|
|
115
|
-
);
|
|
116
|
-
}
|
|
109
|
+
const model: LanguageModel =
|
|
110
|
+
typeof modelInit === 'string'
|
|
111
|
+
? gateway.languageModel(modelInit)
|
|
112
|
+
: modelInit;
|
|
117
113
|
|
|
118
114
|
// Reconstruct tools from serializable definitions with Ajv validation.
|
|
119
115
|
// Tools are serialized before crossing the step boundary because zod schemas
|
|
@@ -126,11 +122,12 @@ export async function doStreamStep(
|
|
|
126
122
|
// model.doStream(), retry logic, and stream part transformation
|
|
127
123
|
// (tool call parsing, finish reason mapping, file wrapping).
|
|
128
124
|
const { stream: modelStream } = await streamModelCall({
|
|
129
|
-
model
|
|
125
|
+
model,
|
|
130
126
|
// streamModelCall expects Prompt (ModelMessage[]) but we pass the
|
|
131
127
|
// pre-converted LanguageModelV4Prompt. standardizePrompt inside
|
|
132
128
|
// streamModelCall handles both formats.
|
|
133
129
|
messages: conversationPrompt as unknown as ModelMessage[],
|
|
130
|
+
allowSystemInMessages: true,
|
|
134
131
|
tools,
|
|
135
132
|
toolChoice: options?.toolChoice,
|
|
136
133
|
includeRawChunks: options?.includeRawChunks,
|
|
@@ -159,6 +156,7 @@ export async function doStreamStep(
|
|
|
159
156
|
// Aggregation for StepResult
|
|
160
157
|
let text = '';
|
|
161
158
|
const reasoningParts: Array<{ text: string }> = [];
|
|
159
|
+
const chunks: unknown[] = [];
|
|
162
160
|
let responseMetadata:
|
|
163
161
|
| { id?: string; timestamp?: Date; modelId?: string }
|
|
164
162
|
| undefined;
|
|
@@ -169,6 +167,14 @@ export async function doStreamStep(
|
|
|
169
167
|
|
|
170
168
|
try {
|
|
171
169
|
for await (const part of modelStream) {
|
|
170
|
+
if (
|
|
171
|
+
part.type !== 'model-call-start' &&
|
|
172
|
+
part.type !== 'model-call-end' &&
|
|
173
|
+
part.type !== 'model-call-response-metadata'
|
|
174
|
+
) {
|
|
175
|
+
chunks.push(part);
|
|
176
|
+
}
|
|
177
|
+
|
|
172
178
|
switch (part.type) {
|
|
173
179
|
case 'text-delta':
|
|
174
180
|
text += part.text;
|
|
@@ -250,14 +256,15 @@ export async function doStreamStep(
|
|
|
250
256
|
|
|
251
257
|
const step: StepResult<ToolSet, any> = {
|
|
252
258
|
callId: 'workflow-agent',
|
|
253
|
-
stepNumber: 0,
|
|
259
|
+
stepNumber: options?.stepNumber ?? 0,
|
|
254
260
|
model: {
|
|
255
261
|
provider: responseMetadata?.modelId?.split(':')[0] ?? 'unknown',
|
|
256
262
|
modelId: responseMetadata?.modelId ?? 'unknown',
|
|
257
263
|
},
|
|
258
264
|
functionId: undefined,
|
|
259
265
|
metadata: undefined,
|
|
260
|
-
|
|
266
|
+
runtimeContext: options?.runtimeContext ?? {},
|
|
267
|
+
toolsContext: options?.toolsContext ?? {},
|
|
261
268
|
content: [
|
|
262
269
|
...(text ? [{ type: 'text' as const, text }] : []),
|
|
263
270
|
...toolCalls
|
|
@@ -318,8 +325,21 @@ export async function doStreamStep(
|
|
|
318
325
|
},
|
|
319
326
|
totalTokens: 0,
|
|
320
327
|
} as LanguageModelUsage),
|
|
328
|
+
performance: {
|
|
329
|
+
effectiveOutputTokensPerSecond: 0,
|
|
330
|
+
outputTokensPerSecond: undefined,
|
|
331
|
+
inputTokensPerSecond: undefined,
|
|
332
|
+
effectiveTotalTokensPerSecond: 0,
|
|
333
|
+
stepTimeMs: 0,
|
|
334
|
+
responseTimeMs: 0,
|
|
335
|
+
toolExecutionMs: {},
|
|
336
|
+
timeToFirstOutputMs: undefined,
|
|
337
|
+
},
|
|
321
338
|
warnings,
|
|
322
|
-
request: {
|
|
339
|
+
request: {
|
|
340
|
+
body: '',
|
|
341
|
+
messages: [], // TODO implement step request messages
|
|
342
|
+
},
|
|
323
343
|
response: {
|
|
324
344
|
id: responseMetadata?.id ?? 'unknown',
|
|
325
345
|
timestamp: responseMetadata?.timestamp ?? new Date(),
|
|
@@ -333,6 +353,7 @@ export async function doStreamStep(
|
|
|
333
353
|
toolCalls,
|
|
334
354
|
finish,
|
|
335
355
|
step,
|
|
356
|
+
chunks,
|
|
336
357
|
providerExecutedToolResults,
|
|
337
358
|
};
|
|
338
359
|
}
|
package/src/index.ts
CHANGED
|
@@ -17,16 +17,19 @@ export {
|
|
|
17
17
|
type PrepareStepInfo,
|
|
18
18
|
type PrepareStepResult,
|
|
19
19
|
type ProviderOptions,
|
|
20
|
-
type
|
|
21
|
-
type
|
|
22
|
-
type
|
|
20
|
+
type WorkflowAgentOnAbortCallback,
|
|
21
|
+
type WorkflowAgentOnEndCallback,
|
|
22
|
+
type WorkflowAgentOnErrorCallback,
|
|
23
|
+
type WorkflowAgentOnFinishCallback,
|
|
24
|
+
type WorkflowAgentOnStepEndCallback,
|
|
25
|
+
type WorkflowAgentOnStepFinishCallback,
|
|
23
26
|
type StreamTextTransform,
|
|
24
|
-
type
|
|
27
|
+
type TelemetryOptions,
|
|
25
28
|
type ToolCallRepairFunction,
|
|
26
29
|
type WorkflowAgentOnStartCallback,
|
|
27
30
|
type WorkflowAgentOnStepStartCallback,
|
|
28
|
-
type
|
|
29
|
-
type
|
|
31
|
+
type WorkflowAgentOnToolExecutionStartCallback,
|
|
32
|
+
type WorkflowAgentOnToolExecutionEndCallback,
|
|
30
33
|
} from './workflow-agent.js';
|
|
31
34
|
|
|
32
35
|
export {
|
package/src/providers/mock.ts
CHANGED
|
@@ -6,19 +6,55 @@ export type MockResponseDescriptor =
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Mock model that returns a fixed text response.
|
|
9
|
-
* Same 'use step' pattern as real providers (anthropic, openai, etc.).
|
|
10
|
-
* Only captures `text` (string) — fully serializable across step boundary.
|
|
11
9
|
*/
|
|
12
10
|
export function mockTextModel(text: string) {
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
return mockProvider({
|
|
12
|
+
doStream: async () => ({
|
|
13
|
+
stream: new ReadableStream({
|
|
14
|
+
start(c) {
|
|
15
|
+
for (const v of [
|
|
16
|
+
{ type: 'stream-start', warnings: [] },
|
|
17
|
+
{
|
|
18
|
+
type: 'response-metadata',
|
|
19
|
+
id: 'r',
|
|
20
|
+
modelId: 'mock',
|
|
21
|
+
timestamp: new Date(),
|
|
22
|
+
},
|
|
23
|
+
{ type: 'text-start', id: '1' },
|
|
24
|
+
{ type: 'text-delta', id: '1', delta: text },
|
|
25
|
+
{ type: 'text-end', id: '1' },
|
|
26
|
+
{
|
|
27
|
+
type: 'finish',
|
|
28
|
+
finishReason: { unified: 'stop', raw: 'stop' },
|
|
29
|
+
usage: {
|
|
30
|
+
inputTokens: { total: 5, noCache: 5 },
|
|
31
|
+
outputTokens: { total: 10, text: 10 },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
] as any[])
|
|
35
|
+
c.enqueue(v);
|
|
36
|
+
c.close();
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Mock model that plays through a sequence of responses.
|
|
45
|
+
* Determines which response to return by counting assistant messages in the prompt.
|
|
46
|
+
*/
|
|
47
|
+
export function mockSequenceModel(responses: MockResponseDescriptor[]) {
|
|
48
|
+
return mockProvider({
|
|
49
|
+
doStream: async (options: any) => {
|
|
50
|
+
const responseIndex = Math.min(
|
|
51
|
+
options.prompt.filter((m: any) => m.role === 'assistant').length,
|
|
52
|
+
responses.length - 1,
|
|
53
|
+
);
|
|
54
|
+
const selectedResponse = responses[responseIndex];
|
|
55
|
+
const parts =
|
|
56
|
+
selectedResponse.type === 'text'
|
|
57
|
+
? [
|
|
22
58
|
{ type: 'stream-start', warnings: [] },
|
|
23
59
|
{
|
|
24
60
|
type: 'response-metadata',
|
|
@@ -27,7 +63,7 @@ export function mockTextModel(text: string) {
|
|
|
27
63
|
timestamp: new Date(),
|
|
28
64
|
},
|
|
29
65
|
{ type: 'text-start', id: '1' },
|
|
30
|
-
{ type: 'text-delta', id: '1', delta:
|
|
66
|
+
{ type: 'text-delta', id: '1', delta: selectedResponse.text },
|
|
31
67
|
{ type: 'text-end', id: '1' },
|
|
32
68
|
{
|
|
33
69
|
type: 'finish',
|
|
@@ -37,87 +73,38 @@ export function mockTextModel(text: string) {
|
|
|
37
73
|
outputTokens: { total: 10, text: 10 },
|
|
38
74
|
},
|
|
39
75
|
},
|
|
40
|
-
]
|
|
41
|
-
|
|
76
|
+
]
|
|
77
|
+
: [
|
|
78
|
+
{ type: 'stream-start', warnings: [] },
|
|
79
|
+
{
|
|
80
|
+
type: 'response-metadata',
|
|
81
|
+
id: 'r',
|
|
82
|
+
modelId: 'mock',
|
|
83
|
+
timestamp: new Date(),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'tool-call',
|
|
87
|
+
toolCallId: `call-${responseIndex + 1}`,
|
|
88
|
+
toolName: selectedResponse.toolName,
|
|
89
|
+
input: selectedResponse.input,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: 'finish',
|
|
93
|
+
finishReason: { unified: 'tool-calls', raw: undefined },
|
|
94
|
+
usage: {
|
|
95
|
+
inputTokens: { total: 5, noCache: 5 },
|
|
96
|
+
outputTokens: { total: 10, text: 10 },
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
return {
|
|
101
|
+
stream: new ReadableStream({
|
|
102
|
+
start(c) {
|
|
103
|
+
for (const streamPart of parts as any[]) c.enqueue(streamPart);
|
|
42
104
|
c.close();
|
|
43
105
|
},
|
|
44
106
|
}),
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Mock model that plays through a sequence of responses.
|
|
52
|
-
* Determines which response to return by counting assistant messages in the prompt.
|
|
53
|
-
* Only captures `responses` (array of plain objects) — fully serializable.
|
|
54
|
-
*/
|
|
55
|
-
export function mockSequenceModel(responses: MockResponseDescriptor[]) {
|
|
56
|
-
return async () => {
|
|
57
|
-
'use step';
|
|
58
|
-
// Bind closure var at step body level so SWC plugin detects it
|
|
59
|
-
const _responses = responses;
|
|
60
|
-
return mockProvider({
|
|
61
|
-
doStream: async (options: any) => {
|
|
62
|
-
const idx = Math.min(
|
|
63
|
-
options.prompt.filter((m: any) => m.role === 'assistant').length,
|
|
64
|
-
_responses.length - 1,
|
|
65
|
-
);
|
|
66
|
-
const r = _responses[idx];
|
|
67
|
-
const parts =
|
|
68
|
-
r.type === 'text'
|
|
69
|
-
? [
|
|
70
|
-
{ type: 'stream-start', warnings: [] },
|
|
71
|
-
{
|
|
72
|
-
type: 'response-metadata',
|
|
73
|
-
id: 'r',
|
|
74
|
-
modelId: 'mock',
|
|
75
|
-
timestamp: new Date(),
|
|
76
|
-
},
|
|
77
|
-
{ type: 'text-start', id: '1' },
|
|
78
|
-
{ type: 'text-delta', id: '1', delta: r.text },
|
|
79
|
-
{ type: 'text-end', id: '1' },
|
|
80
|
-
{
|
|
81
|
-
type: 'finish',
|
|
82
|
-
finishReason: { unified: 'stop', raw: 'stop' },
|
|
83
|
-
usage: {
|
|
84
|
-
inputTokens: { total: 5, noCache: 5 },
|
|
85
|
-
outputTokens: { total: 10, text: 10 },
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
]
|
|
89
|
-
: [
|
|
90
|
-
{ type: 'stream-start', warnings: [] },
|
|
91
|
-
{
|
|
92
|
-
type: 'response-metadata',
|
|
93
|
-
id: 'r',
|
|
94
|
-
modelId: 'mock',
|
|
95
|
-
timestamp: new Date(),
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
type: 'tool-call',
|
|
99
|
-
toolCallId: `call-${idx + 1}`,
|
|
100
|
-
toolName: r.toolName,
|
|
101
|
-
input: r.input,
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
type: 'finish',
|
|
105
|
-
finishReason: { unified: 'tool-calls', raw: undefined },
|
|
106
|
-
usage: {
|
|
107
|
-
inputTokens: { total: 5, noCache: 5 },
|
|
108
|
-
outputTokens: { total: 10, text: 10 },
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
];
|
|
112
|
-
return {
|
|
113
|
-
stream: new ReadableStream({
|
|
114
|
-
start(c) {
|
|
115
|
-
for (const p of parts as any[]) c.enqueue(p);
|
|
116
|
-
c.close();
|
|
117
|
-
},
|
|
118
|
-
}),
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
};
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
});
|
|
123
110
|
}
|
|
@@ -23,6 +23,8 @@ export type SerializableToolDef = {
|
|
|
23
23
|
inputSchema: JSONSchema7;
|
|
24
24
|
/** Present on provider tools (e.g. anthropic.tools.webSearch). */
|
|
25
25
|
type?: 'provider';
|
|
26
|
+
/** Provider tool is executed by the provider. */
|
|
27
|
+
isProviderExecuted?: boolean;
|
|
26
28
|
/** Provider tool ID, e.g. 'anthropic.web_search_20250305'. */
|
|
27
29
|
id?: `${string}.${string}`;
|
|
28
30
|
/** Provider tool configuration args (maxUses, allowedDomains, etc.). */
|
|
@@ -41,7 +43,7 @@ export function serializeToolSet(
|
|
|
41
43
|
return Object.fromEntries(
|
|
42
44
|
Object.entries(tools).map(([name, t]) => {
|
|
43
45
|
const def: SerializableToolDef = {
|
|
44
|
-
description: t.description,
|
|
46
|
+
description: t.description as string, // TODO support tools with function descriptions
|
|
45
47
|
inputSchema: asSchema(t.inputSchema).jsonSchema as JSONSchema7,
|
|
46
48
|
};
|
|
47
49
|
|
|
@@ -49,6 +51,7 @@ export function serializeToolSet(
|
|
|
49
51
|
// them as provider-executed tools (e.g. anthropic webSearch).
|
|
50
52
|
if ((t as any).type === 'provider') {
|
|
51
53
|
def.type = 'provider';
|
|
54
|
+
def.isProviderExecuted = (t as any).isProviderExecuted ?? false;
|
|
52
55
|
def.id = (t as any).id;
|
|
53
56
|
def.args = (t as any).args;
|
|
54
57
|
}
|
|
@@ -81,6 +84,7 @@ export function resolveSerializableTools(
|
|
|
81
84
|
type: 'provider' as const,
|
|
82
85
|
id: t.id!,
|
|
83
86
|
args: t.args ?? {},
|
|
87
|
+
isProviderExecuted: t.isProviderExecuted ?? false,
|
|
84
88
|
inputSchema: jsonSchema(t.inputSchema),
|
|
85
89
|
}),
|
|
86
90
|
];
|