@yolk-sdk/vercel-workflows 0.1.0-canary.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/LICENSE +21 -0
- package/README.md +340 -0
- package/dist/effect.d.mts +56 -0
- package/dist/effect.d.mts.map +1 -0
- package/dist/effect.mjs +58 -0
- package/dist/effect.mjs.map +1 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +4 -0
- package/dist/testing/index.d.mts +97 -0
- package/dist/testing/index.d.mts.map +1 -0
- package/dist/testing/index.mjs +285 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/workflow-children.d.mts +42 -0
- package/dist/workflow-children.d.mts.map +1 -0
- package/dist/workflow-children.mjs +54 -0
- package/dist/workflow-children.mjs.map +1 -0
- package/dist/workflow-events.d.mts +210 -0
- package/dist/workflow-events.d.mts.map +1 -0
- package/dist/workflow-events.mjs +54 -0
- package/dist/workflow-events.mjs.map +1 -0
- package/dist/workflow-loop.d.mts +416 -0
- package/dist/workflow-loop.d.mts.map +1 -0
- package/dist/workflow-loop.mjs +177 -0
- package/dist/workflow-loop.mjs.map +1 -0
- package/dist/workflow.d.mts +4 -0
- package/dist/workflow.mjs +4 -0
- package/package.json +77 -0
- package/src/effect.ts +232 -0
- package/src/index.ts +36 -0
- package/src/testing/index.ts +503 -0
- package/src/workflow-children.ts +81 -0
- package/src/workflow-events.ts +202 -0
- package/src/workflow-loop.ts +404 -0
- package/src/workflow.ts +42 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { Data, Effect } from 'effect'
|
|
2
|
+
|
|
3
|
+
export type DurableAgentEventSequencerState = {
|
|
4
|
+
readonly eventSequence: number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type DurableAgentEvent<Event extends object = object> = Event & {
|
|
8
|
+
readonly eventId: string
|
|
9
|
+
readonly createdAtMs?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type DurableAgentEventIdInput = {
|
|
13
|
+
readonly streamId: string
|
|
14
|
+
readonly turn: number
|
|
15
|
+
readonly eventSequence: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type SequenceDurableAgentEventInput<Event extends object = object> = {
|
|
19
|
+
readonly state: DurableAgentEventSequencerState
|
|
20
|
+
/**
|
|
21
|
+
* Stable id namespace for one independent durable stream.
|
|
22
|
+
*
|
|
23
|
+
* Include the workflow/session/run id when multiple logical runs can be
|
|
24
|
+
* projected in the same client. Replay-safe clients de-dupe by `eventId`, so
|
|
25
|
+
* reusing a stream id across runs can drop legitimate follow-up events.
|
|
26
|
+
*/
|
|
27
|
+
readonly streamId: string
|
|
28
|
+
readonly turn: number
|
|
29
|
+
readonly event: Event
|
|
30
|
+
readonly createdAtMs?: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type SequencedDurableAgentEvent<Event extends object = object> = {
|
|
34
|
+
readonly event: DurableAgentEvent<Event>
|
|
35
|
+
readonly eventSequence: number
|
|
36
|
+
readonly nextEventSequence: number
|
|
37
|
+
readonly nextState: DurableAgentEventSequencerState
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type WriteDurableAgentEventInput<Event extends object = object> =
|
|
41
|
+
SequenceDurableAgentEventInput<Event> & {
|
|
42
|
+
readonly writer: WritableStreamDefaultWriter<Uint8Array>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type CommitThenWriteTerminalEventInput<
|
|
46
|
+
TerminalEvent extends object,
|
|
47
|
+
TerminalWriteResult,
|
|
48
|
+
CommitErrorWriteResult,
|
|
49
|
+
CommitError,
|
|
50
|
+
TerminalWriteError,
|
|
51
|
+
CommitErrorWriteError
|
|
52
|
+
> = {
|
|
53
|
+
readonly commit: Effect.Effect<void, CommitError>
|
|
54
|
+
readonly terminal: TerminalEvent
|
|
55
|
+
readonly write: (event: TerminalEvent) => Effect.Effect<TerminalWriteResult, TerminalWriteError>
|
|
56
|
+
readonly writeCommitError: (
|
|
57
|
+
error: CommitError
|
|
58
|
+
) => Effect.Effect<CommitErrorWriteResult, CommitErrorWriteError>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type CommitThenWriteTerminalEventResult<TerminalWriteResult, CommitErrorWriteResult> =
|
|
62
|
+
Data.TaggedEnum<{
|
|
63
|
+
Committed: {
|
|
64
|
+
readonly writeResult: TerminalWriteResult
|
|
65
|
+
}
|
|
66
|
+
CommitFailed: {
|
|
67
|
+
readonly commitError: unknown
|
|
68
|
+
readonly writeResult: CommitErrorWriteResult
|
|
69
|
+
}
|
|
70
|
+
TerminalWriteFailed: {
|
|
71
|
+
readonly error: unknown
|
|
72
|
+
}
|
|
73
|
+
CommitErrorWriteFailed: {
|
|
74
|
+
readonly commitError: unknown
|
|
75
|
+
readonly error: unknown
|
|
76
|
+
}
|
|
77
|
+
}>
|
|
78
|
+
|
|
79
|
+
interface CommitThenWriteTerminalEventResultDefinition extends Data.TaggedEnum.WithGenerics<2> {
|
|
80
|
+
readonly taggedEnum: CommitThenWriteTerminalEventResult<this['A'], this['B']>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const CommitThenWriteTerminalEventResult =
|
|
84
|
+
Data.taggedEnum<CommitThenWriteTerminalEventResultDefinition>()
|
|
85
|
+
|
|
86
|
+
const textEncoder = new TextEncoder()
|
|
87
|
+
|
|
88
|
+
export const makeDurableAgentEventSequencerState = (
|
|
89
|
+
eventSequence = 0
|
|
90
|
+
): DurableAgentEventSequencerState => ({ eventSequence })
|
|
91
|
+
|
|
92
|
+
export const durableAgentEventId = (input: DurableAgentEventIdInput) =>
|
|
93
|
+
`${input.streamId}:${input.turn}:${input.eventSequence}`
|
|
94
|
+
|
|
95
|
+
export const sequenceDurableAgentEvent = <Event extends object>(
|
|
96
|
+
input: SequenceDurableAgentEventInput<Event>
|
|
97
|
+
): SequencedDurableAgentEvent<Event> => {
|
|
98
|
+
const eventSequence = input.state.eventSequence
|
|
99
|
+
const nextEventSequence = eventSequence + 1
|
|
100
|
+
|
|
101
|
+
type SequencedDurableAgentEventFields = Event & {
|
|
102
|
+
eventId: string
|
|
103
|
+
createdAtMs?: number
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const event: SequencedDurableAgentEventFields = {
|
|
107
|
+
...input.event,
|
|
108
|
+
eventId: durableAgentEventId({
|
|
109
|
+
streamId: input.streamId,
|
|
110
|
+
turn: input.turn,
|
|
111
|
+
eventSequence
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (input.createdAtMs !== undefined) {
|
|
116
|
+
event.createdAtMs = input.createdAtMs
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
event,
|
|
121
|
+
eventSequence,
|
|
122
|
+
nextEventSequence,
|
|
123
|
+
nextState: makeDurableAgentEventSequencerState(nextEventSequence)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const encodeDurableAgentEventNdjson = <Event extends object>(event: DurableAgentEvent<Event>) =>
|
|
128
|
+
textEncoder.encode(`${JSON.stringify(event)}\n`)
|
|
129
|
+
|
|
130
|
+
export const writeDurableAgentEvent = <Event extends object>(
|
|
131
|
+
input: WriteDurableAgentEventInput<Event>
|
|
132
|
+
): Effect.Effect<SequencedDurableAgentEvent<Event>, unknown> =>
|
|
133
|
+
Effect.gen(function* () {
|
|
134
|
+
const sequenced = sequenceDurableAgentEvent(input)
|
|
135
|
+
yield* Effect.tryPromise({
|
|
136
|
+
try: () => input.writer.write(encodeDurableAgentEventNdjson(sequenced.event)),
|
|
137
|
+
catch: cause => cause
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
return sequenced
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
export const commitThenWriteTerminalEvent = <
|
|
144
|
+
TerminalEvent extends object,
|
|
145
|
+
TerminalWriteResult,
|
|
146
|
+
CommitErrorWriteResult,
|
|
147
|
+
CommitError,
|
|
148
|
+
TerminalWriteError,
|
|
149
|
+
CommitErrorWriteError
|
|
150
|
+
>(
|
|
151
|
+
input: CommitThenWriteTerminalEventInput<
|
|
152
|
+
TerminalEvent,
|
|
153
|
+
TerminalWriteResult,
|
|
154
|
+
CommitErrorWriteResult,
|
|
155
|
+
CommitError,
|
|
156
|
+
TerminalWriteError,
|
|
157
|
+
CommitErrorWriteError
|
|
158
|
+
>
|
|
159
|
+
): Effect.Effect<
|
|
160
|
+
CommitThenWriteTerminalEventResult<TerminalWriteResult, CommitErrorWriteResult>,
|
|
161
|
+
never
|
|
162
|
+
> =>
|
|
163
|
+
input.commit.pipe(
|
|
164
|
+
Effect.matchEffect({
|
|
165
|
+
onFailure: commitError =>
|
|
166
|
+
input.writeCommitError(commitError).pipe(
|
|
167
|
+
Effect.match({
|
|
168
|
+
onFailure: error =>
|
|
169
|
+
CommitThenWriteTerminalEventResult.CommitErrorWriteFailed<
|
|
170
|
+
TerminalWriteResult,
|
|
171
|
+
CommitErrorWriteResult
|
|
172
|
+
>({
|
|
173
|
+
commitError,
|
|
174
|
+
error
|
|
175
|
+
}),
|
|
176
|
+
onSuccess: writeResult =>
|
|
177
|
+
CommitThenWriteTerminalEventResult.CommitFailed<
|
|
178
|
+
TerminalWriteResult,
|
|
179
|
+
CommitErrorWriteResult
|
|
180
|
+
>({
|
|
181
|
+
commitError,
|
|
182
|
+
writeResult
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
),
|
|
186
|
+
onSuccess: () =>
|
|
187
|
+
input.write(input.terminal).pipe(
|
|
188
|
+
Effect.match({
|
|
189
|
+
onFailure: error =>
|
|
190
|
+
CommitThenWriteTerminalEventResult.TerminalWriteFailed<
|
|
191
|
+
TerminalWriteResult,
|
|
192
|
+
CommitErrorWriteResult
|
|
193
|
+
>({ error }),
|
|
194
|
+
onSuccess: writeResult =>
|
|
195
|
+
CommitThenWriteTerminalEventResult.Committed<
|
|
196
|
+
TerminalWriteResult,
|
|
197
|
+
CommitErrorWriteResult
|
|
198
|
+
>({ writeResult })
|
|
199
|
+
})
|
|
200
|
+
)
|
|
201
|
+
})
|
|
202
|
+
)
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { Data, Predicate } from 'effect'
|
|
2
|
+
|
|
3
|
+
export type VercelAgentWorkflowInput = {
|
|
4
|
+
readonly request: unknown
|
|
5
|
+
readonly context: unknown
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type SerializableWorkflowState = {
|
|
9
|
+
readonly request: unknown
|
|
10
|
+
readonly messages?: ReadonlyArray<unknown>
|
|
11
|
+
readonly createdMessages: ReadonlyArray<unknown>
|
|
12
|
+
readonly usage?: unknown
|
|
13
|
+
readonly turn: number
|
|
14
|
+
readonly eventSequence?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type VercelAgentWorkflowModelStepInput = {
|
|
18
|
+
readonly context: unknown
|
|
19
|
+
readonly state: SerializableWorkflowState
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type VercelAgentWorkflowModelStepResult = {
|
|
23
|
+
readonly done: boolean
|
|
24
|
+
readonly messages: ReadonlyArray<unknown>
|
|
25
|
+
readonly createdMessages: ReadonlyArray<unknown>
|
|
26
|
+
readonly toolCalls: ReadonlyArray<unknown>
|
|
27
|
+
readonly usage: unknown
|
|
28
|
+
readonly turn: number
|
|
29
|
+
readonly eventSequence?: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type VercelAgentWorkflowToolBatchStepInput = {
|
|
33
|
+
readonly context: unknown
|
|
34
|
+
readonly request: unknown
|
|
35
|
+
readonly calls: ReadonlyArray<unknown>
|
|
36
|
+
readonly createdMessages: ReadonlyArray<unknown>
|
|
37
|
+
readonly hitlResponses?: ReadonlyArray<unknown>
|
|
38
|
+
readonly usage?: unknown
|
|
39
|
+
readonly turn?: number
|
|
40
|
+
readonly eventSequence?: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type VercelAgentWorkflowAwaitingInput = {
|
|
44
|
+
readonly hookToken: string
|
|
45
|
+
readonly requests: ReadonlyArray<unknown>
|
|
46
|
+
readonly messages: ReadonlyArray<unknown>
|
|
47
|
+
readonly usage: unknown
|
|
48
|
+
readonly turns: number
|
|
49
|
+
readonly eventSequence?: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type VercelAgentWorkflowToolBatchStepResult = {
|
|
53
|
+
readonly messages: ReadonlyArray<unknown>
|
|
54
|
+
readonly createdMessages: ReadonlyArray<unknown>
|
|
55
|
+
/** Updated cumulative usage when tools run nested model work such as subagents. */
|
|
56
|
+
readonly usage?: unknown
|
|
57
|
+
readonly awaitingInput?: VercelAgentWorkflowAwaitingInput
|
|
58
|
+
readonly eventSequence?: number
|
|
59
|
+
/**
|
|
60
|
+
* A plain wire-safe failure captured after partial progress. Returning it instead of rejecting
|
|
61
|
+
* lets orchestration retain usage and event sequencing from completed sibling tools.
|
|
62
|
+
*/
|
|
63
|
+
readonly failure?: unknown
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type WorkflowStepResult<A> = Data.TaggedEnum<{
|
|
67
|
+
Success: {
|
|
68
|
+
readonly value: A
|
|
69
|
+
}
|
|
70
|
+
Failure: {
|
|
71
|
+
readonly error: unknown
|
|
72
|
+
}
|
|
73
|
+
}>
|
|
74
|
+
|
|
75
|
+
interface WorkflowStepResultDefinition extends Data.TaggedEnum.WithGenerics<1> {
|
|
76
|
+
readonly taggedEnum: WorkflowStepResult<this['A']>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const WorkflowStepResult = Data.taggedEnum<WorkflowStepResultDefinition>()
|
|
80
|
+
|
|
81
|
+
export type VercelAgentWorkflowStepRetryPolicy = {
|
|
82
|
+
readonly maxAttempts: number
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type VercelAgentWorkflowRunResult = Data.TaggedEnum<{
|
|
86
|
+
Completed: {
|
|
87
|
+
readonly turns: number
|
|
88
|
+
readonly state: SerializableWorkflowState
|
|
89
|
+
}
|
|
90
|
+
ModelStepFailed: {
|
|
91
|
+
readonly turn: number
|
|
92
|
+
readonly error: unknown
|
|
93
|
+
readonly state: SerializableWorkflowState
|
|
94
|
+
}
|
|
95
|
+
ToolBatchStepFailed: {
|
|
96
|
+
readonly turn: number
|
|
97
|
+
readonly error: unknown
|
|
98
|
+
readonly state: SerializableWorkflowState
|
|
99
|
+
}
|
|
100
|
+
AwaitInputFailed: {
|
|
101
|
+
readonly turn: number
|
|
102
|
+
readonly error: unknown
|
|
103
|
+
readonly awaitingInput: VercelAgentWorkflowAwaitingInput
|
|
104
|
+
readonly state: SerializableWorkflowState
|
|
105
|
+
}
|
|
106
|
+
CloseStreamFailed: {
|
|
107
|
+
readonly turns: number
|
|
108
|
+
readonly error: unknown
|
|
109
|
+
readonly state: SerializableWorkflowState
|
|
110
|
+
}
|
|
111
|
+
MaxTurnsExceeded: {
|
|
112
|
+
readonly maxTurns: number
|
|
113
|
+
readonly error: Error
|
|
114
|
+
readonly state: SerializableWorkflowState
|
|
115
|
+
}
|
|
116
|
+
}>
|
|
117
|
+
|
|
118
|
+
export const VercelAgentWorkflowRunResult = Data.taggedEnum<VercelAgentWorkflowRunResult>()
|
|
119
|
+
|
|
120
|
+
export type VercelAgentWorkflowLoopConfig = {
|
|
121
|
+
readonly input: VercelAgentWorkflowInput
|
|
122
|
+
readonly maxTurns?: number
|
|
123
|
+
readonly runModelStep: (
|
|
124
|
+
input: VercelAgentWorkflowModelStepInput
|
|
125
|
+
) => Promise<VercelAgentWorkflowModelStepResult>
|
|
126
|
+
readonly runToolBatchStep: (
|
|
127
|
+
input: VercelAgentWorkflowToolBatchStepInput
|
|
128
|
+
) => Promise<VercelAgentWorkflowToolBatchStepResult>
|
|
129
|
+
/** Called only after a model step returns `done: true`. */
|
|
130
|
+
readonly closeStream: () => Promise<void>
|
|
131
|
+
/**
|
|
132
|
+
* Best-effort final failure handler. It must write a safe final error and
|
|
133
|
+
* close the failure stream; rejection is suppressed in favor of the
|
|
134
|
+
* original structured run result.
|
|
135
|
+
*/
|
|
136
|
+
readonly writeError: (error: unknown) => Promise<void>
|
|
137
|
+
/** A successful await suspends/resumes without invoking either finalizer. */
|
|
138
|
+
readonly awaitInput?: (input: VercelAgentWorkflowAwaitingInput) => Promise<unknown>
|
|
139
|
+
readonly modelStepRetry?: VercelAgentWorkflowStepRetryPolicy
|
|
140
|
+
readonly toolBatchStepRetry?: VercelAgentWorkflowStepRetryPolicy
|
|
141
|
+
readonly awaitInputRetry?: VercelAgentWorkflowStepRetryPolicy
|
|
142
|
+
readonly closeStreamRetry?: VercelAgentWorkflowStepRetryPolicy
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const defaultMaxWorkflowTurns = 500
|
|
146
|
+
|
|
147
|
+
export const noWorkflowStepRetry: VercelAgentWorkflowStepRetryPolicy = { maxAttempts: 1 }
|
|
148
|
+
|
|
149
|
+
export const settleWorkflowStep = <A>(promise: Promise<A>): Promise<WorkflowStepResult<A>> =>
|
|
150
|
+
promise.then(
|
|
151
|
+
value => WorkflowStepResult.Success<A>({ value }),
|
|
152
|
+
error => WorkflowStepResult.Failure<A>({ error })
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const workflowMaxTurnsError = (maxTurns: number) =>
|
|
156
|
+
new Error(`Vercel agent workflow exceeded max turns: ${maxTurns}`)
|
|
157
|
+
|
|
158
|
+
const writeErrorSafely = (writeError: (error: unknown) => Promise<void>, error: unknown) =>
|
|
159
|
+
writeError(error).catch(() => undefined)
|
|
160
|
+
|
|
161
|
+
const missingAwaitInputHandlerError = () =>
|
|
162
|
+
new Error(
|
|
163
|
+
'Vercel agent workflow awaiting input requested but no awaitInput handler is configured'
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
const maxRetryAttempts = (policy: VercelAgentWorkflowStepRetryPolicy | undefined) => {
|
|
167
|
+
const configured = policy?.maxAttempts ?? noWorkflowStepRetry.maxAttempts
|
|
168
|
+
|
|
169
|
+
return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 1
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const maxWorkflowTurns = (configured: number | undefined) => {
|
|
173
|
+
if (configured === undefined || !Number.isFinite(configured)) {
|
|
174
|
+
return defaultMaxWorkflowTurns
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return Math.max(1, Math.floor(configured))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function retryWorkflowStep<A>(
|
|
181
|
+
runStep: () => Promise<A>,
|
|
182
|
+
policy?: VercelAgentWorkflowStepRetryPolicy
|
|
183
|
+
): Promise<A> {
|
|
184
|
+
const maxAttempts = maxRetryAttempts(policy)
|
|
185
|
+
|
|
186
|
+
let attempt = 1
|
|
187
|
+
|
|
188
|
+
for (;;) {
|
|
189
|
+
try {
|
|
190
|
+
return await runStep()
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (attempt >= maxAttempts) {
|
|
193
|
+
throw error
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
attempt += 1
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function runVercelAgentWorkflow(
|
|
202
|
+
config: VercelAgentWorkflowLoopConfig
|
|
203
|
+
): Promise<VercelAgentWorkflowRunResult> {
|
|
204
|
+
const {
|
|
205
|
+
input,
|
|
206
|
+
maxTurns: configuredMaxTurns,
|
|
207
|
+
runModelStep,
|
|
208
|
+
runToolBatchStep,
|
|
209
|
+
closeStream,
|
|
210
|
+
writeError,
|
|
211
|
+
awaitInput,
|
|
212
|
+
modelStepRetry,
|
|
213
|
+
toolBatchStepRetry,
|
|
214
|
+
awaitInputRetry,
|
|
215
|
+
closeStreamRetry
|
|
216
|
+
} = config
|
|
217
|
+
|
|
218
|
+
let state: SerializableWorkflowState = {
|
|
219
|
+
request: input.request,
|
|
220
|
+
createdMessages: [],
|
|
221
|
+
turn: 1,
|
|
222
|
+
eventSequence: 0
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const maxTurns = maxWorkflowTurns(configuredMaxTurns)
|
|
226
|
+
|
|
227
|
+
for (let step = 0; step < maxTurns; step++) {
|
|
228
|
+
const modelResult = await settleWorkflowStep(
|
|
229
|
+
retryWorkflowStep(() => runModelStep({ context: input.context, state }), modelStepRetry)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if (Predicate.isTagged(modelResult, 'Failure')) {
|
|
233
|
+
await writeErrorSafely(writeError, modelResult.error)
|
|
234
|
+
|
|
235
|
+
return VercelAgentWorkflowRunResult.ModelStepFailed({
|
|
236
|
+
turn: state.turn,
|
|
237
|
+
error: modelResult.error,
|
|
238
|
+
state
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (modelResult.value.done) {
|
|
243
|
+
const terminalState: SerializableWorkflowState = {
|
|
244
|
+
request: input.request,
|
|
245
|
+
messages: modelResult.value.messages,
|
|
246
|
+
createdMessages: modelResult.value.createdMessages,
|
|
247
|
+
usage: modelResult.value.usage,
|
|
248
|
+
turn: modelResult.value.turn,
|
|
249
|
+
eventSequence: modelResult.value.eventSequence ?? state.eventSequence
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const closeResult = await settleWorkflowStep(retryWorkflowStep(closeStream, closeStreamRetry))
|
|
253
|
+
|
|
254
|
+
if (Predicate.isTagged(closeResult, 'Failure')) {
|
|
255
|
+
await writeErrorSafely(writeError, closeResult.error)
|
|
256
|
+
|
|
257
|
+
return VercelAgentWorkflowRunResult.CloseStreamFailed({
|
|
258
|
+
turns: modelResult.value.turn,
|
|
259
|
+
error: closeResult.error,
|
|
260
|
+
state: terminalState
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return VercelAgentWorkflowRunResult.Completed({
|
|
265
|
+
turns: modelResult.value.turn,
|
|
266
|
+
state: terminalState
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (modelResult.value.toolCalls.length === 0) {
|
|
271
|
+
state = {
|
|
272
|
+
request: input.request,
|
|
273
|
+
messages: modelResult.value.messages,
|
|
274
|
+
createdMessages: modelResult.value.createdMessages,
|
|
275
|
+
usage: modelResult.value.usage,
|
|
276
|
+
turn: modelResult.value.turn + 1,
|
|
277
|
+
eventSequence: modelResult.value.eventSequence ?? state.eventSequence
|
|
278
|
+
}
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let completedToolsResult: VercelAgentWorkflowToolBatchStepResult | undefined
|
|
283
|
+
const toolHitlResponses: Array<unknown> = []
|
|
284
|
+
let cumulativeUsage = modelResult.value.usage
|
|
285
|
+
let toolEventSequence = modelResult.value.eventSequence ?? state.eventSequence
|
|
286
|
+
|
|
287
|
+
const currentTurnState = (
|
|
288
|
+
toolResult?: VercelAgentWorkflowToolBatchStepResult
|
|
289
|
+
): SerializableWorkflowState => ({
|
|
290
|
+
request: input.request,
|
|
291
|
+
messages:
|
|
292
|
+
toolResult === undefined
|
|
293
|
+
? modelResult.value.messages
|
|
294
|
+
: [...modelResult.value.messages, ...toolResult.messages],
|
|
295
|
+
createdMessages: toolResult?.createdMessages ?? modelResult.value.createdMessages,
|
|
296
|
+
usage: cumulativeUsage,
|
|
297
|
+
turn: modelResult.value.turn,
|
|
298
|
+
eventSequence: toolEventSequence
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
for (;;) {
|
|
302
|
+
const toolsResult = await settleWorkflowStep(
|
|
303
|
+
retryWorkflowStep(
|
|
304
|
+
() =>
|
|
305
|
+
runToolBatchStep({
|
|
306
|
+
context: input.context,
|
|
307
|
+
request: input.request,
|
|
308
|
+
calls: modelResult.value.toolCalls,
|
|
309
|
+
createdMessages: modelResult.value.createdMessages,
|
|
310
|
+
hitlResponses: toolHitlResponses.slice(),
|
|
311
|
+
usage: cumulativeUsage,
|
|
312
|
+
turn: modelResult.value.turn,
|
|
313
|
+
eventSequence: toolEventSequence
|
|
314
|
+
}),
|
|
315
|
+
toolBatchStepRetry
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
if (Predicate.isTagged(toolsResult, 'Failure')) {
|
|
320
|
+
await writeErrorSafely(writeError, toolsResult.error)
|
|
321
|
+
|
|
322
|
+
return VercelAgentWorkflowRunResult.ToolBatchStepFailed({
|
|
323
|
+
turn: modelResult.value.turn,
|
|
324
|
+
error: toolsResult.error,
|
|
325
|
+
state: currentTurnState()
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
cumulativeUsage = toolsResult.value.usage ?? cumulativeUsage
|
|
330
|
+
toolEventSequence = toolsResult.value.eventSequence ?? toolEventSequence
|
|
331
|
+
|
|
332
|
+
if (toolsResult.value.failure !== undefined) {
|
|
333
|
+
await writeErrorSafely(writeError, toolsResult.value.failure)
|
|
334
|
+
|
|
335
|
+
return VercelAgentWorkflowRunResult.ToolBatchStepFailed({
|
|
336
|
+
turn: modelResult.value.turn,
|
|
337
|
+
error: toolsResult.value.failure,
|
|
338
|
+
state: currentTurnState(toolsResult.value)
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (toolsResult.value.awaitingInput === undefined) {
|
|
343
|
+
completedToolsResult = toolsResult.value
|
|
344
|
+
break
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const awaitingInput = toolsResult.value.awaitingInput
|
|
348
|
+
cumulativeUsage = toolsResult.value.usage ?? awaitingInput.usage ?? cumulativeUsage
|
|
349
|
+
toolEventSequence = awaitingInput.eventSequence ?? toolEventSequence
|
|
350
|
+
|
|
351
|
+
const hitlResponse = await settleWorkflowStep(
|
|
352
|
+
retryWorkflowStep(() => {
|
|
353
|
+
if (awaitInput === undefined) {
|
|
354
|
+
return Promise.reject(missingAwaitInputHandlerError())
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return awaitInput(awaitingInput)
|
|
358
|
+
}, awaitInputRetry)
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
if (Predicate.isTagged(hitlResponse, 'Failure')) {
|
|
362
|
+
await writeErrorSafely(writeError, hitlResponse.error)
|
|
363
|
+
|
|
364
|
+
return VercelAgentWorkflowRunResult.AwaitInputFailed({
|
|
365
|
+
turn: modelResult.value.turn,
|
|
366
|
+
error: hitlResponse.error,
|
|
367
|
+
awaitingInput,
|
|
368
|
+
state: currentTurnState(toolsResult.value)
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
toolHitlResponses.push(hitlResponse.value)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (completedToolsResult === undefined) {
|
|
376
|
+
const error = new Error('Vercel agent workflow tool batch did not complete')
|
|
377
|
+
await writeErrorSafely(writeError, error)
|
|
378
|
+
|
|
379
|
+
return VercelAgentWorkflowRunResult.ToolBatchStepFailed({
|
|
380
|
+
turn: modelResult.value.turn,
|
|
381
|
+
error,
|
|
382
|
+
state
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
state = {
|
|
387
|
+
request: input.request,
|
|
388
|
+
messages: [...modelResult.value.messages, ...completedToolsResult.messages],
|
|
389
|
+
createdMessages: completedToolsResult.createdMessages,
|
|
390
|
+
usage: completedToolsResult.usage ?? cumulativeUsage,
|
|
391
|
+
turn: modelResult.value.turn + 1,
|
|
392
|
+
eventSequence: completedToolsResult.eventSequence ?? toolEventSequence
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const error = workflowMaxTurnsError(maxTurns)
|
|
397
|
+
await writeErrorSafely(writeError, error)
|
|
398
|
+
|
|
399
|
+
return VercelAgentWorkflowRunResult.MaxTurnsExceeded({
|
|
400
|
+
maxTurns,
|
|
401
|
+
error,
|
|
402
|
+
state
|
|
403
|
+
})
|
|
404
|
+
}
|
package/src/workflow.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export {
|
|
2
|
+
defaultMaxWorkflowTurns,
|
|
3
|
+
noWorkflowStepRetry,
|
|
4
|
+
retryWorkflowStep,
|
|
5
|
+
runVercelAgentWorkflow,
|
|
6
|
+
settleWorkflowStep,
|
|
7
|
+
VercelAgentWorkflowRunResult,
|
|
8
|
+
WorkflowStepResult
|
|
9
|
+
} from './workflow-loop.ts'
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
commitThenWriteTerminalEvent,
|
|
13
|
+
durableAgentEventId,
|
|
14
|
+
makeDurableAgentEventSequencerState,
|
|
15
|
+
sequenceDurableAgentEvent,
|
|
16
|
+
writeDurableAgentEvent
|
|
17
|
+
} from './workflow-events.ts'
|
|
18
|
+
|
|
19
|
+
export type {
|
|
20
|
+
SerializableWorkflowState,
|
|
21
|
+
VercelAgentWorkflowAwaitingInput,
|
|
22
|
+
VercelAgentWorkflowInput,
|
|
23
|
+
VercelAgentWorkflowLoopConfig,
|
|
24
|
+
VercelAgentWorkflowModelStepInput,
|
|
25
|
+
VercelAgentWorkflowModelStepResult,
|
|
26
|
+
VercelAgentWorkflowStepRetryPolicy,
|
|
27
|
+
VercelAgentWorkflowToolBatchStepInput,
|
|
28
|
+
VercelAgentWorkflowToolBatchStepResult
|
|
29
|
+
} from './workflow-loop.ts'
|
|
30
|
+
|
|
31
|
+
export type {
|
|
32
|
+
DurableAgentEvent,
|
|
33
|
+
DurableAgentEventIdInput,
|
|
34
|
+
DurableAgentEventSequencerState,
|
|
35
|
+
CommitThenWriteTerminalEventInput,
|
|
36
|
+
CommitThenWriteTerminalEventResult,
|
|
37
|
+
SequenceDurableAgentEventInput,
|
|
38
|
+
SequencedDurableAgentEvent,
|
|
39
|
+
WriteDurableAgentEventInput
|
|
40
|
+
} from './workflow-events.ts'
|
|
41
|
+
|
|
42
|
+
export { awaitWorkflowChild, orchestrateWorkflowToolBatch } from './workflow-children.ts'
|