@yolk-sdk/vercel-workflows 0.1.0-canary.15

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yolk SDK contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # @yolk-sdk/vercel-workflows
2
+
3
+ Vercel Workflow-specific contracts for durable agent model/tool step loops.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @yolk-sdk/vercel-workflows@canary effect workflow
9
+ ```
10
+
11
+ Canary APIs are unstable. Keep all `@yolk-sdk/*` packages on the same version.
12
+
13
+ ## Subpaths
14
+
15
+ | Subpath | Purpose |
16
+ | --- | --- |
17
+ | `@yolk-sdk/vercel-workflows` | Workflow-safe agent loop orchestration APIs |
18
+ | `@yolk-sdk/vercel-workflows/workflow` | Explicit equivalent subpath |
19
+
20
+ ## Imports
21
+
22
+ Import Workflow APIs from the package root:
23
+
24
+ ```ts
25
+ import {
26
+ noWorkflowStepRetry,
27
+ runVercelAgentWorkflow
28
+ } from '@yolk-sdk/vercel-workflows'
29
+ ```
30
+
31
+ The `./workflow` subpath is also exported for explicit imports.
32
+
33
+ ## Runtime model
34
+
35
+ `runVercelAgentWorkflow` coordinates host-provided callbacks:
36
+
37
+ - model step: produce model events/tool calls
38
+ - tool batch step: execute requested tools
39
+ - awaiting-input state: carry pending HITL hook data, wait through `awaitInput`, then rerun the
40
+ same tool batch with accumulated responses
41
+ - close step: flush/close output stream
42
+ - terminal status: completed, step failure, await-input failure, close failure, or max turns exceeded
43
+
44
+ Continuation state stays plain serializable data for Workflow persistence.
45
+
46
+ ## Minimal workflow wrapper
47
+
48
+ Host apps keep concrete Workflow directives local and pass them into the package loop:
49
+
50
+ ```ts
51
+ import { createHook } from 'workflow'
52
+ import { runVercelAgentWorkflow } from '@yolk-sdk/vercel-workflows'
53
+
54
+ export async function runAgentWorkflow(input: { request: unknown; context: unknown }) {
55
+ 'use workflow'
56
+
57
+ return await runVercelAgentWorkflow({
58
+ input,
59
+ runModelStep,
60
+ runToolBatchStep,
61
+ closeStream,
62
+ writeError,
63
+ awaitInput: async awaitingInput => {
64
+ using hook = createHook<unknown>({ token: awaitingInput.hookToken })
65
+
66
+ return await hook
67
+ }
68
+ })
69
+ }
70
+ ```
71
+
72
+ `runModelStep`, `runToolBatchStep`, `closeStream`, and `writeError` are host-owned
73
+ `'use step'` functions. Keep provider calls, tools, persistence, telemetry, and Effect runtimes in
74
+ those steps, not in the `'use workflow'` orchestration body.
75
+
76
+ ## Awaiting input
77
+
78
+ When a tool batch needs HITL input, return `awaitingInput` from `runToolBatchStep`. The package
79
+ loop will:
80
+
81
+ 1. pass that payload to `awaitInput`
82
+ 2. wait for the hook/webhook response
83
+ 3. rerun the same tool batch with `hitlResponses: [...previous, response]`
84
+
85
+ The host owns `hookToken` construction, auth, resume routes, and response decoding. Use stable
86
+ tokens that the resume side can reconstruct or store.
87
+
88
+ ## Retry policy
89
+
90
+ Default retry policy is `noWorkflowStepRetry` (`maxAttempts: 1`). Retries are opt-in because streamed retries can duplicate chunks unless host/client de-dupe is ready.
91
+
92
+ ## Host responsibilities
93
+
94
+ - Own Next/Vercel routes, auth, providers, tools, persistence, and telemetry.
95
+ - Encode/decode app transcript/session state into serializable Workflow inputs.
96
+ - Decide cancellation/resume/conflict UX.
97
+ - Own hook tokens and response validation for HITL resume.
98
+ - Test directive behavior with `@workflow/vitest` when changing package-owned Workflow files.
99
+
100
+ ## Boundaries
101
+
102
+ - No Next routes, server actions, auth, app tools, DB, UI, or provider SDKs.
103
+ - Effect may run inside host/package step callbacks; workflow orchestration keeps plain data contracts.
@@ -0,0 +1,2 @@
1
+ import { SerializableWorkflowState, VercelAgentWorkflowAwaitingInput, VercelAgentWorkflowInput, VercelAgentWorkflowLoopConfig, VercelAgentWorkflowModelStepInput, VercelAgentWorkflowModelStepResult, VercelAgentWorkflowRunResult, VercelAgentWorkflowStepRetryPolicy, VercelAgentWorkflowToolBatchStepInput, VercelAgentWorkflowToolBatchStepResult, WorkflowStepResult, defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep } from "./workflow-loop.mjs";
2
+ export { type SerializableWorkflowState, type VercelAgentWorkflowAwaitingInput, type VercelAgentWorkflowInput, type VercelAgentWorkflowLoopConfig, type VercelAgentWorkflowModelStepInput, type VercelAgentWorkflowModelStepResult, type VercelAgentWorkflowRunResult, type VercelAgentWorkflowStepRetryPolicy, type VercelAgentWorkflowToolBatchStepInput, type VercelAgentWorkflowToolBatchStepResult, type WorkflowStepResult, defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep } from "./workflow-loop.mjs";
2
+ export { defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep };
@@ -0,0 +1,112 @@
1
+ //#region src/workflow-loop.d.ts
2
+ type VercelAgentWorkflowInput = {
3
+ readonly request: unknown;
4
+ readonly context: unknown;
5
+ };
6
+ type SerializableWorkflowState = {
7
+ readonly request: unknown;
8
+ readonly messages?: ReadonlyArray<unknown>;
9
+ readonly createdMessages: ReadonlyArray<unknown>;
10
+ readonly usage?: unknown;
11
+ readonly turn: number;
12
+ readonly eventSequence?: number;
13
+ };
14
+ type VercelAgentWorkflowModelStepInput = {
15
+ readonly context: unknown;
16
+ readonly state: SerializableWorkflowState;
17
+ };
18
+ type VercelAgentWorkflowModelStepResult = {
19
+ readonly done: boolean;
20
+ readonly messages: ReadonlyArray<unknown>;
21
+ readonly createdMessages: ReadonlyArray<unknown>;
22
+ readonly toolCalls: ReadonlyArray<unknown>;
23
+ readonly usage: unknown;
24
+ readonly turn: number;
25
+ readonly eventSequence?: number;
26
+ };
27
+ type VercelAgentWorkflowToolBatchStepInput = {
28
+ readonly context: unknown;
29
+ readonly request: unknown;
30
+ readonly calls: ReadonlyArray<unknown>;
31
+ readonly createdMessages: ReadonlyArray<unknown>;
32
+ readonly hitlResponses?: ReadonlyArray<unknown>;
33
+ readonly usage?: unknown;
34
+ readonly turn?: number;
35
+ readonly eventSequence?: number;
36
+ };
37
+ type VercelAgentWorkflowAwaitingInput = {
38
+ readonly hookToken: string;
39
+ readonly requests: ReadonlyArray<unknown>;
40
+ readonly messages: ReadonlyArray<unknown>;
41
+ readonly usage: unknown;
42
+ readonly turns: number;
43
+ readonly eventSequence?: number;
44
+ };
45
+ type VercelAgentWorkflowToolBatchStepResult = {
46
+ readonly messages: ReadonlyArray<unknown>;
47
+ readonly createdMessages: ReadonlyArray<unknown>;
48
+ readonly awaitingInput?: VercelAgentWorkflowAwaitingInput;
49
+ readonly eventSequence?: number;
50
+ };
51
+ type WorkflowStepResult<A> = {
52
+ readonly _tag: 'Success';
53
+ readonly value: A;
54
+ } | {
55
+ readonly _tag: 'Failure';
56
+ readonly error: unknown;
57
+ };
58
+ type VercelAgentWorkflowStepRetryPolicy = {
59
+ readonly maxAttempts: number;
60
+ };
61
+ type VercelAgentWorkflowRunResult = {
62
+ readonly _tag: 'Completed';
63
+ readonly turns: number;
64
+ readonly state: SerializableWorkflowState;
65
+ } | {
66
+ readonly _tag: 'ModelStepFailed';
67
+ readonly turn: number;
68
+ readonly error: unknown;
69
+ readonly state: SerializableWorkflowState;
70
+ } | {
71
+ readonly _tag: 'ToolBatchStepFailed';
72
+ readonly turn: number;
73
+ readonly error: unknown;
74
+ readonly state: SerializableWorkflowState;
75
+ } | {
76
+ readonly _tag: 'AwaitInputFailed';
77
+ readonly turn: number;
78
+ readonly error: unknown;
79
+ readonly awaitingInput: VercelAgentWorkflowAwaitingInput;
80
+ readonly state: SerializableWorkflowState;
81
+ } | {
82
+ readonly _tag: 'CloseStreamFailed';
83
+ readonly turns: number;
84
+ readonly error: unknown;
85
+ readonly state: SerializableWorkflowState;
86
+ } | {
87
+ readonly _tag: 'MaxTurnsExceeded';
88
+ readonly maxTurns: number;
89
+ readonly error: Error;
90
+ readonly state: SerializableWorkflowState;
91
+ };
92
+ type VercelAgentWorkflowLoopConfig = {
93
+ readonly input: VercelAgentWorkflowInput;
94
+ readonly maxTurns?: number;
95
+ readonly runModelStep: (input: VercelAgentWorkflowModelStepInput) => Promise<VercelAgentWorkflowModelStepResult>;
96
+ readonly runToolBatchStep: (input: VercelAgentWorkflowToolBatchStepInput) => Promise<VercelAgentWorkflowToolBatchStepResult>;
97
+ readonly closeStream: () => Promise<void>;
98
+ readonly writeError: (error: unknown) => Promise<void>;
99
+ readonly awaitInput?: (input: VercelAgentWorkflowAwaitingInput) => Promise<unknown>;
100
+ readonly modelStepRetry?: VercelAgentWorkflowStepRetryPolicy;
101
+ readonly toolBatchStepRetry?: VercelAgentWorkflowStepRetryPolicy;
102
+ readonly awaitInputRetry?: VercelAgentWorkflowStepRetryPolicy;
103
+ readonly closeStreamRetry?: VercelAgentWorkflowStepRetryPolicy;
104
+ };
105
+ declare const defaultMaxWorkflowTurns = 500;
106
+ declare const noWorkflowStepRetry: VercelAgentWorkflowStepRetryPolicy;
107
+ declare const settleWorkflowStep: <A>(promise: Promise<A>) => Promise<WorkflowStepResult<A>>;
108
+ declare function retryWorkflowStep<A>(runStep: () => Promise<A>, policy?: VercelAgentWorkflowStepRetryPolicy): Promise<A>;
109
+ declare function runVercelAgentWorkflow(config: VercelAgentWorkflowLoopConfig): Promise<VercelAgentWorkflowRunResult>;
110
+ //#endregion
111
+ export { SerializableWorkflowState, VercelAgentWorkflowAwaitingInput, VercelAgentWorkflowInput, VercelAgentWorkflowLoopConfig, VercelAgentWorkflowModelStepInput, VercelAgentWorkflowModelStepResult, VercelAgentWorkflowRunResult, VercelAgentWorkflowStepRetryPolicy, VercelAgentWorkflowToolBatchStepInput, VercelAgentWorkflowToolBatchStepResult, WorkflowStepResult, defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep };
112
+ //# sourceMappingURL=workflow-loop.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-loop.d.mts","names":[],"sources":["../src/workflow-loop.ts"],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ //#region src/workflow-loop.ts
2
+ const defaultMaxWorkflowTurns = 500;
3
+ const noWorkflowStepRetry = { maxAttempts: 1 };
4
+ const settleWorkflowStep = (promise) => promise.then((value) => ({
5
+ _tag: "Success",
6
+ value
7
+ }), (error) => ({
8
+ _tag: "Failure",
9
+ error
10
+ }));
11
+ const workflowMaxTurnsError = (maxTurns) => /* @__PURE__ */ new Error(`Vercel agent workflow exceeded max turns: ${maxTurns}`);
12
+ const writeErrorSafely = (writeError, error) => writeError(error).catch(() => void 0);
13
+ const missingAwaitInputHandlerError = () => /* @__PURE__ */ new Error("Vercel agent workflow awaiting input requested but no awaitInput handler is configured");
14
+ const maxRetryAttempts = (policy) => Math.max(1, Math.floor(policy?.maxAttempts ?? noWorkflowStepRetry.maxAttempts));
15
+ async function retryWorkflowStep(runStep, policy) {
16
+ const maxAttempts = maxRetryAttempts(policy);
17
+ let attempt = 1;
18
+ for (;;) try {
19
+ return await runStep();
20
+ } catch (error) {
21
+ if (attempt >= maxAttempts) throw error;
22
+ attempt += 1;
23
+ }
24
+ }
25
+ async function runVercelAgentWorkflow(config) {
26
+ const { input, maxTurns: configuredMaxTurns, runModelStep, runToolBatchStep, closeStream, writeError, awaitInput, modelStepRetry, toolBatchStepRetry, awaitInputRetry, closeStreamRetry } = config;
27
+ let state = {
28
+ request: input.request,
29
+ createdMessages: [],
30
+ turn: 1,
31
+ eventSequence: 0
32
+ };
33
+ const maxTurns = configuredMaxTurns ?? 500;
34
+ for (let step = 0; step < maxTurns; step++) {
35
+ const modelResult = await settleWorkflowStep(retryWorkflowStep(() => runModelStep({
36
+ context: input.context,
37
+ state
38
+ }), modelStepRetry));
39
+ if (modelResult._tag === "Failure") {
40
+ await writeErrorSafely(writeError, modelResult.error);
41
+ return {
42
+ _tag: "ModelStepFailed",
43
+ turn: state.turn,
44
+ error: modelResult.error,
45
+ state
46
+ };
47
+ }
48
+ if (modelResult.value.done) {
49
+ const closeResult = await settleWorkflowStep(retryWorkflowStep(closeStream, closeStreamRetry));
50
+ if (closeResult._tag === "Failure") {
51
+ await writeErrorSafely(writeError, closeResult.error);
52
+ return {
53
+ _tag: "CloseStreamFailed",
54
+ turns: modelResult.value.turn,
55
+ error: closeResult.error,
56
+ state
57
+ };
58
+ }
59
+ return {
60
+ _tag: "Completed",
61
+ turns: modelResult.value.turn,
62
+ state
63
+ };
64
+ }
65
+ let completedToolsResult;
66
+ let toolHitlResponses = [];
67
+ let toolEventSequence = modelResult.value.eventSequence ?? state.eventSequence;
68
+ for (;;) {
69
+ const toolsResult = await settleWorkflowStep(retryWorkflowStep(() => runToolBatchStep({
70
+ context: input.context,
71
+ request: input.request,
72
+ calls: modelResult.value.toolCalls,
73
+ createdMessages: modelResult.value.createdMessages,
74
+ hitlResponses: toolHitlResponses,
75
+ usage: modelResult.value.usage,
76
+ turn: modelResult.value.turn,
77
+ eventSequence: toolEventSequence
78
+ }), toolBatchStepRetry));
79
+ if (toolsResult._tag === "Failure") {
80
+ await writeErrorSafely(writeError, toolsResult.error);
81
+ return {
82
+ _tag: "ToolBatchStepFailed",
83
+ turn: modelResult.value.turn,
84
+ error: toolsResult.error,
85
+ state
86
+ };
87
+ }
88
+ if (toolsResult.value.awaitingInput === void 0) {
89
+ completedToolsResult = toolsResult.value;
90
+ break;
91
+ }
92
+ const awaitingInput = toolsResult.value.awaitingInput;
93
+ toolEventSequence = awaitingInput.eventSequence ?? toolsResult.value.eventSequence ?? toolEventSequence;
94
+ const hitlResponse = await settleWorkflowStep(retryWorkflowStep(() => {
95
+ if (awaitInput === void 0) return Promise.reject(missingAwaitInputHandlerError());
96
+ return awaitInput(awaitingInput);
97
+ }, awaitInputRetry));
98
+ if (hitlResponse._tag === "Failure") {
99
+ await writeErrorSafely(writeError, hitlResponse.error);
100
+ return {
101
+ _tag: "AwaitInputFailed",
102
+ turn: modelResult.value.turn,
103
+ error: hitlResponse.error,
104
+ awaitingInput,
105
+ state
106
+ };
107
+ }
108
+ toolHitlResponses = [...toolHitlResponses, hitlResponse.value];
109
+ }
110
+ if (completedToolsResult === void 0) {
111
+ const error = /* @__PURE__ */ new Error("Vercel agent workflow tool batch did not complete");
112
+ await writeErrorSafely(writeError, error);
113
+ return {
114
+ _tag: "ToolBatchStepFailed",
115
+ turn: modelResult.value.turn,
116
+ error,
117
+ state
118
+ };
119
+ }
120
+ state = {
121
+ request: input.request,
122
+ messages: [...modelResult.value.messages, ...completedToolsResult.messages],
123
+ createdMessages: completedToolsResult.createdMessages,
124
+ usage: modelResult.value.usage,
125
+ turn: modelResult.value.turn + 1,
126
+ eventSequence: completedToolsResult.eventSequence ?? toolEventSequence
127
+ };
128
+ }
129
+ const error = workflowMaxTurnsError(maxTurns);
130
+ await writeErrorSafely(writeError, error);
131
+ return {
132
+ _tag: "MaxTurnsExceeded",
133
+ maxTurns,
134
+ error,
135
+ state
136
+ };
137
+ }
138
+ //#endregion
139
+ export { defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep };
140
+
141
+ //# sourceMappingURL=workflow-loop.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-loop.mjs","names":[],"sources":["../src/workflow-loop.ts"],"sourcesContent":["export type VercelAgentWorkflowInput = {\n readonly request: unknown\n readonly context: unknown\n}\n\nexport type SerializableWorkflowState = {\n readonly request: unknown\n readonly messages?: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly usage?: unknown\n readonly turn: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowModelStepInput = {\n readonly context: unknown\n readonly state: SerializableWorkflowState\n}\n\nexport type VercelAgentWorkflowModelStepResult = {\n readonly done: boolean\n readonly messages: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly toolCalls: ReadonlyArray<unknown>\n readonly usage: unknown\n readonly turn: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowToolBatchStepInput = {\n readonly context: unknown\n readonly request: unknown\n readonly calls: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly hitlResponses?: ReadonlyArray<unknown>\n readonly usage?: unknown\n readonly turn?: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowAwaitingInput = {\n readonly hookToken: string\n readonly requests: ReadonlyArray<unknown>\n readonly messages: ReadonlyArray<unknown>\n readonly usage: unknown\n readonly turns: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowToolBatchStepResult = {\n readonly messages: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly awaitingInput?: VercelAgentWorkflowAwaitingInput\n readonly eventSequence?: number\n}\n\nexport type WorkflowStepResult<A> =\n | {\n readonly _tag: 'Success'\n readonly value: A\n }\n | {\n readonly _tag: 'Failure'\n readonly error: unknown\n }\n\nexport type VercelAgentWorkflowStepRetryPolicy = {\n readonly maxAttempts: number\n}\n\nexport type VercelAgentWorkflowRunResult =\n | {\n readonly _tag: 'Completed'\n readonly turns: number\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'ModelStepFailed'\n readonly turn: number\n readonly error: unknown\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'ToolBatchStepFailed'\n readonly turn: number\n readonly error: unknown\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'AwaitInputFailed'\n readonly turn: number\n readonly error: unknown\n readonly awaitingInput: VercelAgentWorkflowAwaitingInput\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'CloseStreamFailed'\n readonly turns: number\n readonly error: unknown\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'MaxTurnsExceeded'\n readonly maxTurns: number\n readonly error: Error\n readonly state: SerializableWorkflowState\n }\n\nexport type VercelAgentWorkflowLoopConfig = {\n readonly input: VercelAgentWorkflowInput\n readonly maxTurns?: number\n readonly runModelStep: (\n input: VercelAgentWorkflowModelStepInput\n ) => Promise<VercelAgentWorkflowModelStepResult>\n readonly runToolBatchStep: (\n input: VercelAgentWorkflowToolBatchStepInput\n ) => Promise<VercelAgentWorkflowToolBatchStepResult>\n readonly closeStream: () => Promise<void>\n readonly writeError: (error: unknown) => Promise<void>\n readonly awaitInput?: (input: VercelAgentWorkflowAwaitingInput) => Promise<unknown>\n readonly modelStepRetry?: VercelAgentWorkflowStepRetryPolicy\n readonly toolBatchStepRetry?: VercelAgentWorkflowStepRetryPolicy\n readonly awaitInputRetry?: VercelAgentWorkflowStepRetryPolicy\n readonly closeStreamRetry?: VercelAgentWorkflowStepRetryPolicy\n}\n\nexport const defaultMaxWorkflowTurns = 500\nexport const noWorkflowStepRetry: VercelAgentWorkflowStepRetryPolicy = { maxAttempts: 1 }\n\nexport const settleWorkflowStep = <A>(promise: Promise<A>): Promise<WorkflowStepResult<A>> =>\n promise.then(\n value => ({ _tag: 'Success', value }),\n error => ({ _tag: 'Failure', error })\n )\n\nconst workflowMaxTurnsError = (maxTurns: number) =>\n new Error(`Vercel agent workflow exceeded max turns: ${maxTurns}`)\n\nconst writeErrorSafely = (writeError: (error: unknown) => Promise<void>, error: unknown) =>\n writeError(error).catch(() => undefined)\n\nconst missingAwaitInputHandlerError = () =>\n new Error('Vercel agent workflow awaiting input requested but no awaitInput handler is configured')\n\nconst maxRetryAttempts = (policy: VercelAgentWorkflowStepRetryPolicy | undefined) =>\n Math.max(1, Math.floor(policy?.maxAttempts ?? noWorkflowStepRetry.maxAttempts))\n\nexport async function retryWorkflowStep<A>(\n runStep: () => Promise<A>,\n policy?: VercelAgentWorkflowStepRetryPolicy\n): Promise<A> {\n const maxAttempts = maxRetryAttempts(policy)\n let attempt = 1\n\n for (;;) {\n try {\n return await runStep()\n } catch (error) {\n if (attempt >= maxAttempts) {\n throw error\n }\n\n attempt += 1\n }\n }\n}\n\nexport async function runVercelAgentWorkflow(\n config: VercelAgentWorkflowLoopConfig\n): Promise<VercelAgentWorkflowRunResult> {\n const {\n input,\n maxTurns: configuredMaxTurns,\n runModelStep,\n runToolBatchStep,\n closeStream,\n writeError,\n awaitInput,\n modelStepRetry,\n toolBatchStepRetry,\n awaitInputRetry,\n closeStreamRetry\n } = config\n let state: SerializableWorkflowState = {\n request: input.request,\n createdMessages: [],\n turn: 1,\n eventSequence: 0\n }\n const maxTurns = configuredMaxTurns ?? defaultMaxWorkflowTurns\n\n for (let step = 0; step < maxTurns; step++) {\n const modelResult = await settleWorkflowStep(\n retryWorkflowStep(\n () => runModelStep({ context: input.context, state }),\n modelStepRetry\n )\n )\n\n if (modelResult._tag === 'Failure') {\n await writeErrorSafely(writeError, modelResult.error)\n return {\n _tag: 'ModelStepFailed',\n turn: state.turn,\n error: modelResult.error,\n state\n }\n }\n\n if (modelResult.value.done) {\n const closeResult = await settleWorkflowStep(\n retryWorkflowStep(closeStream, closeStreamRetry)\n )\n\n if (closeResult._tag === 'Failure') {\n await writeErrorSafely(writeError, closeResult.error)\n\n return {\n _tag: 'CloseStreamFailed',\n turns: modelResult.value.turn,\n error: closeResult.error,\n state\n }\n }\n\n return {\n _tag: 'Completed',\n turns: modelResult.value.turn,\n state\n }\n }\n\n let completedToolsResult: VercelAgentWorkflowToolBatchStepResult | undefined\n let toolHitlResponses: ReadonlyArray<unknown> = []\n let toolEventSequence = modelResult.value.eventSequence ?? state.eventSequence\n\n for (;;) {\n const toolsResult = await settleWorkflowStep(\n retryWorkflowStep(\n () =>\n runToolBatchStep({\n context: input.context,\n request: input.request,\n calls: modelResult.value.toolCalls,\n createdMessages: modelResult.value.createdMessages,\n hitlResponses: toolHitlResponses,\n usage: modelResult.value.usage,\n turn: modelResult.value.turn,\n eventSequence: toolEventSequence\n }),\n toolBatchStepRetry\n )\n )\n\n if (toolsResult._tag === 'Failure') {\n await writeErrorSafely(writeError, toolsResult.error)\n\n return {\n _tag: 'ToolBatchStepFailed',\n turn: modelResult.value.turn,\n error: toolsResult.error,\n state\n }\n }\n\n if (toolsResult.value.awaitingInput === undefined) {\n completedToolsResult = toolsResult.value\n break\n }\n\n const awaitingInput = toolsResult.value.awaitingInput\n toolEventSequence = awaitingInput.eventSequence ?? toolsResult.value.eventSequence ?? toolEventSequence\n\n const hitlResponse = await settleWorkflowStep(\n retryWorkflowStep(\n () => {\n if (awaitInput === undefined) {\n return Promise.reject(missingAwaitInputHandlerError())\n }\n\n return awaitInput(awaitingInput)\n },\n awaitInputRetry\n )\n )\n\n if (hitlResponse._tag === 'Failure') {\n await writeErrorSafely(writeError, hitlResponse.error)\n\n return {\n _tag: 'AwaitInputFailed',\n turn: modelResult.value.turn,\n error: hitlResponse.error,\n awaitingInput,\n state\n }\n }\n\n toolHitlResponses = [...toolHitlResponses, hitlResponse.value]\n }\n\n if (completedToolsResult === undefined) {\n const error = new Error('Vercel agent workflow tool batch did not complete')\n await writeErrorSafely(writeError, error)\n\n return {\n _tag: 'ToolBatchStepFailed',\n turn: modelResult.value.turn,\n error,\n state\n }\n }\n\n state = {\n request: input.request,\n messages: [...modelResult.value.messages, ...completedToolsResult.messages],\n createdMessages: completedToolsResult.createdMessages,\n usage: modelResult.value.usage,\n turn: modelResult.value.turn + 1,\n eventSequence: completedToolsResult.eventSequence ?? toolEventSequence\n }\n }\n\n const error = workflowMaxTurnsError(maxTurns)\n await writeErrorSafely(writeError, error)\n\n return {\n _tag: 'MaxTurnsExceeded',\n maxTurns,\n error,\n state\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { SerializableWorkflowState, VercelAgentWorkflowAwaitingInput, VercelAgentWorkflowInput, VercelAgentWorkflowLoopConfig, VercelAgentWorkflowModelStepInput, VercelAgentWorkflowModelStepResult, VercelAgentWorkflowRunResult, VercelAgentWorkflowStepRetryPolicy, VercelAgentWorkflowToolBatchStepInput, VercelAgentWorkflowToolBatchStepResult, WorkflowStepResult, defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep } from "./workflow-loop.mjs";
2
+ export { type SerializableWorkflowState, type VercelAgentWorkflowAwaitingInput, type VercelAgentWorkflowInput, type VercelAgentWorkflowLoopConfig, type VercelAgentWorkflowModelStepInput, type VercelAgentWorkflowModelStepResult, type VercelAgentWorkflowRunResult, type VercelAgentWorkflowStepRetryPolicy, type VercelAgentWorkflowToolBatchStepInput, type VercelAgentWorkflowToolBatchStepResult, type WorkflowStepResult, defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep };
@@ -0,0 +1,2 @@
1
+ import { defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep } from "./workflow-loop.mjs";
2
+ export { defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep };
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@yolk-sdk/vercel-workflows",
3
+ "version": "0.1.0-canary.15",
4
+ "description": "Vercel Workflow orchestration primitives for Yolk agent loops.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/magoz/yolk-sdk.git",
11
+ "directory": "packages/vercel-workflows"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/magoz/yolk-sdk/issues"
15
+ },
16
+ "homepage": "https://github.com/magoz/yolk-sdk#readme",
17
+ "keywords": [
18
+ "vercel",
19
+ "workflow",
20
+ "agents",
21
+ "runtime",
22
+ "effect"
23
+ ],
24
+ "engines": {
25
+ "node": ">=22"
26
+ },
27
+ "exports": {
28
+ "./package.json": "./package.json",
29
+ ".": {
30
+ "types": "./dist/index.d.mts",
31
+ "import": "./dist/index.mjs",
32
+ "default": "./dist/index.mjs"
33
+ },
34
+ "./workflow": {
35
+ "types": "./dist/workflow.d.mts",
36
+ "import": "./dist/workflow.mjs",
37
+ "default": "./dist/workflow.mjs"
38
+ }
39
+ },
40
+ "files": [
41
+ "src/**/*.ts",
42
+ "!src/**/*.test.ts",
43
+ "!src/**/*.test.tsx",
44
+ "dist/**/*",
45
+ "README.md"
46
+ ],
47
+ "publishConfig": {
48
+ "access": "public",
49
+ "provenance": true
50
+ },
51
+ "dependencies": {
52
+ "effect": "4.0.0-beta.65",
53
+ "workflow": "^4.2.4"
54
+ },
55
+ "devDependencies": {
56
+ "@effect/vitest": "4.0.0-beta.65",
57
+ "@workflow/vitest": "^4.0.5",
58
+ "vitest": "^4.0.17"
59
+ },
60
+ "scripts": {
61
+ "build": "tsdown",
62
+ "check": "tsc -p tsconfig.json --noEmit",
63
+ "test": "vitest run --passWithNoTests",
64
+ "test:workflow": "vitest run --config vitest.workflow.config.ts",
65
+ "test:run": "vitest run --passWithNoTests"
66
+ }
67
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ export {
2
+ defaultMaxWorkflowTurns,
3
+ noWorkflowStepRetry,
4
+ retryWorkflowStep,
5
+ runVercelAgentWorkflow,
6
+ settleWorkflowStep
7
+ } from './workflow.ts'
8
+ export type {
9
+ SerializableWorkflowState,
10
+ VercelAgentWorkflowAwaitingInput,
11
+ VercelAgentWorkflowInput,
12
+ VercelAgentWorkflowLoopConfig,
13
+ VercelAgentWorkflowModelStepInput,
14
+ VercelAgentWorkflowModelStepResult,
15
+ VercelAgentWorkflowRunResult,
16
+ VercelAgentWorkflowStepRetryPolicy,
17
+ VercelAgentWorkflowToolBatchStepInput,
18
+ VercelAgentWorkflowToolBatchStepResult,
19
+ WorkflowStepResult
20
+ } from './workflow.ts'
@@ -0,0 +1,333 @@
1
+ export type VercelAgentWorkflowInput = {
2
+ readonly request: unknown
3
+ readonly context: unknown
4
+ }
5
+
6
+ export type SerializableWorkflowState = {
7
+ readonly request: unknown
8
+ readonly messages?: ReadonlyArray<unknown>
9
+ readonly createdMessages: ReadonlyArray<unknown>
10
+ readonly usage?: unknown
11
+ readonly turn: number
12
+ readonly eventSequence?: number
13
+ }
14
+
15
+ export type VercelAgentWorkflowModelStepInput = {
16
+ readonly context: unknown
17
+ readonly state: SerializableWorkflowState
18
+ }
19
+
20
+ export type VercelAgentWorkflowModelStepResult = {
21
+ readonly done: boolean
22
+ readonly messages: ReadonlyArray<unknown>
23
+ readonly createdMessages: ReadonlyArray<unknown>
24
+ readonly toolCalls: ReadonlyArray<unknown>
25
+ readonly usage: unknown
26
+ readonly turn: number
27
+ readonly eventSequence?: number
28
+ }
29
+
30
+ export type VercelAgentWorkflowToolBatchStepInput = {
31
+ readonly context: unknown
32
+ readonly request: unknown
33
+ readonly calls: ReadonlyArray<unknown>
34
+ readonly createdMessages: ReadonlyArray<unknown>
35
+ readonly hitlResponses?: ReadonlyArray<unknown>
36
+ readonly usage?: unknown
37
+ readonly turn?: number
38
+ readonly eventSequence?: number
39
+ }
40
+
41
+ export type VercelAgentWorkflowAwaitingInput = {
42
+ readonly hookToken: string
43
+ readonly requests: ReadonlyArray<unknown>
44
+ readonly messages: ReadonlyArray<unknown>
45
+ readonly usage: unknown
46
+ readonly turns: number
47
+ readonly eventSequence?: number
48
+ }
49
+
50
+ export type VercelAgentWorkflowToolBatchStepResult = {
51
+ readonly messages: ReadonlyArray<unknown>
52
+ readonly createdMessages: ReadonlyArray<unknown>
53
+ readonly awaitingInput?: VercelAgentWorkflowAwaitingInput
54
+ readonly eventSequence?: number
55
+ }
56
+
57
+ export type WorkflowStepResult<A> =
58
+ | {
59
+ readonly _tag: 'Success'
60
+ readonly value: A
61
+ }
62
+ | {
63
+ readonly _tag: 'Failure'
64
+ readonly error: unknown
65
+ }
66
+
67
+ export type VercelAgentWorkflowStepRetryPolicy = {
68
+ readonly maxAttempts: number
69
+ }
70
+
71
+ export type VercelAgentWorkflowRunResult =
72
+ | {
73
+ readonly _tag: 'Completed'
74
+ readonly turns: number
75
+ readonly state: SerializableWorkflowState
76
+ }
77
+ | {
78
+ readonly _tag: 'ModelStepFailed'
79
+ readonly turn: number
80
+ readonly error: unknown
81
+ readonly state: SerializableWorkflowState
82
+ }
83
+ | {
84
+ readonly _tag: 'ToolBatchStepFailed'
85
+ readonly turn: number
86
+ readonly error: unknown
87
+ readonly state: SerializableWorkflowState
88
+ }
89
+ | {
90
+ readonly _tag: 'AwaitInputFailed'
91
+ readonly turn: number
92
+ readonly error: unknown
93
+ readonly awaitingInput: VercelAgentWorkflowAwaitingInput
94
+ readonly state: SerializableWorkflowState
95
+ }
96
+ | {
97
+ readonly _tag: 'CloseStreamFailed'
98
+ readonly turns: number
99
+ readonly error: unknown
100
+ readonly state: SerializableWorkflowState
101
+ }
102
+ | {
103
+ readonly _tag: 'MaxTurnsExceeded'
104
+ readonly maxTurns: number
105
+ readonly error: Error
106
+ readonly state: SerializableWorkflowState
107
+ }
108
+
109
+ export type VercelAgentWorkflowLoopConfig = {
110
+ readonly input: VercelAgentWorkflowInput
111
+ readonly maxTurns?: number
112
+ readonly runModelStep: (
113
+ input: VercelAgentWorkflowModelStepInput
114
+ ) => Promise<VercelAgentWorkflowModelStepResult>
115
+ readonly runToolBatchStep: (
116
+ input: VercelAgentWorkflowToolBatchStepInput
117
+ ) => Promise<VercelAgentWorkflowToolBatchStepResult>
118
+ readonly closeStream: () => Promise<void>
119
+ readonly writeError: (error: unknown) => Promise<void>
120
+ readonly awaitInput?: (input: VercelAgentWorkflowAwaitingInput) => Promise<unknown>
121
+ readonly modelStepRetry?: VercelAgentWorkflowStepRetryPolicy
122
+ readonly toolBatchStepRetry?: VercelAgentWorkflowStepRetryPolicy
123
+ readonly awaitInputRetry?: VercelAgentWorkflowStepRetryPolicy
124
+ readonly closeStreamRetry?: VercelAgentWorkflowStepRetryPolicy
125
+ }
126
+
127
+ export const defaultMaxWorkflowTurns = 500
128
+ export const noWorkflowStepRetry: VercelAgentWorkflowStepRetryPolicy = { maxAttempts: 1 }
129
+
130
+ export const settleWorkflowStep = <A>(promise: Promise<A>): Promise<WorkflowStepResult<A>> =>
131
+ promise.then(
132
+ value => ({ _tag: 'Success', value }),
133
+ error => ({ _tag: 'Failure', error })
134
+ )
135
+
136
+ const workflowMaxTurnsError = (maxTurns: number) =>
137
+ new Error(`Vercel agent workflow exceeded max turns: ${maxTurns}`)
138
+
139
+ const writeErrorSafely = (writeError: (error: unknown) => Promise<void>, error: unknown) =>
140
+ writeError(error).catch(() => undefined)
141
+
142
+ const missingAwaitInputHandlerError = () =>
143
+ new Error('Vercel agent workflow awaiting input requested but no awaitInput handler is configured')
144
+
145
+ const maxRetryAttempts = (policy: VercelAgentWorkflowStepRetryPolicy | undefined) =>
146
+ Math.max(1, Math.floor(policy?.maxAttempts ?? noWorkflowStepRetry.maxAttempts))
147
+
148
+ export async function retryWorkflowStep<A>(
149
+ runStep: () => Promise<A>,
150
+ policy?: VercelAgentWorkflowStepRetryPolicy
151
+ ): Promise<A> {
152
+ const maxAttempts = maxRetryAttempts(policy)
153
+ let attempt = 1
154
+
155
+ for (;;) {
156
+ try {
157
+ return await runStep()
158
+ } catch (error) {
159
+ if (attempt >= maxAttempts) {
160
+ throw error
161
+ }
162
+
163
+ attempt += 1
164
+ }
165
+ }
166
+ }
167
+
168
+ export async function runVercelAgentWorkflow(
169
+ config: VercelAgentWorkflowLoopConfig
170
+ ): Promise<VercelAgentWorkflowRunResult> {
171
+ const {
172
+ input,
173
+ maxTurns: configuredMaxTurns,
174
+ runModelStep,
175
+ runToolBatchStep,
176
+ closeStream,
177
+ writeError,
178
+ awaitInput,
179
+ modelStepRetry,
180
+ toolBatchStepRetry,
181
+ awaitInputRetry,
182
+ closeStreamRetry
183
+ } = config
184
+ let state: SerializableWorkflowState = {
185
+ request: input.request,
186
+ createdMessages: [],
187
+ turn: 1,
188
+ eventSequence: 0
189
+ }
190
+ const maxTurns = configuredMaxTurns ?? defaultMaxWorkflowTurns
191
+
192
+ for (let step = 0; step < maxTurns; step++) {
193
+ const modelResult = await settleWorkflowStep(
194
+ retryWorkflowStep(
195
+ () => runModelStep({ context: input.context, state }),
196
+ modelStepRetry
197
+ )
198
+ )
199
+
200
+ if (modelResult._tag === 'Failure') {
201
+ await writeErrorSafely(writeError, modelResult.error)
202
+ return {
203
+ _tag: 'ModelStepFailed',
204
+ turn: state.turn,
205
+ error: modelResult.error,
206
+ state
207
+ }
208
+ }
209
+
210
+ if (modelResult.value.done) {
211
+ const closeResult = await settleWorkflowStep(
212
+ retryWorkflowStep(closeStream, closeStreamRetry)
213
+ )
214
+
215
+ if (closeResult._tag === 'Failure') {
216
+ await writeErrorSafely(writeError, closeResult.error)
217
+
218
+ return {
219
+ _tag: 'CloseStreamFailed',
220
+ turns: modelResult.value.turn,
221
+ error: closeResult.error,
222
+ state
223
+ }
224
+ }
225
+
226
+ return {
227
+ _tag: 'Completed',
228
+ turns: modelResult.value.turn,
229
+ state
230
+ }
231
+ }
232
+
233
+ let completedToolsResult: VercelAgentWorkflowToolBatchStepResult | undefined
234
+ let toolHitlResponses: ReadonlyArray<unknown> = []
235
+ let toolEventSequence = modelResult.value.eventSequence ?? state.eventSequence
236
+
237
+ for (;;) {
238
+ const toolsResult = await settleWorkflowStep(
239
+ retryWorkflowStep(
240
+ () =>
241
+ runToolBatchStep({
242
+ context: input.context,
243
+ request: input.request,
244
+ calls: modelResult.value.toolCalls,
245
+ createdMessages: modelResult.value.createdMessages,
246
+ hitlResponses: toolHitlResponses,
247
+ usage: modelResult.value.usage,
248
+ turn: modelResult.value.turn,
249
+ eventSequence: toolEventSequence
250
+ }),
251
+ toolBatchStepRetry
252
+ )
253
+ )
254
+
255
+ if (toolsResult._tag === 'Failure') {
256
+ await writeErrorSafely(writeError, toolsResult.error)
257
+
258
+ return {
259
+ _tag: 'ToolBatchStepFailed',
260
+ turn: modelResult.value.turn,
261
+ error: toolsResult.error,
262
+ state
263
+ }
264
+ }
265
+
266
+ if (toolsResult.value.awaitingInput === undefined) {
267
+ completedToolsResult = toolsResult.value
268
+ break
269
+ }
270
+
271
+ const awaitingInput = toolsResult.value.awaitingInput
272
+ toolEventSequence = awaitingInput.eventSequence ?? toolsResult.value.eventSequence ?? toolEventSequence
273
+
274
+ const hitlResponse = await settleWorkflowStep(
275
+ retryWorkflowStep(
276
+ () => {
277
+ if (awaitInput === undefined) {
278
+ return Promise.reject(missingAwaitInputHandlerError())
279
+ }
280
+
281
+ return awaitInput(awaitingInput)
282
+ },
283
+ awaitInputRetry
284
+ )
285
+ )
286
+
287
+ if (hitlResponse._tag === 'Failure') {
288
+ await writeErrorSafely(writeError, hitlResponse.error)
289
+
290
+ return {
291
+ _tag: 'AwaitInputFailed',
292
+ turn: modelResult.value.turn,
293
+ error: hitlResponse.error,
294
+ awaitingInput,
295
+ state
296
+ }
297
+ }
298
+
299
+ toolHitlResponses = [...toolHitlResponses, hitlResponse.value]
300
+ }
301
+
302
+ if (completedToolsResult === undefined) {
303
+ const error = new Error('Vercel agent workflow tool batch did not complete')
304
+ await writeErrorSafely(writeError, error)
305
+
306
+ return {
307
+ _tag: 'ToolBatchStepFailed',
308
+ turn: modelResult.value.turn,
309
+ error,
310
+ state
311
+ }
312
+ }
313
+
314
+ state = {
315
+ request: input.request,
316
+ messages: [...modelResult.value.messages, ...completedToolsResult.messages],
317
+ createdMessages: completedToolsResult.createdMessages,
318
+ usage: modelResult.value.usage,
319
+ turn: modelResult.value.turn + 1,
320
+ eventSequence: completedToolsResult.eventSequence ?? toolEventSequence
321
+ }
322
+ }
323
+
324
+ const error = workflowMaxTurnsError(maxTurns)
325
+ await writeErrorSafely(writeError, error)
326
+
327
+ return {
328
+ _tag: 'MaxTurnsExceeded',
329
+ maxTurns,
330
+ error,
331
+ state
332
+ }
333
+ }
@@ -0,0 +1,20 @@
1
+ export {
2
+ defaultMaxWorkflowTurns,
3
+ noWorkflowStepRetry,
4
+ retryWorkflowStep,
5
+ runVercelAgentWorkflow,
6
+ settleWorkflowStep
7
+ } from './workflow-loop.ts'
8
+ export type {
9
+ SerializableWorkflowState,
10
+ VercelAgentWorkflowAwaitingInput,
11
+ VercelAgentWorkflowInput,
12
+ VercelAgentWorkflowLoopConfig,
13
+ VercelAgentWorkflowModelStepInput,
14
+ VercelAgentWorkflowModelStepResult,
15
+ VercelAgentWorkflowRunResult,
16
+ VercelAgentWorkflowStepRetryPolicy,
17
+ VercelAgentWorkflowToolBatchStepInput,
18
+ VercelAgentWorkflowToolBatchStepResult,
19
+ WorkflowStepResult
20
+ } from './workflow-loop.ts'