@ai-sdk/workflow 0.0.0-6b196531-20260710185421
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 +1145 -0
- package/LICENSE +13 -0
- package/README.md +62 -0
- package/dist/index.d.ts +1188 -0
- package/dist/index.js +2522 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
- package/src/create-language-model-tool-result-output.ts +85 -0
- package/src/do-stream-step.ts +265 -0
- package/src/index.ts +49 -0
- package/src/normalize-ui-message-stream.ts +164 -0
- package/src/providers/mock-function-wrapper.ts +11 -0
- package/src/providers/mock.ts +110 -0
- package/src/serializable-schema.ts +114 -0
- package/src/stream-text-iterator.ts +661 -0
- package/src/test/agent-e2e-workflows.ts +673 -0
- package/src/test/calculate-workflow.ts +19 -0
- package/src/test/test-sandbox.ts +26 -0
- package/src/to-ui-message-chunk.ts +238 -0
- package/src/types.ts +11 -0
- package/src/workflow-agent.ts +2930 -0
- package/src/workflow-chat-transport.ts +534 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { mockProvider } from './mock-function-wrapper.js';
|
|
2
|
+
|
|
3
|
+
export type MockResponseDescriptor =
|
|
4
|
+
| { type: 'text'; text: string }
|
|
5
|
+
| { type: 'tool-call'; toolName: string; input: string };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Mock model that returns a fixed text response.
|
|
9
|
+
*/
|
|
10
|
+
export function mockTextModel(text: string) {
|
|
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
|
+
? [
|
|
58
|
+
{ type: 'stream-start', warnings: [] },
|
|
59
|
+
{
|
|
60
|
+
type: 'response-metadata',
|
|
61
|
+
id: 'r',
|
|
62
|
+
modelId: 'mock',
|
|
63
|
+
timestamp: new Date(),
|
|
64
|
+
},
|
|
65
|
+
{ type: 'text-start', id: '1' },
|
|
66
|
+
{ type: 'text-delta', id: '1', delta: selectedResponse.text },
|
|
67
|
+
{ type: 'text-end', id: '1' },
|
|
68
|
+
{
|
|
69
|
+
type: 'finish',
|
|
70
|
+
finishReason: { unified: 'stop', raw: 'stop' },
|
|
71
|
+
usage: {
|
|
72
|
+
inputTokens: { total: 5, noCache: 5 },
|
|
73
|
+
outputTokens: { total: 10, text: 10 },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
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);
|
|
104
|
+
c.close();
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for passing tool schemas across workflow step boundaries.
|
|
3
|
+
*
|
|
4
|
+
* Tool schemas (zod, valibot, arktype, etc.) contain functions that can't be
|
|
5
|
+
* serialized by the workflow runtime. These helpers extract JSON Schema from
|
|
6
|
+
* schemas, then reconstruct tools with Ajv validation inside step functions.
|
|
7
|
+
*
|
|
8
|
+
* Uses `asSchema()` from `@ai-sdk/provider-utils` for JSON Schema extraction,
|
|
9
|
+
* which supports Standard Schema compatible libraries. When libraries adopt
|
|
10
|
+
* `~standard.jsonSchema` (Standard Schema v2), extraction can be simplified
|
|
11
|
+
* to use that interface directly.
|
|
12
|
+
*/
|
|
13
|
+
import type { JSONSchema7 } from '@ai-sdk/provider';
|
|
14
|
+
import { asSchema, jsonSchema } from '@ai-sdk/provider-utils';
|
|
15
|
+
import { tool, type ToolSet } from 'ai';
|
|
16
|
+
import Ajv from 'ajv';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Serializable tool definition — plain objects only, safe for workflow steps.
|
|
20
|
+
*/
|
|
21
|
+
export type SerializableToolDef = {
|
|
22
|
+
description?: string;
|
|
23
|
+
inputSchema: JSONSchema7;
|
|
24
|
+
/** Present on provider tools (e.g. anthropic.tools.webSearch). */
|
|
25
|
+
type?: 'provider';
|
|
26
|
+
/** Provider tool is executed by the provider. */
|
|
27
|
+
isProviderExecuted?: boolean;
|
|
28
|
+
/** Provider tool ID, e.g. 'anthropic.web_search_20250305'. */
|
|
29
|
+
id?: `${string}.${string}`;
|
|
30
|
+
/** Provider tool configuration args (maxUses, allowedDomains, etc.). */
|
|
31
|
+
args?: Record<string, unknown>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Converts a ToolSet (with zod/standard schemas and execute functions) to a
|
|
36
|
+
* serializable record of tool definitions. Only description and inputSchema
|
|
37
|
+
* (as JSON Schema) are preserved — execute functions are stripped since they
|
|
38
|
+
* run outside the step.
|
|
39
|
+
*/
|
|
40
|
+
export function serializeToolSet(
|
|
41
|
+
tools: ToolSet,
|
|
42
|
+
): Record<string, SerializableToolDef> {
|
|
43
|
+
return Object.fromEntries(
|
|
44
|
+
Object.entries(tools).map(([name, t]) => {
|
|
45
|
+
const def: SerializableToolDef = {
|
|
46
|
+
description: t.description as string, // TODO support tools with function descriptions
|
|
47
|
+
inputSchema: asSchema(t.inputSchema).jsonSchema as JSONSchema7,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Preserve provider tool identity so the Gateway can recognize
|
|
51
|
+
// them as provider-executed tools (e.g. anthropic webSearch).
|
|
52
|
+
if ((t as any).type === 'provider') {
|
|
53
|
+
def.type = 'provider';
|
|
54
|
+
def.isProviderExecuted = (t as any).isProviderExecuted ?? false;
|
|
55
|
+
def.id = (t as any).id;
|
|
56
|
+
def.args = (t as any).args;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return [name, def];
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Reconstructs tool objects from serializable tool definitions inside a step.
|
|
66
|
+
*
|
|
67
|
+
* Wraps each tool's JSON Schema with `jsonSchema()` and validates tool call
|
|
68
|
+
* arguments against the schema using Ajv. This provides runtime type safety
|
|
69
|
+
* equivalent to using zod schemas directly with the AI SDK.
|
|
70
|
+
*/
|
|
71
|
+
export function resolveSerializableTools(
|
|
72
|
+
tools: Record<string, SerializableToolDef>,
|
|
73
|
+
): ToolSet {
|
|
74
|
+
const ajv = new Ajv();
|
|
75
|
+
|
|
76
|
+
return Object.fromEntries(
|
|
77
|
+
Object.entries(tools).map(([name, t]) => {
|
|
78
|
+
// Provider tools are executed server-side — pass them through
|
|
79
|
+
// with their identity intact, no client-side validation needed.
|
|
80
|
+
if (t.type === 'provider') {
|
|
81
|
+
return [
|
|
82
|
+
name,
|
|
83
|
+
tool({
|
|
84
|
+
type: 'provider' as const,
|
|
85
|
+
id: t.id!,
|
|
86
|
+
args: t.args ?? {},
|
|
87
|
+
isProviderExecuted: t.isProviderExecuted ?? false,
|
|
88
|
+
inputSchema: jsonSchema(t.inputSchema),
|
|
89
|
+
}),
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const validateFn = ajv.compile(t.inputSchema);
|
|
94
|
+
|
|
95
|
+
return [
|
|
96
|
+
name,
|
|
97
|
+
tool({
|
|
98
|
+
description: t.description,
|
|
99
|
+
inputSchema: jsonSchema(t.inputSchema, {
|
|
100
|
+
validate: value => {
|
|
101
|
+
if (validateFn(value)) {
|
|
102
|
+
return { success: true, value: value as any };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: new Error(ajv.errorsText(validateFn.errors)),
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
}),
|
|
111
|
+
];
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
}
|