@yolk-sdk/vercel-workflows 0.1.0-canary.66 → 0.1.0-canary.68

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/README.md CHANGED
@@ -5,7 +5,7 @@ Vercel Workflow agent-loop contracts, durable stream helpers, and an Effect-nati
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- pnpm add @yolk-sdk/vercel-workflows@canary effect workflow
8
+ pnpm add @yolk-sdk/vercel-workflows@canary effect workflow@^5.0.0-beta.42
9
9
  ```
10
10
 
11
11
  Canary APIs are unstable. Keep all `@yolk-sdk/*` packages on the same version.
@@ -59,11 +59,40 @@ const program = Effect.gen(function* () {
59
59
  The API wrapper keeps Vercel's SDK underneath. It does not reimplement Vercel backend HTTP,
60
60
  queues, hooks, or stream storage.
61
61
 
62
+ ## Test Workflow behavior
63
+
64
+ `@yolk-sdk/vercel-workflows/testing` provides `TestWorkflowWorld`, a behavioral emulator for host
65
+ tests. It covers run lifecycle, append-only streams with close-once conflicts, step retries and
66
+ attempt metadata, hooks/resume, cancellation, and the SDK seam used by
67
+ `VercelWorkflows.layerFromSdk`. Ambient module mocks do not apply retry policy by themselves: wrap
68
+ host step callbacks with `world.step(...)` or call `world.runStep(...)` explicitly when the test
69
+ must exercise platform retry attempts and `getStepMetadata().attempt`.
70
+
71
+ Mock the ambient `workflow` module so production orchestration runs against the active test world:
72
+
73
+ ```ts
74
+ import { vi } from 'vitest'
75
+ import { TestWorkflowWorld } from '@yolk-sdk/vercel-workflows/testing'
76
+
77
+ vi.mock('workflow', async () => {
78
+ const { testWorkflowModule } = await import('@yolk-sdk/vercel-workflows/testing')
79
+ return testWorkflowModule
80
+ })
81
+
82
+ const world = new TestWorkflowWorld()
83
+ ```
84
+
85
+ Tests whose import graph reaches `workflow` must use Vitest's Node environment, not jsdom, because
86
+ Workflow 5 captures Node `URL` intrinsics at import time. Hosts should test their own `'use workflow'`
87
+ and `'use step'` directive files with `@workflow/vitest` in addition to using the emulator.
88
+
62
89
  ## Runtime model
63
90
 
64
91
  `runVercelAgentWorkflow` coordinates host-provided callbacks:
65
92
 
66
93
  - model step: produce model events/tool calls
94
+ - no-tool continuation: when a model step returns `done: false` with no tool calls, continue
95
+ directly to the next model step without invoking the tool-batch callback
67
96
  - tool batch step: execute requested tools
68
97
  - tool batch result: return one ordered tool-result message per host call, including failed `isError` results
69
98
  - partial tool-step failure: return wire-safe `failure`, cumulative `usage`, and `eventSequence` when
@@ -222,7 +251,8 @@ Never close on resumable `AgentAwaitingInput`.
222
251
  - Write durable terminal events only after host persistence has settled.
223
252
  - Implement `closeStream` for successful final closure and `writeError` for safe final error write
224
253
  plus failure closure. Release, but do not close, the writer after `AgentAwaitingInput`.
225
- - Test directive behavior with `@workflow/vitest` when changing package-owned Workflow files.
254
+ - Test host-owned Workflow directive files with `@workflow/vitest`; use the `./testing` emulator for
255
+ platform behavior in host tests.
226
256
 
227
257
  ## Boundaries
228
258
 
@@ -77,6 +77,17 @@ async function runVercelAgentWorkflow(config) {
77
77
  state: terminalState
78
78
  };
79
79
  }
80
+ if (modelResult.value.toolCalls.length === 0) {
81
+ state = {
82
+ request: input.request,
83
+ messages: modelResult.value.messages,
84
+ createdMessages: modelResult.value.createdMessages,
85
+ usage: modelResult.value.usage,
86
+ turn: modelResult.value.turn + 1,
87
+ eventSequence: modelResult.value.eventSequence ?? state.eventSequence
88
+ };
89
+ continue;
90
+ }
80
91
  let completedToolsResult;
81
92
  let toolHitlResponses = [];
82
93
  let cumulativeUsage = modelResult.value.usage;
@@ -1 +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 /** Updated cumulative usage when tools run nested model work such as subagents. */\n readonly usage?: unknown\n readonly awaitingInput?: VercelAgentWorkflowAwaitingInput\n readonly eventSequence?: number\n /**\n * A plain wire-safe failure captured after partial progress. Returning it instead of rejecting\n * lets orchestration retain usage and event sequencing from completed sibling tools.\n */\n readonly failure?: unknown\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 /** Called only after a model step returns `done: true`. */\n readonly closeStream: () => Promise<void>\n /**\n * Best-effort final failure handler. It must write a safe final error and\n * close the failure stream; rejection is suppressed in favor of the\n * original structured run result.\n */\n readonly writeError: (error: unknown) => Promise<void>\n /** A successful await suspends/resumes without invoking either finalizer. */\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(\n 'Vercel agent workflow awaiting input requested but no awaitInput handler is configured'\n )\n\nconst maxRetryAttempts = (policy: VercelAgentWorkflowStepRetryPolicy | undefined) => {\n const configured = policy?.maxAttempts ?? noWorkflowStepRetry.maxAttempts\n\n return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 1\n}\n\nconst maxWorkflowTurns = (configured: number | undefined) => {\n if (configured === undefined || !Number.isFinite(configured)) {\n return defaultMaxWorkflowTurns\n }\n\n return Math.max(1, Math.floor(configured))\n}\n\nexport async function retryWorkflowStep<A>(\n runStep: () => Promise<A>,\n policy?: VercelAgentWorkflowStepRetryPolicy\n): Promise<A> {\n const maxAttempts = maxRetryAttempts(policy)\n\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 = maxWorkflowTurns(configuredMaxTurns)\n\n for (let step = 0; step < maxTurns; step++) {\n const modelResult = await settleWorkflowStep(\n retryWorkflowStep(() => runModelStep({ context: input.context, state }), modelStepRetry)\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 terminalState: SerializableWorkflowState = {\n request: input.request,\n messages: modelResult.value.messages,\n createdMessages: modelResult.value.createdMessages,\n usage: modelResult.value.usage,\n turn: modelResult.value.turn,\n eventSequence: modelResult.value.eventSequence ?? state.eventSequence\n }\n const closeResult = await settleWorkflowStep(retryWorkflowStep(closeStream, closeStreamRetry))\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: terminalState\n }\n }\n\n return {\n _tag: 'Completed',\n turns: modelResult.value.turn,\n state: terminalState\n }\n }\n\n let completedToolsResult: VercelAgentWorkflowToolBatchStepResult | undefined\n let toolHitlResponses: ReadonlyArray<unknown> = []\n let cumulativeUsage = modelResult.value.usage\n let toolEventSequence = modelResult.value.eventSequence ?? state.eventSequence\n const currentTurnState = (\n toolResult?: VercelAgentWorkflowToolBatchStepResult\n ): SerializableWorkflowState => ({\n request: input.request,\n messages:\n toolResult === undefined\n ? modelResult.value.messages\n : [...modelResult.value.messages, ...toolResult.messages],\n createdMessages: toolResult?.createdMessages ?? modelResult.value.createdMessages,\n usage: cumulativeUsage,\n turn: modelResult.value.turn,\n eventSequence: toolEventSequence\n })\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: cumulativeUsage,\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: currentTurnState()\n }\n }\n\n cumulativeUsage = toolsResult.value.usage ?? cumulativeUsage\n toolEventSequence = toolsResult.value.eventSequence ?? toolEventSequence\n\n if (toolsResult.value.failure !== undefined) {\n await writeErrorSafely(writeError, toolsResult.value.failure)\n\n return {\n _tag: 'ToolBatchStepFailed',\n turn: modelResult.value.turn,\n error: toolsResult.value.failure,\n state: currentTurnState(toolsResult.value)\n }\n }\n\n if (toolsResult.value.awaitingInput === undefined) {\n completedToolsResult = toolsResult.value\n break\n }\n\n const awaitingInput = toolsResult.value.awaitingInput\n cumulativeUsage = toolsResult.value.usage ?? awaitingInput.usage ?? cumulativeUsage\n toolEventSequence = awaitingInput.eventSequence ?? toolEventSequence\n\n const hitlResponse = await settleWorkflowStep(\n retryWorkflowStep(() => {\n if (awaitInput === undefined) {\n return Promise.reject(missingAwaitInputHandlerError())\n }\n\n return awaitInput(awaitingInput)\n }, awaitInputRetry)\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: currentTurnState(toolsResult.value)\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: completedToolsResult.usage ?? cumulativeUsage,\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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"}
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 /** Updated cumulative usage when tools run nested model work such as subagents. */\n readonly usage?: unknown\n readonly awaitingInput?: VercelAgentWorkflowAwaitingInput\n readonly eventSequence?: number\n /**\n * A plain wire-safe failure captured after partial progress. Returning it instead of rejecting\n * lets orchestration retain usage and event sequencing from completed sibling tools.\n */\n readonly failure?: unknown\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 /** Called only after a model step returns `done: true`. */\n readonly closeStream: () => Promise<void>\n /**\n * Best-effort final failure handler. It must write a safe final error and\n * close the failure stream; rejection is suppressed in favor of the\n * original structured run result.\n */\n readonly writeError: (error: unknown) => Promise<void>\n /** A successful await suspends/resumes without invoking either finalizer. */\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(\n 'Vercel agent workflow awaiting input requested but no awaitInput handler is configured'\n )\n\nconst maxRetryAttempts = (policy: VercelAgentWorkflowStepRetryPolicy | undefined) => {\n const configured = policy?.maxAttempts ?? noWorkflowStepRetry.maxAttempts\n\n return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 1\n}\n\nconst maxWorkflowTurns = (configured: number | undefined) => {\n if (configured === undefined || !Number.isFinite(configured)) {\n return defaultMaxWorkflowTurns\n }\n\n return Math.max(1, Math.floor(configured))\n}\n\nexport async function retryWorkflowStep<A>(\n runStep: () => Promise<A>,\n policy?: VercelAgentWorkflowStepRetryPolicy\n): Promise<A> {\n const maxAttempts = maxRetryAttempts(policy)\n\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 = maxWorkflowTurns(configuredMaxTurns)\n\n for (let step = 0; step < maxTurns; step++) {\n const modelResult = await settleWorkflowStep(\n retryWorkflowStep(() => runModelStep({ context: input.context, state }), modelStepRetry)\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 terminalState: SerializableWorkflowState = {\n request: input.request,\n messages: modelResult.value.messages,\n createdMessages: modelResult.value.createdMessages,\n usage: modelResult.value.usage,\n turn: modelResult.value.turn,\n eventSequence: modelResult.value.eventSequence ?? state.eventSequence\n }\n const closeResult = await settleWorkflowStep(retryWorkflowStep(closeStream, closeStreamRetry))\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: terminalState\n }\n }\n\n return {\n _tag: 'Completed',\n turns: modelResult.value.turn,\n state: terminalState\n }\n }\n\n if (modelResult.value.toolCalls.length === 0) {\n state = {\n request: input.request,\n messages: modelResult.value.messages,\n createdMessages: modelResult.value.createdMessages,\n usage: modelResult.value.usage,\n turn: modelResult.value.turn + 1,\n eventSequence: modelResult.value.eventSequence ?? state.eventSequence\n }\n continue\n }\n\n let completedToolsResult: VercelAgentWorkflowToolBatchStepResult | undefined\n let toolHitlResponses: ReadonlyArray<unknown> = []\n let cumulativeUsage = modelResult.value.usage\n let toolEventSequence = modelResult.value.eventSequence ?? state.eventSequence\n const currentTurnState = (\n toolResult?: VercelAgentWorkflowToolBatchStepResult\n ): SerializableWorkflowState => ({\n request: input.request,\n messages:\n toolResult === undefined\n ? modelResult.value.messages\n : [...modelResult.value.messages, ...toolResult.messages],\n createdMessages: toolResult?.createdMessages ?? modelResult.value.createdMessages,\n usage: cumulativeUsage,\n turn: modelResult.value.turn,\n eventSequence: toolEventSequence\n })\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: cumulativeUsage,\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: currentTurnState()\n }\n }\n\n cumulativeUsage = toolsResult.value.usage ?? cumulativeUsage\n toolEventSequence = toolsResult.value.eventSequence ?? toolEventSequence\n\n if (toolsResult.value.failure !== undefined) {\n await writeErrorSafely(writeError, toolsResult.value.failure)\n\n return {\n _tag: 'ToolBatchStepFailed',\n turn: modelResult.value.turn,\n error: toolsResult.value.failure,\n state: currentTurnState(toolsResult.value)\n }\n }\n\n if (toolsResult.value.awaitingInput === undefined) {\n completedToolsResult = toolsResult.value\n break\n }\n\n const awaitingInput = toolsResult.value.awaitingInput\n cumulativeUsage = toolsResult.value.usage ?? awaitingInput.usage ?? cumulativeUsage\n toolEventSequence = awaitingInput.eventSequence ?? toolEventSequence\n\n const hitlResponse = await settleWorkflowStep(\n retryWorkflowStep(() => {\n if (awaitInput === undefined) {\n return Promise.reject(missingAwaitInputHandlerError())\n }\n\n return awaitInput(awaitingInput)\n }, awaitInputRetry)\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: currentTurnState(toolsResult.value)\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: completedToolsResult.usage ?? cumulativeUsage,\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yolk-sdk/vercel-workflows",
3
- "version": "0.1.0-canary.66",
3
+ "version": "0.1.0-canary.68",
4
4
  "description": "Vercel Workflow orchestration primitives for Yolk agent loops.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -261,6 +261,18 @@ export async function runVercelAgentWorkflow(
261
261
  }
262
262
  }
263
263
 
264
+ if (modelResult.value.toolCalls.length === 0) {
265
+ state = {
266
+ request: input.request,
267
+ messages: modelResult.value.messages,
268
+ createdMessages: modelResult.value.createdMessages,
269
+ usage: modelResult.value.usage,
270
+ turn: modelResult.value.turn + 1,
271
+ eventSequence: modelResult.value.eventSequence ?? state.eventSequence
272
+ }
273
+ continue
274
+ }
275
+
264
276
  let completedToolsResult: VercelAgentWorkflowToolBatchStepResult | undefined
265
277
  let toolHitlResponses: ReadonlyArray<unknown> = []
266
278
  let cumulativeUsage = modelResult.value.usage