@ai-sdk/workflow 2.0.57 → 2.0.59
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 +31 -0
- package/dist/index.d.ts +61 -43
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +681 -299
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/build-model-step-result.ts +329 -0
- package/src/do-generate-step.ts +212 -0
- package/src/do-stream-step.ts +50 -179
- package/src/index.ts +3 -0
- package/src/{stream-text-iterator.ts → model-call-iterator.ts} +117 -297
- package/src/model-call.ts +196 -0
- package/src/providers/mock-function-wrapper.ts +32 -0
- package/src/test/agent-e2e-workflows.ts +60 -1
- package/src/test/approval-workflows.ts +105 -0
- package/src/test/generation-boundary-model.ts +115 -0
- package/src/test/generation-boundary-workflows.ts +106 -0
- package/src/test/model-call-payload-workflow.ts +132 -0
- package/src/to-model-response-messages.ts +28 -0
- package/src/workflow-agent.ts +509 -201
- package/src/workflow-execution-result.ts +55 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { getWritable, sleep } from 'workflow';
|
|
2
|
+
import { buildModelStepResult } from '../build-model-step-result.js';
|
|
3
|
+
import type { ModelCallResult } from '../model-call.js';
|
|
4
|
+
|
|
5
|
+
export async function modelCallPayloadWorkflow() {
|
|
6
|
+
'use workflow';
|
|
7
|
+
|
|
8
|
+
const payloads = await producePayloads();
|
|
9
|
+
await sleep('1h');
|
|
10
|
+
const restored = await roundTripPayloads(payloads);
|
|
11
|
+
|
|
12
|
+
return Promise.all(
|
|
13
|
+
restored.map(async payload => {
|
|
14
|
+
if (payload.aborted) return { aborted: true };
|
|
15
|
+
|
|
16
|
+
// Reconstruct files and derived fields only in workflow context. Return
|
|
17
|
+
// selected data, not the reconstructed result's generated-file instances.
|
|
18
|
+
const step = await buildModelStepResult(
|
|
19
|
+
payload.raw,
|
|
20
|
+
payload.toolCalls,
|
|
21
|
+
payload.finish,
|
|
22
|
+
payload.providerExecutedToolResults,
|
|
23
|
+
{ stepNumber: 0, runtimeContext: {}, toolsContext: {} },
|
|
24
|
+
);
|
|
25
|
+
return {
|
|
26
|
+
text: step.text,
|
|
27
|
+
file: {
|
|
28
|
+
base64: step.files[0].base64,
|
|
29
|
+
mediaType: step.files[0].mediaType,
|
|
30
|
+
},
|
|
31
|
+
timestamp: step.response.timestamp,
|
|
32
|
+
sourceIds: step.sources.map(source => source.id),
|
|
33
|
+
contentTypes: step.content.map(part => part.type),
|
|
34
|
+
providerResult: payload.providerExecutedToolResults.get('lookup-1'),
|
|
35
|
+
providerMetadata: step.providerMetadata,
|
|
36
|
+
warnings: step.warnings,
|
|
37
|
+
hasTerminalError: 'terminalError' in payload,
|
|
38
|
+
terminalError: payload.terminalError,
|
|
39
|
+
// Derived fields must not inflate the durable model result.
|
|
40
|
+
rawKeys: Object.keys(payload.raw),
|
|
41
|
+
};
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function producePayloads(): Promise<ModelCallResult[]> {
|
|
47
|
+
'use step';
|
|
48
|
+
|
|
49
|
+
// The test observes this write after suspension/resumption to ensure this
|
|
50
|
+
// completed step is replayed from its result rather than executed again.
|
|
51
|
+
const writer = getWritable<string>().getWriter();
|
|
52
|
+
try {
|
|
53
|
+
await writer.write('produced');
|
|
54
|
+
await writer.close();
|
|
55
|
+
} finally {
|
|
56
|
+
writer.releaseLock();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const completed: ModelCallResult = {
|
|
60
|
+
toolCalls: [
|
|
61
|
+
{
|
|
62
|
+
type: 'tool-call',
|
|
63
|
+
toolCallId: 'lookup-1',
|
|
64
|
+
toolName: 'lookup',
|
|
65
|
+
input: { query: 'example' },
|
|
66
|
+
providerExecuted: true,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
finish: {
|
|
70
|
+
finishReason: 'stop',
|
|
71
|
+
rawFinishReason: 'end_turn',
|
|
72
|
+
usage: {
|
|
73
|
+
inputTokens: 3,
|
|
74
|
+
inputTokenDetails: {
|
|
75
|
+
noCacheTokens: 2,
|
|
76
|
+
cacheReadTokens: 1,
|
|
77
|
+
cacheWriteTokens: undefined,
|
|
78
|
+
},
|
|
79
|
+
outputTokens: 1,
|
|
80
|
+
outputTokenDetails: { textTokens: 1, reasoningTokens: undefined },
|
|
81
|
+
totalTokens: 4,
|
|
82
|
+
},
|
|
83
|
+
providerMetadata: { fixture: { retained: true } },
|
|
84
|
+
},
|
|
85
|
+
raw: {
|
|
86
|
+
content: [
|
|
87
|
+
{ type: 'text', text: 'answer' },
|
|
88
|
+
{ type: 'file', data: 'aGVsbG8=', mediaType: 'text/plain' },
|
|
89
|
+
{
|
|
90
|
+
type: 'source',
|
|
91
|
+
sourceType: 'url',
|
|
92
|
+
id: 'source-1',
|
|
93
|
+
url: 'https://example.com',
|
|
94
|
+
},
|
|
95
|
+
{ type: 'tool-call', toolCallIndex: 0 },
|
|
96
|
+
{ type: 'provider-tool-result', toolCallId: 'lookup-1' },
|
|
97
|
+
],
|
|
98
|
+
reasoning: [],
|
|
99
|
+
responseMetadata: {
|
|
100
|
+
id: 'response-1',
|
|
101
|
+
timestamp: new Date('2026-01-01T00:00:00Z'),
|
|
102
|
+
modelId: 'fixture:model',
|
|
103
|
+
},
|
|
104
|
+
warnings: [{ type: 'other', message: 'fixture warning' }],
|
|
105
|
+
},
|
|
106
|
+
providerExecutedToolResults: new Map([
|
|
107
|
+
[
|
|
108
|
+
'lookup-1',
|
|
109
|
+
{
|
|
110
|
+
toolCallId: 'lookup-1',
|
|
111
|
+
toolName: 'lookup',
|
|
112
|
+
result: { answer: 42 },
|
|
113
|
+
providerMetadata: { fixture: { source: 'retained' } },
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
]),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return [
|
|
120
|
+
completed,
|
|
121
|
+
{ ...completed, terminalError: undefined },
|
|
122
|
+
{ ...completed, terminalError: new Error('model failed') },
|
|
123
|
+
{ aborted: true },
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function roundTripPayloads(
|
|
128
|
+
payloads: ModelCallResult[],
|
|
129
|
+
): Promise<ModelCallResult[]> {
|
|
130
|
+
'use step';
|
|
131
|
+
return payloads;
|
|
132
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { LanguageModelV4Prompt } from '@ai-sdk/provider';
|
|
2
|
+
import type { GenerateTextResult } from 'ai';
|
|
3
|
+
|
|
4
|
+
/** Convert provider file references back to public message data without re-running tool output hooks. */
|
|
5
|
+
export function toModelResponseMessages(
|
|
6
|
+
messages: LanguageModelV4Prompt,
|
|
7
|
+
): GenerateTextResult<any, any, any>['responseMessages'] {
|
|
8
|
+
return messages.map(message =>
|
|
9
|
+
message.role !== 'assistant'
|
|
10
|
+
? message
|
|
11
|
+
: {
|
|
12
|
+
...message,
|
|
13
|
+
content: message.content.map(part => {
|
|
14
|
+
if (part.type !== 'file' && part.type !== 'reasoning-file')
|
|
15
|
+
return part;
|
|
16
|
+
return {
|
|
17
|
+
...part,
|
|
18
|
+
data:
|
|
19
|
+
part.data.type === 'data'
|
|
20
|
+
? part.data.data
|
|
21
|
+
: part.data.type === 'url'
|
|
22
|
+
? part.data.url
|
|
23
|
+
: part.data,
|
|
24
|
+
};
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
) as GenerateTextResult<any, any, any>['responseMessages'];
|
|
28
|
+
}
|