@ai-sdk/workflow-harness 1.0.107 → 1.0.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ai-sdk/workflow-harness
2
2
 
3
+ ## 1.0.108
4
+
5
+ ### Patch Changes
6
+
7
+ - 633a385: fix(workflow-harness): preserve resumability when harness runs fail
8
+ - d70a334: feat(workflow-harness): automatically persist validated `HarnessAgent` output in workflow results
9
+ - Updated dependencies [4d1bf28]
10
+ - Updated dependencies [cdc12a1]
11
+ - Updated dependencies [d70a334]
12
+ - Updated dependencies [81ba84c]
13
+ - @ai-sdk/harness@1.0.108
14
+
3
15
  ## 1.0.107
4
16
 
5
17
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -32,10 +32,15 @@ interface HarnessWorkflowUsageSummary {
32
32
  readonly inputTokens?: number;
33
33
  readonly outputTokens?: number;
34
34
  }
35
- interface HarnessWorkflowFinalResult {
35
+ interface HarnessWorkflowFinalResult<OUTPUT = unknown> {
36
36
  readonly sessionId: string;
37
37
  readonly finishReason: string;
38
38
  readonly usage?: HarnessWorkflowUsageSummary;
39
+ /**
40
+ * The agent's parsed and schema-validated output when the agent has an output
41
+ * specification.
42
+ */
43
+ readonly output?: OUTPUT;
39
44
  }
40
45
  interface HarnessWorkflowSerializedChunk {
41
46
  readonly type: string;
@@ -63,7 +68,7 @@ interface HarnessWorkflowStreamContext {
63
68
  * - `continueFrom` reattaches to a suspended turn from this same run and
64
69
  * continues it without sending `prompt` again.
65
70
  */
66
- interface HarnessWorkflowState {
71
+ interface HarnessWorkflowState<OUTPUT = unknown> {
67
72
  /**
68
73
  * Stable harness session id; doubles as the sandbox name across processes.
69
74
  * Reuse the chat/conversation id so every user turn resumes the same warm
@@ -96,7 +101,7 @@ interface HarnessWorkflowState {
96
101
  */
97
102
  readonly continueFrom?: HarnessV1ContinueTurnState;
98
103
  readonly streamContext?: HarnessWorkflowStreamContext;
99
- readonly finalResult?: HarnessWorkflowFinalResult;
104
+ readonly finalResult?: HarnessWorkflowFinalResult<OUTPUT>;
100
105
  readonly error?: string;
101
106
  }
102
107
  /**
@@ -116,12 +121,12 @@ interface HarnessWorkflowInput {
116
121
  continueFrom?: HarnessV1ContinueTurnState;
117
122
  }
118
123
  /** Initial state for one user turn (see {@link HarnessWorkflowInput}). */
119
- declare function createHarnessWorkflowState(input: HarnessWorkflowInput): HarnessWorkflowState;
124
+ declare function createHarnessWorkflowState<OUTPUT = unknown>(input: HarnessWorkflowInput): HarnessWorkflowState<OUTPUT>;
120
125
  /**
121
126
  * Collapse a terminal state into its result. Throws if the run failed; returns
122
127
  * the captured `finalResult` when finished, or a best-effort result otherwise.
123
128
  */
124
- declare function finalizeHarnessWorkflow(state: HarnessWorkflowState): HarnessWorkflowFinalResult;
129
+ declare function finalizeHarnessWorkflow<OUTPUT = unknown>(state: HarnessWorkflowState<OUTPUT>): HarnessWorkflowFinalResult<OUTPUT>;
125
130
 
126
131
  /** The non-string arm of {@link HarnessV1Prompt} — a single `UserModelMessage`. */
127
132
  type HarnessV1UserMessage = Exclude<HarnessV1Prompt, string>;
@@ -134,16 +139,23 @@ interface HarnessWorkflowChunk {
134
139
  * The subset of a harness `stream()` / `continueStream()` result the runner uses.
135
140
  * `StreamTextResult` satisfies it structurally.
136
141
  */
137
- interface HarnessWorkflowStreamResult {
142
+ interface HarnessWorkflowStreamResult<OUTPUT = unknown> {
138
143
  toUIMessageStream(): ReadableStream<HarnessWorkflowChunk>;
139
144
  readonly finishReason: PromiseLike<unknown>;
140
145
  readonly totalUsage: PromiseLike<unknown>;
146
+ /**
147
+ * Parsed and schema-validated final output. Optional because the workflow
148
+ * runner also accepts structural agents that do not expose output.
149
+ */
150
+ readonly output?: PromiseLike<OUTPUT>;
141
151
  }
142
152
  /**
143
153
  * The subset of `HarnessAgent` the runner drives. Declared structurally so
144
154
  * the engine is decoupled from the concrete agent generics and easy to mock.
145
155
  */
146
- interface HarnessWorkflowAgent {
156
+ interface HarnessWorkflowAgent<OUTPUT = unknown> {
157
+ /** Whether the agent exposes a parsed output for completed turns. */
158
+ readonly hasOutput?: boolean;
147
159
  createSession(options?: {
148
160
  sessionId?: string;
149
161
  resumeFrom?: HarnessV1ResumeSessionState;
@@ -162,21 +174,21 @@ interface HarnessWorkflowAgent {
162
174
  session: HarnessAgentSession;
163
175
  prompt?: undefined;
164
176
  messages: HarnessWorkflowModelMessage[];
165
- }): Promise<HarnessWorkflowStreamResult>;
177
+ }): Promise<HarnessWorkflowStreamResult<OUTPUT>>;
166
178
  continueStream(options: {
167
179
  session: HarnessAgentSession;
168
- }): Promise<HarnessWorkflowStreamResult>;
180
+ }): Promise<HarnessWorkflowStreamResult<OUTPUT>>;
169
181
  }
170
- interface RunHarnessAgentOptions {
171
- readonly agent: HarnessWorkflowAgent;
182
+ interface RunHarnessAgentOptions<OUTPUT = unknown> {
183
+ readonly agent: HarnessWorkflowAgent<OUTPUT>;
172
184
  readonly state: HarnessWorkflowState;
173
185
  readonly timeSliceSeconds?: number;
174
186
  /**
175
- * When the turn finishes, whether to destroy the sandbox. Defaults to `false`:
176
- * the session is parked or stopped and a fresh resume state is returned in
177
- * `resumeFrom`, so the next user turn reattaches to the same conversation
178
- * (multi-turn chat). Set `true` for a one-shot run that should release the
179
- * sandbox when the turn completes.
187
+ * When the run finishes or fails, whether to destroy the sandbox. Defaults to
188
+ * `false`: the session is parked or stopped and a fresh resume state is
189
+ * returned in `resumeFrom`, so the next user turn reattaches to the same
190
+ * conversation (multi-turn chat). Set `true` for a one-shot run that should
191
+ * release the sandbox when the run ends.
180
192
  */
181
193
  readonly destroyOnFinish?: boolean;
182
194
  /**
@@ -187,7 +199,7 @@ interface RunHarnessAgentOptions {
187
199
  readonly writable?: WritableStream<HarnessWorkflowChunk>;
188
200
  }
189
201
 
190
- type RunHarnessAgentStepOptions = Omit<RunHarnessAgentOptions, 'timeSliceSeconds'>;
202
+ type RunHarnessAgentStepOptions<OUTPUT = unknown> = Omit<RunHarnessAgentOptions<OUTPUT>, 'timeSliceSeconds'>;
191
203
  /**
192
204
  * Run a harness agent until its next semantic step boundary.
193
205
  *
@@ -196,9 +208,9 @@ type RunHarnessAgentStepOptions = Omit<RunHarnessAgentOptions, 'timeSliceSeconds
196
208
  * unfinished, the returned state has status `ready_for_next_step` and carries
197
209
  * the continuation state for the next workflow step.
198
210
  */
199
- declare function runHarnessAgentStep(options: RunHarnessAgentStepOptions): Promise<HarnessWorkflowState>;
211
+ declare function runHarnessAgentStep<OUTPUT = unknown>(options: RunHarnessAgentStepOptions<OUTPUT>): Promise<HarnessWorkflowState<OUTPUT>>;
200
212
 
201
- interface RunHarnessAgentTimeSliceOptions extends Omit<RunHarnessAgentOptions, 'timeSliceSeconds'> {
213
+ interface RunHarnessAgentTimeSliceOptions<OUTPUT = unknown> extends Omit<RunHarnessAgentOptions<OUTPUT>, 'timeSliceSeconds'> {
202
214
  /**
203
215
  * Wall-clock budget for one time slice. Defaults to 750 seconds.
204
216
  */
@@ -210,9 +222,9 @@ interface RunHarnessAgentTimeSliceOptions extends Omit<RunHarnessAgentOptions, '
210
222
  * When the time slice completes before the turn, the returned state has status
211
223
  * `ready_for_next_step` and carries the continuation state for the next slice.
212
224
  */
213
- declare function runHarnessAgentTimeSlice(options: RunHarnessAgentTimeSliceOptions): Promise<HarnessWorkflowState>;
225
+ declare function runHarnessAgentTimeSlice<OUTPUT = unknown>(options: RunHarnessAgentTimeSliceOptions<OUTPUT>): Promise<HarnessWorkflowState<OUTPUT>>;
214
226
 
215
- interface RunHarnessAgentSliceOptions extends Omit<RunHarnessAgentTimeSliceOptions, 'timeSliceSeconds'> {
227
+ interface RunHarnessAgentSliceOptions<OUTPUT = unknown> extends Omit<RunHarnessAgentTimeSliceOptions<OUTPUT>, 'timeSliceSeconds'> {
216
228
  readonly timeSliceSeconds?: number;
217
229
  /**
218
230
  * @deprecated Use `timeSliceSeconds` instead.
@@ -222,6 +234,6 @@ interface RunHarnessAgentSliceOptions extends Omit<RunHarnessAgentTimeSliceOptio
222
234
  /**
223
235
  * @deprecated Use {@link runHarnessAgentTimeSlice} instead.
224
236
  */
225
- declare function runHarnessAgentSlice(options: RunHarnessAgentSliceOptions): Promise<HarnessWorkflowState>;
237
+ declare function runHarnessAgentSlice<OUTPUT = unknown>(options: RunHarnessAgentSliceOptions<OUTPUT>): Promise<HarnessWorkflowState<OUTPUT>>;
226
238
 
227
239
  export { type HarnessWorkflowAgent, type HarnessWorkflowChunk, type HarnessWorkflowFinalResult, type HarnessWorkflowInput, type HarnessWorkflowModelMessage, type HarnessWorkflowState, type HarnessWorkflowStatus, type HarnessWorkflowStreamResult, type HarnessWorkflowUsageSummary, type RunHarnessAgentSliceOptions, type RunHarnessAgentStepOptions, type RunHarnessAgentTimeSliceOptions, createHarnessWorkflowState, finalizeHarnessWorkflow, runHarnessAgentSlice, runHarnessAgentStep, runHarnessAgentTimeSlice };
package/dist/index.js CHANGED
@@ -40,13 +40,15 @@ async function runHarnessAgent(options) {
40
40
  prompt: typeof state.prompt === "string" ? state.prompt : [state.prompt]
41
41
  });
42
42
  } catch (err) {
43
- await destroyQuietly(session);
43
+ const failedSessionState = await endFailedSession({
44
+ session,
45
+ destroyOnFinish
46
+ });
44
47
  return {
45
48
  sessionId: state.sessionId,
46
49
  prompt: state.prompt,
47
50
  status: "failed",
48
- ...state.resumeFrom != null ? { resumeFrom: state.resumeFrom } : {},
49
- ...state.continueFrom != null ? { continueFrom: state.continueFrom } : {},
51
+ ...failedSessionState,
50
52
  error: errorMessage(err)
51
53
  };
52
54
  }
@@ -96,15 +98,17 @@ async function runHarnessAgent(options) {
96
98
  reader.releaseLock();
97
99
  }
98
100
  if (sawError) {
99
- if (suspendPromise != null) await suspendPromise.catch(() => {
101
+ const continueFrom = suspendPromise == null ? void 0 : destroyOnFinish ? await suspendPromise.catch(() => void 0) : await suspendPromise;
102
+ const failedSessionState = await endFailedSession({
103
+ session,
104
+ destroyOnFinish,
105
+ continueFrom
100
106
  });
101
- await destroyQuietly(session);
102
107
  return {
103
108
  sessionId: state.sessionId,
104
109
  prompt: state.prompt,
105
110
  status: "failed",
106
- ...state.resumeFrom != null ? { resumeFrom: state.resumeFrom } : {},
107
- ...state.continueFrom != null ? { continueFrom: state.continueFrom } : {},
111
+ ...failedSessionState,
108
112
  error: "harness turn emitted an error"
109
113
  };
110
114
  }
@@ -163,6 +167,31 @@ async function runHarnessAgent(options) {
163
167
  }
164
168
  };
165
169
  }
170
+ let output;
171
+ const shouldCaptureOutput = agent.hasOutput === true;
172
+ if (shouldCaptureOutput) {
173
+ try {
174
+ const outputPromise = result.output;
175
+ if (outputPromise == null) {
176
+ throw new Error(
177
+ "Harness agent result does not expose structured output."
178
+ );
179
+ }
180
+ output = await Promise.resolve(outputPromise);
181
+ } catch (err) {
182
+ const failedSessionState = await endFailedSession({
183
+ session,
184
+ destroyOnFinish
185
+ });
186
+ return {
187
+ sessionId: state.sessionId,
188
+ prompt: state.prompt,
189
+ status: "failed",
190
+ ...failedSessionState,
191
+ error: errorMessage(err)
192
+ };
193
+ }
194
+ }
166
195
  await writer.write({ type: "finish" });
167
196
  await writer.close();
168
197
  writerClosed = true;
@@ -181,7 +210,8 @@ async function runHarnessAgent(options) {
181
210
  finalResult: {
182
211
  sessionId: state.sessionId,
183
212
  finishReason: normalizedFinishReason,
184
- usage: toUsageSummary(usage)
213
+ usage: toUsageSummary(usage),
214
+ ...shouldCaptureOutput ? { output } : {}
185
215
  }
186
216
  };
187
217
  } finally {
@@ -347,6 +377,19 @@ async function destroyQuietly(session) {
347
377
  await session.destroy().catch(() => {
348
378
  });
349
379
  }
380
+ async function endFailedSession(options) {
381
+ if (options.destroyOnFinish) {
382
+ await destroyQuietly(options.session);
383
+ return {};
384
+ }
385
+ if (options.continueFrom != null) {
386
+ return {
387
+ continueFrom: options.continueFrom,
388
+ resumeFrom: toResumeState({ continueFrom: options.continueFrom })
389
+ };
390
+ }
391
+ return { resumeFrom: await options.session.detach() };
392
+ }
350
393
  function errorMessage(err) {
351
394
  return err instanceof Error ? err.message : String(err);
352
395
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/harness-workflow-state.ts","../src/run-harness-agent.ts","../src/run-harness-agent-step.ts","../src/run-harness-agent-time-slice.ts","../src/run-harness-agent-slice.ts"],"sourcesContent":["import type {\n HarnessV1ContinueTurnState,\n HarnessV1Prompt,\n HarnessV1ResumeSessionState,\n} from '@ai-sdk/harness';\n\nexport type HarnessWorkflowModelMessage =\n | { readonly role: 'system'; readonly content: any }\n | { readonly role: 'user'; readonly content: any }\n | { readonly role: 'assistant'; readonly content: any }\n | { readonly role: 'tool'; readonly content: any };\n\n/**\n * Where a workflow-driven harness run is in its execution loop.\n *\n * - `not_started` — fresh state, no workflow step has run yet.\n * - `ready_for_next_step` — the turn remains unfinished and `continueFrom`\n * carries the cursor for the next workflow step.\n * - `awaiting_tool_approval` — the turn emitted one or more tool approval\n * requests and `continueFrom` carries the suspended turn.\n * - `finished` — the agent turn completed on its own; `finalResult` is set.\n * - `failed` — the turn errored; `error` is set.\n */\nexport type HarnessWorkflowStatus =\n | 'not_started'\n | 'ready_for_next_step'\n | 'awaiting_tool_approval'\n | 'finished'\n | 'failed'\n /** @deprecated Use `ready_for_next_step` instead. */\n | 'timed_out';\n\nexport interface HarnessWorkflowUsageSummary {\n readonly inputTokens?: number;\n readonly outputTokens?: number;\n}\n\nexport interface HarnessWorkflowFinalResult {\n readonly sessionId: string;\n readonly finishReason: string;\n readonly usage?: HarnessWorkflowUsageSummary;\n}\n\nexport interface HarnessWorkflowSerializedChunk {\n readonly type: string;\n readonly [key: string]: unknown;\n}\n\nexport interface HarnessWorkflowStreamContext {\n readonly activeTextParts?: Record<string, HarnessWorkflowSerializedChunk>;\n readonly activeReasoningParts?: Record<\n string,\n HarnessWorkflowSerializedChunk\n >;\n readonly activeToolInputs?: Record<\n string,\n {\n readonly start: HarnessWorkflowSerializedChunk;\n readonly text: string;\n }\n >;\n readonly pendingToolInputs?: Record<string, HarnessWorkflowSerializedChunk>;\n}\n\n/**\n * Serializable state machine threaded between workflow steps. A `'use step'`\n * returns the next value of this object, and the Workflow DevKit persists that\n * return value — so this is the entire durable state of a harness run. Every\n * field must be JSON-serializable.\n *\n * Two independent lifecycle states drive the engine:\n *\n * - `resumeFrom` reattaches to a warm session before starting this run's new\n * user turn.\n * - `continueFrom` reattaches to a suspended turn from this same run and\n * continues it without sending `prompt` again.\n */\nexport interface HarnessWorkflowState {\n /**\n * Stable harness session id; doubles as the sandbox name across processes.\n * Reuse the chat/conversation id so every user turn resumes the same warm\n * session and the agent retains prior-turn context.\n */\n readonly sessionId: string;\n /**\n * The new user turn for this run — a plain string or a single\n * `UserModelMessage` (the harness's own {@link HarnessV1Prompt}), so\n * structured content survives instead of being flattened to text. Sent once,\n * on the execution that starts the turn.\n */\n readonly prompt: HarnessV1Prompt;\n /**\n * Full AI SDK model messages for continuing a suspended approval turn. When\n * present, the next execution sends these to `HarnessAgent.stream()` so\n * approval responses can resume the suspended turn.\n */\n readonly messages?: HarnessWorkflowModelMessage[];\n readonly status: HarnessWorkflowStatus;\n /**\n * Resume coordinates for the next user turn. Absent only on the first turn of\n * a brand-new conversation or when the sandbox was destroyed after finish.\n */\n readonly resumeFrom?: HarnessV1ResumeSessionState;\n /**\n * Continuation coordinates for this run's current suspended turn. When\n * present, the next execution continues the turn rather than sending `prompt`\n * again.\n */\n readonly continueFrom?: HarnessV1ContinueTurnState;\n readonly streamContext?: HarnessWorkflowStreamContext;\n readonly finalResult?: HarnessWorkflowFinalResult;\n readonly error?: string;\n}\n\n/**\n * Input for one user turn — the argument to {@link createHarnessWorkflowState}\n * and the natural shape for a workflow function's input. `sessionId` is required\n * (and must be caller-supplied, since the workflow runtime forbids\n * non-deterministic id generation inside a step) — reuse the conversation id so\n * the sandbox name is stable across turns. Pass `resumeFrom` (the handle\n * persisted after the previous turn) to resume the warm conversation; omit it\n * only for the first turn of a new conversation.\n */\nexport interface HarnessWorkflowInput {\n prompt?: HarnessV1Prompt;\n messages?: HarnessWorkflowModelMessage[];\n sessionId: string;\n resumeFrom?: HarnessV1ResumeSessionState;\n continueFrom?: HarnessV1ContinueTurnState;\n}\n\n/** Initial state for one user turn (see {@link HarnessWorkflowInput}). */\nexport function createHarnessWorkflowState(\n input: HarnessWorkflowInput,\n): HarnessWorkflowState {\n return {\n sessionId: input.sessionId,\n prompt: input.prompt ?? '',\n ...(input.messages != null ? { messages: input.messages } : {}),\n status: 'not_started',\n ...(input.resumeFrom != null ? { resumeFrom: input.resumeFrom } : {}),\n ...(input.continueFrom != null ? { continueFrom: input.continueFrom } : {}),\n };\n}\n\n/**\n * Collapse a terminal state into its result. Throws if the run failed; returns\n * the captured `finalResult` when finished, or a best-effort result otherwise.\n */\nexport function finalizeHarnessWorkflow(\n state: HarnessWorkflowState,\n): HarnessWorkflowFinalResult {\n if (state.status === 'failed') {\n throw new Error(state.error ?? 'harness workflow failed');\n }\n if (state.finalResult) {\n return state.finalResult;\n }\n return { sessionId: state.sessionId, finishReason: 'unknown' };\n}\n","import type {\n HarnessV1ContinueTurnState,\n HarnessV1Prompt,\n HarnessV1ResumeSessionState,\n} from '@ai-sdk/harness';\nimport type { HarnessAgentSession } from '@ai-sdk/harness/agent';\nimport type {\n HarnessWorkflowModelMessage,\n HarnessWorkflowSerializedChunk,\n HarnessWorkflowState,\n HarnessWorkflowStreamContext,\n HarnessWorkflowUsageSummary,\n} from './harness-workflow-state';\n\n/** The non-string arm of {@link HarnessV1Prompt} — a single `UserModelMessage`. */\ntype HarnessV1UserMessage = Exclude<HarnessV1Prompt, string>;\n\n/** A UI-message-stream chunk. Kept structural so this package need not depend on `ai`. */\nexport interface HarnessWorkflowChunk {\n readonly type: string;\n readonly [key: string]: unknown;\n}\n\n/**\n * The subset of a harness `stream()` / `continueStream()` result the runner uses.\n * `StreamTextResult` satisfies it structurally.\n */\nexport interface HarnessWorkflowStreamResult {\n toUIMessageStream(): ReadableStream<HarnessWorkflowChunk>;\n readonly finishReason: PromiseLike<unknown>;\n readonly totalUsage: PromiseLike<unknown>;\n}\n\n/**\n * The subset of `HarnessAgent` the runner drives. Declared structurally so\n * the engine is decoupled from the concrete agent generics and easy to mock.\n */\nexport interface HarnessWorkflowAgent {\n createSession(options?: {\n sessionId?: string;\n resumeFrom?: HarnessV1ResumeSessionState;\n continueFrom?: HarnessV1ContinueTurnState;\n }): Promise<HarnessAgentSession>;\n stream(\n options:\n | {\n session: HarnessAgentSession;\n /**\n * The new user turn. A string or an array of user messages — the shape\n * `HarnessAgent.stream` accepts (it collapses an array to its last user\n * entry). The engine passes the run's single {@link HarnessV1Prompt}.\n */\n prompt: string | HarnessV1UserMessage[];\n messages?: undefined;\n }\n | {\n session: HarnessAgentSession;\n prompt?: undefined;\n messages: HarnessWorkflowModelMessage[];\n },\n ): Promise<HarnessWorkflowStreamResult>;\n continueStream(options: {\n session: HarnessAgentSession;\n }): Promise<HarnessWorkflowStreamResult>;\n}\n\nexport interface RunHarnessAgentOptions {\n readonly agent: HarnessWorkflowAgent;\n readonly state: HarnessWorkflowState;\n readonly timeSliceSeconds?: number;\n /**\n * When the turn finishes, whether to destroy the sandbox. Defaults to `false`:\n * the session is parked or stopped and a fresh resume state is returned in\n * `resumeFrom`, so the next user turn reattaches to the same conversation\n * (multi-turn chat). Set `true` for a one-shot run that should release the\n * sandbox when the turn completes.\n */\n readonly destroyOnFinish?: boolean;\n /**\n * Where to write the turn's UI-message chunks. Defaults to the workflow's\n * output stream (`getWritable()` from `workflow`). Inject a stream in tests\n * to run the engine without a workflow runtime.\n */\n readonly writable?: WritableStream<HarnessWorkflowChunk>;\n}\n\n/**\n * Run one durable execution of a harness agent turn.\n *\n * Intended to be the body of a consumer's `'use step'`: it resumes (or starts)\n * the session and streams the turn's chunks to the workflow output. When\n * `timeSliceSeconds` is present, it also races the turn against that wall-clock\n * budget and suspends the turn when the time slice completes.\n *\n * The returned {@link HarnessWorkflowState} is serializable and is meant to be\n * the step's return value — the Workflow DevKit persists it as the durable\n * checkpoint between workflow steps.\n */\nexport async function runHarnessAgent(\n options: RunHarnessAgentOptions,\n): Promise<HarnessWorkflowState> {\n const { agent, state } = options;\n const destroyOnFinish = options.destroyOnFinish ?? false;\n\n const session =\n state.continueFrom != null\n ? await agent.createSession({\n sessionId: state.sessionId,\n continueFrom: state.continueFrom,\n })\n : state.resumeFrom != null\n ? await agent.createSession({\n sessionId: state.sessionId,\n resumeFrom: state.resumeFrom,\n })\n : await agent.createSession({ sessionId: state.sessionId });\n\n let result: HarnessWorkflowStreamResult;\n try {\n result =\n state.messages != null\n ? await agent.stream({\n session,\n messages: state.messages,\n })\n : state.continueFrom != null\n ? await agent.continueStream({ session })\n : await agent.stream({\n session,\n prompt:\n typeof state.prompt === 'string'\n ? state.prompt\n : [state.prompt],\n });\n } catch (err) {\n await destroyQuietly(session);\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'failed',\n ...(state.resumeFrom != null ? { resumeFrom: state.resumeFrom } : {}),\n ...(state.continueFrom != null\n ? { continueFrom: state.continueFrom }\n : {}),\n error: errorMessage(err),\n };\n }\n\n const writable = options.writable ?? (await resolveWorkflowWritable());\n const writer = writable.getWriter();\n const streamContext = createMutableStreamContext(state.streamContext);\n const executionPartState = createExecutionPartState();\n let skipFirstStartStep =\n state.continueFrom != null &&\n Object.keys(streamContext.activeToolInputs).length > 0;\n\n let suspendPromise: Promise<HarnessV1ContinueTurnState> | undefined;\n const timer =\n options.timeSliceSeconds == null\n ? undefined\n : setTimeout(() => {\n if (session.hasUnfinishedTurn()) {\n suspendPromise = session.suspendTurn();\n }\n }, options.timeSliceSeconds * 1000);\n (timer as { unref?: () => void } | undefined)?.unref?.();\n\n let sawError = false;\n // Tracks whether the writable was closed (only on a finished turn). The\n // `finally` then releases the lock only when we did NOT close, since closing\n // already releases it.\n let writerClosed = false;\n try {\n const reader = result.toUIMessageStream().getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n if (value == null) continue;\n /*\n * One continuous assistant message per user turn: the execution that\n * starts the turn keeps the opening `start`; executions that continue\n * an already suspended turn drop it. Intermediate `finish` chunks are\n * dropped and the loop writes a single terminal `finish` itself when\n * the run completes. A new user turn keeps its `start` so the UI\n * renders it as a fresh assistant message.\n */\n if (value.type === 'start' && state.continueFrom != null) continue;\n if (value.type === 'finish') continue;\n if (value.type === 'start-step' && skipFirstStartStep) {\n skipFirstStartStep = false;\n continue;\n }\n if (value.type === 'error') {\n const errorText = (value as { errorText?: unknown }).errorText;\n /*\n * When a suspend is in flight we tore the turn down at the execution\n * boundary, so an *abort* error is the expected consequence —\n * swallow it (surfacing it would replace the streamed content with\n * an error, and the next execution continues the turn). Any\n * non-abort error is unanticipated and must NOT be silenced: surface\n * it and fail the execution, even mid-suspend.\n */\n if (suspendPromise != null && isAbortError(errorText)) {\n continue;\n }\n sawError = true;\n }\n await writeWorkflowChunk({\n chunk: value,\n writer,\n streamContext,\n executionPartState,\n });\n }\n } finally {\n if (timer != null) clearTimeout(timer);\n reader.releaseLock();\n }\n\n /*\n * A non-abort error (anything `sawError` survived the abort filter for) is\n * a real failure and takes priority over a completed time slice. Abort\n * errors during suspension were already filtered above.\n */\n if (sawError) {\n if (suspendPromise != null) await suspendPromise.catch(() => {});\n await destroyQuietly(session);\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'failed',\n ...(state.resumeFrom != null ? { resumeFrom: state.resumeFrom } : {}),\n ...(state.continueFrom != null\n ? { continueFrom: state.continueFrom }\n : {}),\n error: 'harness turn emitted an error',\n };\n }\n\n /*\n * The time slice completed: the turn keeps running in the sandbox. Persist\n * the cursor so the next time slice attaches to the same in-flight turn.\n */\n if (suspendPromise != null) {\n const continueFrom = await suspendPromise;\n await closeOpenExecutionParts({\n writer,\n streamContext,\n executionPartState,\n });\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'ready_for_next_step',\n continueFrom,\n ...serializeStreamContextField(streamContext),\n };\n }\n\n const [finishReason, usage] = await Promise.all([\n Promise.resolve(result.finishReason).catch(() => undefined),\n Promise.resolve(result.totalUsage).catch(() => undefined),\n ]);\n const normalizedFinishReason = toFinishReasonString(finishReason);\n\n if (session.hasUnfinishedTurn()) {\n const continueFrom = await session.suspendTurn();\n\n if (!hasPendingHostInput(continueFrom)) {\n await closeOpenExecutionParts({\n writer,\n streamContext,\n executionPartState,\n });\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'ready_for_next_step',\n continueFrom,\n ...serializeStreamContextField(streamContext),\n };\n }\n\n await writer.write({\n type: 'finish',\n finishReason: normalizedFinishReason,\n });\n await writer.close();\n writerClosed = true;\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'awaiting_tool_approval',\n continueFrom,\n resumeFrom: toResumeState({ continueFrom }),\n finalResult: {\n sessionId: state.sessionId,\n finishReason: normalizedFinishReason,\n usage: toUsageSummary(usage),\n },\n };\n }\n\n // The turn finished on its own: write the single terminal `finish` for the\n // UI message, then CLOSE the writable. Closing matters: the workflow output\n // stream (`getWritable()`) is what the run's `readable` is fed from, and the\n // DevKit only marks that run stream \"done\" when the writable is closed — a\n // released-but-open writer leaves it open forever, so a consumer piping\n // `run.readable` into a UI-message-stream response never receives the\n // terminal close and the client stays \"streaming\" indefinitely.\n // `ready_for_next_step` and `failed` deliberately do NOT close — another\n // execution keeps writing, or the failure propagates.\n await writer.write({ type: 'finish' });\n await writer.close();\n writerClosed = true;\n\n /*\n * Capture resume state for the *next user turn* before ending this local\n * session handle. `detach()` parks the session without stopping the sandbox;\n * bridge-backed sessions usually resume by attach/replay, while\n * host-resident sessions may resume by rerun. A one-shot consumer opts into\n * `destroyOnFinish` to destroy the sandbox instead.\n */\n let resumeFrom = state.resumeFrom;\n if (destroyOnFinish) {\n await destroyQuietly(session);\n resumeFrom = undefined;\n } else {\n resumeFrom = await session.detach().catch(() => state.resumeFrom);\n }\n\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'finished',\n ...(resumeFrom != null ? { resumeFrom } : {}),\n finalResult: {\n sessionId: state.sessionId,\n finishReason: normalizedFinishReason,\n usage: toUsageSummary(usage),\n },\n };\n } finally {\n if (!writerClosed) writer.releaseLock();\n }\n}\n\ntype MutableStreamContext = {\n activeTextParts: Record<string, HarnessWorkflowSerializedChunk>;\n activeReasoningParts: Record<string, HarnessWorkflowSerializedChunk>;\n activeToolInputs: Record<\n string,\n {\n readonly start: HarnessWorkflowSerializedChunk;\n readonly text: string;\n }\n >;\n pendingToolInputs: Record<string, HarnessWorkflowSerializedChunk>;\n};\n\ntype ExecutionPartState = {\n openedTextParts: Set<string>;\n openedReasoningParts: Set<string>;\n openedToolInputs: Set<string>;\n};\n\nfunction createMutableStreamContext(\n context: HarnessWorkflowStreamContext | undefined,\n): MutableStreamContext {\n return {\n activeTextParts: { ...(context?.activeTextParts ?? {}) },\n activeReasoningParts: { ...(context?.activeReasoningParts ?? {}) },\n activeToolInputs: { ...(context?.activeToolInputs ?? {}) },\n pendingToolInputs: { ...(context?.pendingToolInputs ?? {}) },\n };\n}\n\nfunction createExecutionPartState(): ExecutionPartState {\n return {\n openedTextParts: new Set(),\n openedReasoningParts: new Set(),\n openedToolInputs: new Set(),\n };\n}\n\nasync function writeWorkflowChunk(options: {\n chunk: HarnessWorkflowChunk;\n writer: WritableStreamDefaultWriter<HarnessWorkflowChunk>;\n streamContext: MutableStreamContext;\n executionPartState: ExecutionPartState;\n}): Promise<void> {\n await writeRequiredPrelude(options);\n await options.writer.write(options.chunk);\n recordWorkflowChunk(options);\n}\n\nasync function writeRequiredPrelude(options: {\n chunk: HarnessWorkflowChunk;\n writer: WritableStreamDefaultWriter<HarnessWorkflowChunk>;\n streamContext: MutableStreamContext;\n executionPartState: ExecutionPartState;\n}): Promise<void> {\n const { chunk, writer, streamContext, executionPartState } = options;\n const id = stringProperty({ chunk, key: 'id' });\n const toolCallId = stringProperty({ chunk, key: 'toolCallId' });\n\n if (\n (chunk.type === 'text-delta' || chunk.type === 'text-end') &&\n id != null &&\n streamContext.activeTextParts[id] != null &&\n !executionPartState.openedTextParts.has(id)\n ) {\n await writer.write(streamContext.activeTextParts[id]);\n executionPartState.openedTextParts.add(id);\n }\n\n if (\n (chunk.type === 'reasoning-delta' || chunk.type === 'reasoning-end') &&\n id != null &&\n streamContext.activeReasoningParts[id] != null &&\n !executionPartState.openedReasoningParts.has(id)\n ) {\n await writer.write(streamContext.activeReasoningParts[id]);\n executionPartState.openedReasoningParts.add(id);\n }\n\n const activeToolInput =\n toolCallId == null ? undefined : streamContext.activeToolInputs[toolCallId];\n if (\n chunk.type === 'tool-input-delta' &&\n toolCallId != null &&\n activeToolInput != null &&\n !executionPartState.openedToolInputs.has(toolCallId)\n ) {\n await writer.write(activeToolInput.start);\n if (activeToolInput.text.length > 0) {\n await writer.write({\n type: 'tool-input-delta',\n toolCallId,\n inputTextDelta: activeToolInput.text,\n });\n }\n executionPartState.openedToolInputs.add(toolCallId);\n }\n}\n\nfunction recordWorkflowChunk(options: {\n chunk: HarnessWorkflowChunk;\n streamContext: MutableStreamContext;\n executionPartState: ExecutionPartState;\n}): void {\n const { chunk, streamContext, executionPartState } = options;\n const id = stringProperty({ chunk, key: 'id' });\n const toolCallId = stringProperty({ chunk, key: 'toolCallId' });\n\n if (chunk.type === 'text-start' && id != null) {\n streamContext.activeTextParts[id] = cloneChunk(chunk);\n executionPartState.openedTextParts.add(id);\n return;\n }\n\n if (chunk.type === 'text-end' && id != null) {\n delete streamContext.activeTextParts[id];\n executionPartState.openedTextParts.delete(id);\n return;\n }\n\n if (chunk.type === 'reasoning-start' && id != null) {\n streamContext.activeReasoningParts[id] = cloneChunk(chunk);\n executionPartState.openedReasoningParts.add(id);\n return;\n }\n\n if (chunk.type === 'reasoning-end' && id != null) {\n delete streamContext.activeReasoningParts[id];\n executionPartState.openedReasoningParts.delete(id);\n return;\n }\n\n if (chunk.type === 'tool-input-start' && toolCallId != null) {\n streamContext.activeToolInputs[toolCallId] = {\n start: cloneChunk(chunk),\n text: '',\n };\n executionPartState.openedToolInputs.add(toolCallId);\n return;\n }\n\n if (chunk.type === 'tool-input-delta' && toolCallId != null) {\n const activeToolInput = streamContext.activeToolInputs[toolCallId];\n const inputTextDelta = stringProperty({\n chunk,\n key: 'inputTextDelta',\n });\n if (activeToolInput != null && inputTextDelta != null) {\n streamContext.activeToolInputs[toolCallId] = {\n start: activeToolInput.start,\n text: activeToolInput.text + inputTextDelta,\n };\n }\n return;\n }\n\n if (chunk.type === 'tool-input-available' && toolCallId != null) {\n delete streamContext.activeToolInputs[toolCallId];\n executionPartState.openedToolInputs.delete(toolCallId);\n streamContext.pendingToolInputs[toolCallId] = cloneChunk(chunk);\n return;\n }\n\n if (chunk.type === 'tool-input-error' && toolCallId != null) {\n delete streamContext.activeToolInputs[toolCallId];\n executionPartState.openedToolInputs.delete(toolCallId);\n delete streamContext.pendingToolInputs[toolCallId];\n return;\n }\n\n if (\n (chunk.type === 'tool-output-error' ||\n chunk.type === 'tool-output-denied' ||\n (chunk.type === 'tool-output-available' &&\n (chunk as { preliminary?: unknown }).preliminary !== true)) &&\n toolCallId != null\n ) {\n delete streamContext.pendingToolInputs[toolCallId];\n }\n}\n\nasync function closeOpenExecutionParts(options: {\n writer: WritableStreamDefaultWriter<HarnessWorkflowChunk>;\n streamContext: MutableStreamContext;\n executionPartState: ExecutionPartState;\n}): Promise<void> {\n const { writer, streamContext, executionPartState } = options;\n\n for (const id of executionPartState.openedTextParts) {\n if (streamContext.activeTextParts[id] != null) {\n await writer.write({ type: 'text-end', id });\n }\n }\n executionPartState.openedTextParts.clear();\n\n for (const id of executionPartState.openedReasoningParts) {\n if (streamContext.activeReasoningParts[id] != null) {\n await writer.write({ type: 'reasoning-end', id });\n }\n }\n executionPartState.openedReasoningParts.clear();\n}\n\nfunction serializeStreamContextField(context: MutableStreamContext): {\n streamContext?: HarnessWorkflowStreamContext;\n} {\n const streamContext: HarnessWorkflowStreamContext = {\n ...(Object.keys(context.activeTextParts).length > 0\n ? { activeTextParts: context.activeTextParts }\n : {}),\n ...(Object.keys(context.activeReasoningParts).length > 0\n ? { activeReasoningParts: context.activeReasoningParts }\n : {}),\n ...(Object.keys(context.activeToolInputs).length > 0\n ? { activeToolInputs: context.activeToolInputs }\n : {}),\n ...(Object.keys(context.pendingToolInputs).length > 0\n ? { pendingToolInputs: context.pendingToolInputs }\n : {}),\n };\n return Object.keys(streamContext).length > 0 ? { streamContext } : {};\n}\n\nfunction hasPendingHostInput(state: HarnessV1ContinueTurnState): boolean {\n return (\n (state.pendingToolApprovals?.length ?? 0) > 0 ||\n (state.pendingToolResults?.length ?? 0) > 0\n );\n}\n\nfunction toResumeState(options: {\n continueFrom: HarnessV1ContinueTurnState;\n}): HarnessV1ResumeSessionState {\n const { continueFrom } = options;\n return {\n type: 'resume-session',\n harnessId: continueFrom.harnessId,\n specificationVersion: continueFrom.specificationVersion,\n data: continueFrom.data,\n continueFrom,\n };\n}\n\nfunction stringProperty(options: {\n chunk: HarnessWorkflowChunk;\n key: string;\n}): string | undefined {\n const value = options.chunk[options.key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction cloneChunk(\n chunk: HarnessWorkflowChunk,\n): HarnessWorkflowSerializedChunk {\n return { ...chunk };\n}\n\n/**\n * Resolve the workflow's default output stream lazily, so importing this module\n * never requires the `workflow` runtime (tests inject their own `writable`).\n */\nasync function resolveWorkflowWritable(): Promise<\n WritableStream<HarnessWorkflowChunk>\n> {\n const { getWritable } = (await import('workflow')) as {\n getWritable: <T>() => WritableStream<T>;\n };\n return getWritable<HarnessWorkflowChunk>();\n}\n\nasync function destroyQuietly(session: HarnessAgentSession): Promise<void> {\n await session.destroy().catch(() => {});\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Whether an error (or UI `error` part's `errorText`) is an abort — the\n * expected consequence of `suspendTurn()` tearing the turn down at a slice\n * boundary. Only these are safe to swallow during a suspend; any other error is\n * unanticipated and must surface.\n */\nfunction isAbortError(value: unknown): boolean {\n if (value == null) return false;\n if (\n typeof value === 'object' &&\n (value as { name?: unknown }).name === 'AbortError'\n ) {\n return true;\n }\n const text =\n typeof value === 'string'\n ? value\n : value instanceof Error\n ? value.message\n : String(value);\n return /\\baborted\\b|AbortError|operation was aborted/i.test(text);\n}\n\nfunction toFinishReasonString(finishReason: unknown): string {\n if (typeof finishReason === 'string') return finishReason;\n if (\n finishReason != null &&\n typeof finishReason === 'object' &&\n typeof (finishReason as { unified?: unknown }).unified === 'string'\n ) {\n return (finishReason as { unified: string }).unified;\n }\n return 'stop';\n}\n\nfunction toUsageSummary(\n usage: unknown,\n): HarnessWorkflowUsageSummary | undefined {\n if (usage == null || typeof usage !== 'object') return undefined;\n const u = usage as {\n inputTokens?: number | { total?: number };\n outputTokens?: number | { total?: number };\n };\n const inputTokens =\n typeof u.inputTokens === 'number' ? u.inputTokens : u.inputTokens?.total;\n const outputTokens =\n typeof u.outputTokens === 'number' ? u.outputTokens : u.outputTokens?.total;\n if (inputTokens == null && outputTokens == null) return undefined;\n return {\n ...(inputTokens != null ? { inputTokens } : {}),\n ...(outputTokens != null ? { outputTokens } : {}),\n };\n}\n","import type { HarnessWorkflowState } from './harness-workflow-state';\nimport {\n runHarnessAgent,\n type RunHarnessAgentOptions,\n} from './run-harness-agent';\n\nexport type RunHarnessAgentStepOptions = Omit<\n RunHarnessAgentOptions,\n 'timeSliceSeconds'\n>;\n\n/**\n * Run a harness agent until its next semantic step boundary.\n *\n * Configure the agent with a `stopWhen` condition such as `isStepCount(1)`.\n * When that condition completes a result while the underlying turn remains\n * unfinished, the returned state has status `ready_for_next_step` and carries\n * the continuation state for the next workflow step.\n */\nexport async function runHarnessAgentStep(\n options: RunHarnessAgentStepOptions,\n): Promise<HarnessWorkflowState> {\n return runHarnessAgent(options);\n}\n","import type { HarnessWorkflowState } from './harness-workflow-state';\nimport {\n runHarnessAgent,\n type RunHarnessAgentOptions,\n} from './run-harness-agent';\n\n/*\n * Vercel Fluid Compute recycles a function instance at approximately 800\n * seconds. Completing a time slice at 750 seconds leaves a safety buffer so\n * the next invocation can reattach to the still-running sandbox.\n */\nconst DEFAULT_TIME_SLICE_SECONDS = 750;\n\nexport interface RunHarnessAgentTimeSliceOptions extends Omit<\n RunHarnessAgentOptions,\n 'timeSliceSeconds'\n> {\n /**\n * Wall-clock budget for one time slice. Defaults to 750 seconds.\n */\n readonly timeSliceSeconds?: number;\n}\n\n/**\n * Run one time-boxed slice of a durable harness agent turn.\n *\n * When the time slice completes before the turn, the returned state has status\n * `ready_for_next_step` and carries the continuation state for the next slice.\n */\nexport async function runHarnessAgentTimeSlice(\n options: RunHarnessAgentTimeSliceOptions,\n): Promise<HarnessWorkflowState> {\n return runHarnessAgent({\n ...options,\n timeSliceSeconds: options.timeSliceSeconds ?? DEFAULT_TIME_SLICE_SECONDS,\n });\n}\n","import type { HarnessWorkflowState } from './harness-workflow-state';\nimport {\n runHarnessAgentTimeSlice,\n type RunHarnessAgentTimeSliceOptions,\n} from './run-harness-agent-time-slice';\n\nexport interface RunHarnessAgentSliceOptions extends Omit<\n RunHarnessAgentTimeSliceOptions,\n 'timeSliceSeconds'\n> {\n readonly timeSliceSeconds?: number;\n /**\n * @deprecated Use `timeSliceSeconds` instead.\n */\n readonly sliceTimeoutSeconds?: number;\n}\n\n/**\n * @deprecated Use {@link runHarnessAgentTimeSlice} instead.\n */\nexport async function runHarnessAgentSlice(\n options: RunHarnessAgentSliceOptions,\n): Promise<HarnessWorkflowState> {\n const { sliceTimeoutSeconds, timeSliceSeconds, ...timeSliceOptions } =\n options;\n const state = await runHarnessAgentTimeSlice({\n ...timeSliceOptions,\n timeSliceSeconds: timeSliceSeconds ?? sliceTimeoutSeconds,\n });\n\n return state.status === 'ready_for_next_step'\n ? { ...state, status: 'timed_out' }\n : state;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/harness-workflow-state.ts","../src/run-harness-agent.ts","../src/run-harness-agent-step.ts","../src/run-harness-agent-time-slice.ts","../src/run-harness-agent-slice.ts"],"sourcesContent":["import type {\n HarnessV1ContinueTurnState,\n HarnessV1Prompt,\n HarnessV1ResumeSessionState,\n} from '@ai-sdk/harness';\n\nexport type HarnessWorkflowModelMessage =\n | { readonly role: 'system'; readonly content: any }\n | { readonly role: 'user'; readonly content: any }\n | { readonly role: 'assistant'; readonly content: any }\n | { readonly role: 'tool'; readonly content: any };\n\n/**\n * Where a workflow-driven harness run is in its execution loop.\n *\n * - `not_started` — fresh state, no workflow step has run yet.\n * - `ready_for_next_step` — the turn remains unfinished and `continueFrom`\n * carries the cursor for the next workflow step.\n * - `awaiting_tool_approval` — the turn emitted one or more tool approval\n * requests and `continueFrom` carries the suspended turn.\n * - `finished` — the agent turn completed on its own; `finalResult` is set.\n * - `failed` — the turn errored; `error` is set.\n */\nexport type HarnessWorkflowStatus =\n | 'not_started'\n | 'ready_for_next_step'\n | 'awaiting_tool_approval'\n | 'finished'\n | 'failed'\n /** @deprecated Use `ready_for_next_step` instead. */\n | 'timed_out';\n\nexport interface HarnessWorkflowUsageSummary {\n readonly inputTokens?: number;\n readonly outputTokens?: number;\n}\n\nexport interface HarnessWorkflowFinalResult<OUTPUT = unknown> {\n readonly sessionId: string;\n readonly finishReason: string;\n readonly usage?: HarnessWorkflowUsageSummary;\n /**\n * The agent's parsed and schema-validated output when the agent has an output\n * specification.\n */\n readonly output?: OUTPUT;\n}\n\nexport interface HarnessWorkflowSerializedChunk {\n readonly type: string;\n readonly [key: string]: unknown;\n}\n\nexport interface HarnessWorkflowStreamContext {\n readonly activeTextParts?: Record<string, HarnessWorkflowSerializedChunk>;\n readonly activeReasoningParts?: Record<\n string,\n HarnessWorkflowSerializedChunk\n >;\n readonly activeToolInputs?: Record<\n string,\n {\n readonly start: HarnessWorkflowSerializedChunk;\n readonly text: string;\n }\n >;\n readonly pendingToolInputs?: Record<string, HarnessWorkflowSerializedChunk>;\n}\n\n/**\n * Serializable state machine threaded between workflow steps. A `'use step'`\n * returns the next value of this object, and the Workflow DevKit persists that\n * return value — so this is the entire durable state of a harness run. Every\n * field must be JSON-serializable.\n *\n * Two independent lifecycle states drive the engine:\n *\n * - `resumeFrom` reattaches to a warm session before starting this run's new\n * user turn.\n * - `continueFrom` reattaches to a suspended turn from this same run and\n * continues it without sending `prompt` again.\n */\nexport interface HarnessWorkflowState<OUTPUT = unknown> {\n /**\n * Stable harness session id; doubles as the sandbox name across processes.\n * Reuse the chat/conversation id so every user turn resumes the same warm\n * session and the agent retains prior-turn context.\n */\n readonly sessionId: string;\n /**\n * The new user turn for this run — a plain string or a single\n * `UserModelMessage` (the harness's own {@link HarnessV1Prompt}), so\n * structured content survives instead of being flattened to text. Sent once,\n * on the execution that starts the turn.\n */\n readonly prompt: HarnessV1Prompt;\n /**\n * Full AI SDK model messages for continuing a suspended approval turn. When\n * present, the next execution sends these to `HarnessAgent.stream()` so\n * approval responses can resume the suspended turn.\n */\n readonly messages?: HarnessWorkflowModelMessage[];\n readonly status: HarnessWorkflowStatus;\n /**\n * Resume coordinates for the next user turn. Absent only on the first turn of\n * a brand-new conversation or when the sandbox was destroyed after finish.\n */\n readonly resumeFrom?: HarnessV1ResumeSessionState;\n /**\n * Continuation coordinates for this run's current suspended turn. When\n * present, the next execution continues the turn rather than sending `prompt`\n * again.\n */\n readonly continueFrom?: HarnessV1ContinueTurnState;\n readonly streamContext?: HarnessWorkflowStreamContext;\n readonly finalResult?: HarnessWorkflowFinalResult<OUTPUT>;\n readonly error?: string;\n}\n\n/**\n * Input for one user turn — the argument to {@link createHarnessWorkflowState}\n * and the natural shape for a workflow function's input. `sessionId` is required\n * (and must be caller-supplied, since the workflow runtime forbids\n * non-deterministic id generation inside a step) — reuse the conversation id so\n * the sandbox name is stable across turns. Pass `resumeFrom` (the handle\n * persisted after the previous turn) to resume the warm conversation; omit it\n * only for the first turn of a new conversation.\n */\nexport interface HarnessWorkflowInput {\n prompt?: HarnessV1Prompt;\n messages?: HarnessWorkflowModelMessage[];\n sessionId: string;\n resumeFrom?: HarnessV1ResumeSessionState;\n continueFrom?: HarnessV1ContinueTurnState;\n}\n\n/** Initial state for one user turn (see {@link HarnessWorkflowInput}). */\nexport function createHarnessWorkflowState<OUTPUT = unknown>(\n input: HarnessWorkflowInput,\n): HarnessWorkflowState<OUTPUT> {\n return {\n sessionId: input.sessionId,\n prompt: input.prompt ?? '',\n ...(input.messages != null ? { messages: input.messages } : {}),\n status: 'not_started',\n ...(input.resumeFrom != null ? { resumeFrom: input.resumeFrom } : {}),\n ...(input.continueFrom != null ? { continueFrom: input.continueFrom } : {}),\n };\n}\n\n/**\n * Collapse a terminal state into its result. Throws if the run failed; returns\n * the captured `finalResult` when finished, or a best-effort result otherwise.\n */\nexport function finalizeHarnessWorkflow<OUTPUT = unknown>(\n state: HarnessWorkflowState<OUTPUT>,\n): HarnessWorkflowFinalResult<OUTPUT> {\n if (state.status === 'failed') {\n throw new Error(state.error ?? 'harness workflow failed');\n }\n if (state.finalResult) {\n return state.finalResult;\n }\n return { sessionId: state.sessionId, finishReason: 'unknown' };\n}\n","import type {\n HarnessV1ContinueTurnState,\n HarnessV1Prompt,\n HarnessV1ResumeSessionState,\n} from '@ai-sdk/harness';\nimport type { HarnessAgentSession } from '@ai-sdk/harness/agent';\nimport type {\n HarnessWorkflowModelMessage,\n HarnessWorkflowSerializedChunk,\n HarnessWorkflowState,\n HarnessWorkflowStreamContext,\n HarnessWorkflowUsageSummary,\n} from './harness-workflow-state';\n\n/** The non-string arm of {@link HarnessV1Prompt} — a single `UserModelMessage`. */\ntype HarnessV1UserMessage = Exclude<HarnessV1Prompt, string>;\n\n/** A UI-message-stream chunk. Kept structural so this package need not depend on `ai`. */\nexport interface HarnessWorkflowChunk {\n readonly type: string;\n readonly [key: string]: unknown;\n}\n\n/**\n * The subset of a harness `stream()` / `continueStream()` result the runner uses.\n * `StreamTextResult` satisfies it structurally.\n */\nexport interface HarnessWorkflowStreamResult<OUTPUT = unknown> {\n toUIMessageStream(): ReadableStream<HarnessWorkflowChunk>;\n readonly finishReason: PromiseLike<unknown>;\n readonly totalUsage: PromiseLike<unknown>;\n /**\n * Parsed and schema-validated final output. Optional because the workflow\n * runner also accepts structural agents that do not expose output.\n */\n readonly output?: PromiseLike<OUTPUT>;\n}\n\n/**\n * The subset of `HarnessAgent` the runner drives. Declared structurally so\n * the engine is decoupled from the concrete agent generics and easy to mock.\n */\nexport interface HarnessWorkflowAgent<OUTPUT = unknown> {\n /** Whether the agent exposes a parsed output for completed turns. */\n readonly hasOutput?: boolean;\n createSession(options?: {\n sessionId?: string;\n resumeFrom?: HarnessV1ResumeSessionState;\n continueFrom?: HarnessV1ContinueTurnState;\n }): Promise<HarnessAgentSession>;\n stream(\n options:\n | {\n session: HarnessAgentSession;\n /**\n * The new user turn. A string or an array of user messages — the shape\n * `HarnessAgent.stream` accepts (it collapses an array to its last user\n * entry). The engine passes the run's single {@link HarnessV1Prompt}.\n */\n prompt: string | HarnessV1UserMessage[];\n messages?: undefined;\n }\n | {\n session: HarnessAgentSession;\n prompt?: undefined;\n messages: HarnessWorkflowModelMessage[];\n },\n ): Promise<HarnessWorkflowStreamResult<OUTPUT>>;\n continueStream(options: {\n session: HarnessAgentSession;\n }): Promise<HarnessWorkflowStreamResult<OUTPUT>>;\n}\n\nexport interface RunHarnessAgentOptions<OUTPUT = unknown> {\n readonly agent: HarnessWorkflowAgent<OUTPUT>;\n readonly state: HarnessWorkflowState;\n readonly timeSliceSeconds?: number;\n /**\n * When the run finishes or fails, whether to destroy the sandbox. Defaults to\n * `false`: the session is parked or stopped and a fresh resume state is\n * returned in `resumeFrom`, so the next user turn reattaches to the same\n * conversation (multi-turn chat). Set `true` for a one-shot run that should\n * release the sandbox when the run ends.\n */\n readonly destroyOnFinish?: boolean;\n /**\n * Where to write the turn's UI-message chunks. Defaults to the workflow's\n * output stream (`getWritable()` from `workflow`). Inject a stream in tests\n * to run the engine without a workflow runtime.\n */\n readonly writable?: WritableStream<HarnessWorkflowChunk>;\n}\n\n/**\n * Run one durable execution of a harness agent turn.\n *\n * Intended to be the body of a consumer's `'use step'`: it resumes (or starts)\n * the session and streams the turn's chunks to the workflow output. When\n * `timeSliceSeconds` is present, it also races the turn against that wall-clock\n * budget and suspends the turn when the time slice completes.\n *\n * The returned {@link HarnessWorkflowState} is serializable and is meant to be\n * the step's return value — the Workflow DevKit persists it as the durable\n * checkpoint between workflow steps.\n */\nexport async function runHarnessAgent<OUTPUT = unknown>(\n options: RunHarnessAgentOptions<OUTPUT>,\n): Promise<HarnessWorkflowState<OUTPUT>> {\n const { agent, state } = options;\n const destroyOnFinish = options.destroyOnFinish ?? false;\n\n const session =\n state.continueFrom != null\n ? await agent.createSession({\n sessionId: state.sessionId,\n continueFrom: state.continueFrom,\n })\n : state.resumeFrom != null\n ? await agent.createSession({\n sessionId: state.sessionId,\n resumeFrom: state.resumeFrom,\n })\n : await agent.createSession({ sessionId: state.sessionId });\n\n let result: HarnessWorkflowStreamResult<OUTPUT>;\n try {\n result =\n state.messages != null\n ? await agent.stream({\n session,\n messages: state.messages,\n })\n : state.continueFrom != null\n ? await agent.continueStream({ session })\n : await agent.stream({\n session,\n prompt:\n typeof state.prompt === 'string'\n ? state.prompt\n : [state.prompt],\n });\n } catch (err) {\n const failedSessionState = await endFailedSession({\n session,\n destroyOnFinish,\n });\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'failed',\n ...failedSessionState,\n error: errorMessage(err),\n };\n }\n\n const writable = options.writable ?? (await resolveWorkflowWritable());\n const writer = writable.getWriter();\n const streamContext = createMutableStreamContext(state.streamContext);\n const executionPartState = createExecutionPartState();\n let skipFirstStartStep =\n state.continueFrom != null &&\n Object.keys(streamContext.activeToolInputs).length > 0;\n\n let suspendPromise: Promise<HarnessV1ContinueTurnState> | undefined;\n const timer =\n options.timeSliceSeconds == null\n ? undefined\n : setTimeout(() => {\n if (session.hasUnfinishedTurn()) {\n suspendPromise = session.suspendTurn();\n }\n }, options.timeSliceSeconds * 1000);\n (timer as { unref?: () => void } | undefined)?.unref?.();\n\n let sawError = false;\n // Tracks whether the writable was closed (only on a finished turn). The\n // `finally` then releases the lock only when we did NOT close, since closing\n // already releases it.\n let writerClosed = false;\n try {\n const reader = result.toUIMessageStream().getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n if (value == null) continue;\n /*\n * One continuous assistant message per user turn: the execution that\n * starts the turn keeps the opening `start`; executions that continue\n * an already suspended turn drop it. Intermediate `finish` chunks are\n * dropped and the loop writes a single terminal `finish` itself when\n * the run completes. A new user turn keeps its `start` so the UI\n * renders it as a fresh assistant message.\n */\n if (value.type === 'start' && state.continueFrom != null) continue;\n if (value.type === 'finish') continue;\n if (value.type === 'start-step' && skipFirstStartStep) {\n skipFirstStartStep = false;\n continue;\n }\n if (value.type === 'error') {\n const errorText = (value as { errorText?: unknown }).errorText;\n /*\n * When a suspend is in flight we tore the turn down at the execution\n * boundary, so an *abort* error is the expected consequence —\n * swallow it (surfacing it would replace the streamed content with\n * an error, and the next execution continues the turn). Any\n * non-abort error is unanticipated and must NOT be silenced: surface\n * it and fail the execution, even mid-suspend.\n */\n if (suspendPromise != null && isAbortError(errorText)) {\n continue;\n }\n sawError = true;\n }\n await writeWorkflowChunk({\n chunk: value,\n writer,\n streamContext,\n executionPartState,\n });\n }\n } finally {\n if (timer != null) clearTimeout(timer);\n reader.releaseLock();\n }\n\n /*\n * A non-abort error (anything `sawError` survived the abort filter for) is\n * a real failure and takes priority over a completed time slice. Abort\n * errors during suspension were already filtered above.\n */\n if (sawError) {\n const continueFrom =\n suspendPromise == null\n ? undefined\n : destroyOnFinish\n ? await suspendPromise.catch(() => undefined)\n : await suspendPromise;\n const failedSessionState = await endFailedSession({\n session,\n destroyOnFinish,\n continueFrom,\n });\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'failed',\n ...failedSessionState,\n error: 'harness turn emitted an error',\n };\n }\n\n /*\n * The time slice completed: the turn keeps running in the sandbox. Persist\n * the cursor so the next time slice attaches to the same in-flight turn.\n */\n if (suspendPromise != null) {\n const continueFrom = await suspendPromise;\n await closeOpenExecutionParts({\n writer,\n streamContext,\n executionPartState,\n });\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'ready_for_next_step',\n continueFrom,\n ...serializeStreamContextField(streamContext),\n };\n }\n\n const [finishReason, usage] = await Promise.all([\n Promise.resolve(result.finishReason).catch(() => undefined),\n Promise.resolve(result.totalUsage).catch(() => undefined),\n ]);\n const normalizedFinishReason = toFinishReasonString(finishReason);\n\n if (session.hasUnfinishedTurn()) {\n const continueFrom = await session.suspendTurn();\n\n if (!hasPendingHostInput(continueFrom)) {\n await closeOpenExecutionParts({\n writer,\n streamContext,\n executionPartState,\n });\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'ready_for_next_step',\n continueFrom,\n ...serializeStreamContextField(streamContext),\n };\n }\n\n await writer.write({\n type: 'finish',\n finishReason: normalizedFinishReason,\n });\n await writer.close();\n writerClosed = true;\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'awaiting_tool_approval',\n continueFrom,\n resumeFrom: toResumeState({ continueFrom }),\n finalResult: {\n sessionId: state.sessionId,\n finishReason: normalizedFinishReason,\n usage: toUsageSummary(usage),\n },\n };\n }\n\n let output: OUTPUT | undefined;\n const shouldCaptureOutput = agent.hasOutput === true;\n if (shouldCaptureOutput) {\n try {\n const outputPromise = result.output;\n if (outputPromise == null) {\n throw new Error(\n 'Harness agent result does not expose structured output.',\n );\n }\n output = await Promise.resolve(outputPromise);\n } catch (err) {\n const failedSessionState = await endFailedSession({\n session,\n destroyOnFinish,\n });\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'failed',\n ...failedSessionState,\n error: errorMessage(err),\n };\n }\n }\n\n // The turn finished on its own: write the single terminal `finish` for the\n // UI message, then CLOSE the writable. Closing matters: the workflow output\n // stream (`getWritable()`) is what the run's `readable` is fed from, and the\n // DevKit only marks that run stream \"done\" when the writable is closed — a\n // released-but-open writer leaves it open forever, so a consumer piping\n // `run.readable` into a UI-message-stream response never receives the\n // terminal close and the client stays \"streaming\" indefinitely.\n // `ready_for_next_step` and `failed` deliberately do NOT close — another\n // execution keeps writing, or the failure propagates.\n await writer.write({ type: 'finish' });\n await writer.close();\n writerClosed = true;\n\n /*\n * Capture resume state for the *next user turn* before ending this local\n * session handle. `detach()` parks the session without stopping the sandbox;\n * bridge-backed sessions usually resume by attach/replay, while\n * host-resident sessions may resume by rerun. A one-shot consumer opts into\n * `destroyOnFinish` to destroy the sandbox instead.\n */\n let resumeFrom = state.resumeFrom;\n if (destroyOnFinish) {\n await destroyQuietly(session);\n resumeFrom = undefined;\n } else {\n resumeFrom = await session.detach().catch(() => state.resumeFrom);\n }\n\n return {\n sessionId: state.sessionId,\n prompt: state.prompt,\n status: 'finished',\n ...(resumeFrom != null ? { resumeFrom } : {}),\n finalResult: {\n sessionId: state.sessionId,\n finishReason: normalizedFinishReason,\n usage: toUsageSummary(usage),\n ...(shouldCaptureOutput ? { output } : {}),\n },\n };\n } finally {\n if (!writerClosed) writer.releaseLock();\n }\n}\n\ntype MutableStreamContext = {\n activeTextParts: Record<string, HarnessWorkflowSerializedChunk>;\n activeReasoningParts: Record<string, HarnessWorkflowSerializedChunk>;\n activeToolInputs: Record<\n string,\n {\n readonly start: HarnessWorkflowSerializedChunk;\n readonly text: string;\n }\n >;\n pendingToolInputs: Record<string, HarnessWorkflowSerializedChunk>;\n};\n\ntype ExecutionPartState = {\n openedTextParts: Set<string>;\n openedReasoningParts: Set<string>;\n openedToolInputs: Set<string>;\n};\n\nfunction createMutableStreamContext(\n context: HarnessWorkflowStreamContext | undefined,\n): MutableStreamContext {\n return {\n activeTextParts: { ...(context?.activeTextParts ?? {}) },\n activeReasoningParts: { ...(context?.activeReasoningParts ?? {}) },\n activeToolInputs: { ...(context?.activeToolInputs ?? {}) },\n pendingToolInputs: { ...(context?.pendingToolInputs ?? {}) },\n };\n}\n\nfunction createExecutionPartState(): ExecutionPartState {\n return {\n openedTextParts: new Set(),\n openedReasoningParts: new Set(),\n openedToolInputs: new Set(),\n };\n}\n\nasync function writeWorkflowChunk(options: {\n chunk: HarnessWorkflowChunk;\n writer: WritableStreamDefaultWriter<HarnessWorkflowChunk>;\n streamContext: MutableStreamContext;\n executionPartState: ExecutionPartState;\n}): Promise<void> {\n await writeRequiredPrelude(options);\n await options.writer.write(options.chunk);\n recordWorkflowChunk(options);\n}\n\nasync function writeRequiredPrelude(options: {\n chunk: HarnessWorkflowChunk;\n writer: WritableStreamDefaultWriter<HarnessWorkflowChunk>;\n streamContext: MutableStreamContext;\n executionPartState: ExecutionPartState;\n}): Promise<void> {\n const { chunk, writer, streamContext, executionPartState } = options;\n const id = stringProperty({ chunk, key: 'id' });\n const toolCallId = stringProperty({ chunk, key: 'toolCallId' });\n\n if (\n (chunk.type === 'text-delta' || chunk.type === 'text-end') &&\n id != null &&\n streamContext.activeTextParts[id] != null &&\n !executionPartState.openedTextParts.has(id)\n ) {\n await writer.write(streamContext.activeTextParts[id]);\n executionPartState.openedTextParts.add(id);\n }\n\n if (\n (chunk.type === 'reasoning-delta' || chunk.type === 'reasoning-end') &&\n id != null &&\n streamContext.activeReasoningParts[id] != null &&\n !executionPartState.openedReasoningParts.has(id)\n ) {\n await writer.write(streamContext.activeReasoningParts[id]);\n executionPartState.openedReasoningParts.add(id);\n }\n\n const activeToolInput =\n toolCallId == null ? undefined : streamContext.activeToolInputs[toolCallId];\n if (\n chunk.type === 'tool-input-delta' &&\n toolCallId != null &&\n activeToolInput != null &&\n !executionPartState.openedToolInputs.has(toolCallId)\n ) {\n await writer.write(activeToolInput.start);\n if (activeToolInput.text.length > 0) {\n await writer.write({\n type: 'tool-input-delta',\n toolCallId,\n inputTextDelta: activeToolInput.text,\n });\n }\n executionPartState.openedToolInputs.add(toolCallId);\n }\n}\n\nfunction recordWorkflowChunk(options: {\n chunk: HarnessWorkflowChunk;\n streamContext: MutableStreamContext;\n executionPartState: ExecutionPartState;\n}): void {\n const { chunk, streamContext, executionPartState } = options;\n const id = stringProperty({ chunk, key: 'id' });\n const toolCallId = stringProperty({ chunk, key: 'toolCallId' });\n\n if (chunk.type === 'text-start' && id != null) {\n streamContext.activeTextParts[id] = cloneChunk(chunk);\n executionPartState.openedTextParts.add(id);\n return;\n }\n\n if (chunk.type === 'text-end' && id != null) {\n delete streamContext.activeTextParts[id];\n executionPartState.openedTextParts.delete(id);\n return;\n }\n\n if (chunk.type === 'reasoning-start' && id != null) {\n streamContext.activeReasoningParts[id] = cloneChunk(chunk);\n executionPartState.openedReasoningParts.add(id);\n return;\n }\n\n if (chunk.type === 'reasoning-end' && id != null) {\n delete streamContext.activeReasoningParts[id];\n executionPartState.openedReasoningParts.delete(id);\n return;\n }\n\n if (chunk.type === 'tool-input-start' && toolCallId != null) {\n streamContext.activeToolInputs[toolCallId] = {\n start: cloneChunk(chunk),\n text: '',\n };\n executionPartState.openedToolInputs.add(toolCallId);\n return;\n }\n\n if (chunk.type === 'tool-input-delta' && toolCallId != null) {\n const activeToolInput = streamContext.activeToolInputs[toolCallId];\n const inputTextDelta = stringProperty({\n chunk,\n key: 'inputTextDelta',\n });\n if (activeToolInput != null && inputTextDelta != null) {\n streamContext.activeToolInputs[toolCallId] = {\n start: activeToolInput.start,\n text: activeToolInput.text + inputTextDelta,\n };\n }\n return;\n }\n\n if (chunk.type === 'tool-input-available' && toolCallId != null) {\n delete streamContext.activeToolInputs[toolCallId];\n executionPartState.openedToolInputs.delete(toolCallId);\n streamContext.pendingToolInputs[toolCallId] = cloneChunk(chunk);\n return;\n }\n\n if (chunk.type === 'tool-input-error' && toolCallId != null) {\n delete streamContext.activeToolInputs[toolCallId];\n executionPartState.openedToolInputs.delete(toolCallId);\n delete streamContext.pendingToolInputs[toolCallId];\n return;\n }\n\n if (\n (chunk.type === 'tool-output-error' ||\n chunk.type === 'tool-output-denied' ||\n (chunk.type === 'tool-output-available' &&\n (chunk as { preliminary?: unknown }).preliminary !== true)) &&\n toolCallId != null\n ) {\n delete streamContext.pendingToolInputs[toolCallId];\n }\n}\n\nasync function closeOpenExecutionParts(options: {\n writer: WritableStreamDefaultWriter<HarnessWorkflowChunk>;\n streamContext: MutableStreamContext;\n executionPartState: ExecutionPartState;\n}): Promise<void> {\n const { writer, streamContext, executionPartState } = options;\n\n for (const id of executionPartState.openedTextParts) {\n if (streamContext.activeTextParts[id] != null) {\n await writer.write({ type: 'text-end', id });\n }\n }\n executionPartState.openedTextParts.clear();\n\n for (const id of executionPartState.openedReasoningParts) {\n if (streamContext.activeReasoningParts[id] != null) {\n await writer.write({ type: 'reasoning-end', id });\n }\n }\n executionPartState.openedReasoningParts.clear();\n}\n\nfunction serializeStreamContextField(context: MutableStreamContext): {\n streamContext?: HarnessWorkflowStreamContext;\n} {\n const streamContext: HarnessWorkflowStreamContext = {\n ...(Object.keys(context.activeTextParts).length > 0\n ? { activeTextParts: context.activeTextParts }\n : {}),\n ...(Object.keys(context.activeReasoningParts).length > 0\n ? { activeReasoningParts: context.activeReasoningParts }\n : {}),\n ...(Object.keys(context.activeToolInputs).length > 0\n ? { activeToolInputs: context.activeToolInputs }\n : {}),\n ...(Object.keys(context.pendingToolInputs).length > 0\n ? { pendingToolInputs: context.pendingToolInputs }\n : {}),\n };\n return Object.keys(streamContext).length > 0 ? { streamContext } : {};\n}\n\nfunction hasPendingHostInput(state: HarnessV1ContinueTurnState): boolean {\n return (\n (state.pendingToolApprovals?.length ?? 0) > 0 ||\n (state.pendingToolResults?.length ?? 0) > 0\n );\n}\n\nfunction toResumeState(options: {\n continueFrom: HarnessV1ContinueTurnState;\n}): HarnessV1ResumeSessionState {\n const { continueFrom } = options;\n return {\n type: 'resume-session',\n harnessId: continueFrom.harnessId,\n specificationVersion: continueFrom.specificationVersion,\n data: continueFrom.data,\n continueFrom,\n };\n}\n\nfunction stringProperty(options: {\n chunk: HarnessWorkflowChunk;\n key: string;\n}): string | undefined {\n const value = options.chunk[options.key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction cloneChunk(\n chunk: HarnessWorkflowChunk,\n): HarnessWorkflowSerializedChunk {\n return { ...chunk };\n}\n\n/**\n * Resolve the workflow's default output stream lazily, so importing this module\n * never requires the `workflow` runtime (tests inject their own `writable`).\n */\nasync function resolveWorkflowWritable(): Promise<\n WritableStream<HarnessWorkflowChunk>\n> {\n const { getWritable } = (await import('workflow')) as {\n getWritable: <T>() => WritableStream<T>;\n };\n return getWritable<HarnessWorkflowChunk>();\n}\n\nasync function destroyQuietly(session: HarnessAgentSession): Promise<void> {\n await session.destroy().catch(() => {});\n}\n\nasync function endFailedSession(options: {\n session: HarnessAgentSession;\n destroyOnFinish: boolean;\n continueFrom?: HarnessV1ContinueTurnState;\n}): Promise<{\n resumeFrom?: HarnessV1ResumeSessionState;\n continueFrom?: HarnessV1ContinueTurnState;\n}> {\n if (options.destroyOnFinish) {\n await destroyQuietly(options.session);\n return {};\n }\n\n if (options.continueFrom != null) {\n return {\n continueFrom: options.continueFrom,\n resumeFrom: toResumeState({ continueFrom: options.continueFrom }),\n };\n }\n\n return { resumeFrom: await options.session.detach() };\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Whether an error (or UI `error` part's `errorText`) is an abort — the\n * expected consequence of `suspendTurn()` tearing the turn down at a slice\n * boundary. Only these are safe to swallow during a suspend; any other error is\n * unanticipated and must surface.\n */\nfunction isAbortError(value: unknown): boolean {\n if (value == null) return false;\n if (\n typeof value === 'object' &&\n (value as { name?: unknown }).name === 'AbortError'\n ) {\n return true;\n }\n const text =\n typeof value === 'string'\n ? value\n : value instanceof Error\n ? value.message\n : String(value);\n return /\\baborted\\b|AbortError|operation was aborted/i.test(text);\n}\n\nfunction toFinishReasonString(finishReason: unknown): string {\n if (typeof finishReason === 'string') return finishReason;\n if (\n finishReason != null &&\n typeof finishReason === 'object' &&\n typeof (finishReason as { unified?: unknown }).unified === 'string'\n ) {\n return (finishReason as { unified: string }).unified;\n }\n return 'stop';\n}\n\nfunction toUsageSummary(\n usage: unknown,\n): HarnessWorkflowUsageSummary | undefined {\n if (usage == null || typeof usage !== 'object') return undefined;\n const u = usage as {\n inputTokens?: number | { total?: number };\n outputTokens?: number | { total?: number };\n };\n const inputTokens =\n typeof u.inputTokens === 'number' ? u.inputTokens : u.inputTokens?.total;\n const outputTokens =\n typeof u.outputTokens === 'number' ? u.outputTokens : u.outputTokens?.total;\n if (inputTokens == null && outputTokens == null) return undefined;\n return {\n ...(inputTokens != null ? { inputTokens } : {}),\n ...(outputTokens != null ? { outputTokens } : {}),\n };\n}\n","import type { HarnessWorkflowState } from './harness-workflow-state';\nimport {\n runHarnessAgent,\n type RunHarnessAgentOptions,\n} from './run-harness-agent';\n\nexport type RunHarnessAgentStepOptions<OUTPUT = unknown> = Omit<\n RunHarnessAgentOptions<OUTPUT>,\n 'timeSliceSeconds'\n>;\n\n/**\n * Run a harness agent until its next semantic step boundary.\n *\n * Configure the agent with a `stopWhen` condition such as `isStepCount(1)`.\n * When that condition completes a result while the underlying turn remains\n * unfinished, the returned state has status `ready_for_next_step` and carries\n * the continuation state for the next workflow step.\n */\nexport async function runHarnessAgentStep<OUTPUT = unknown>(\n options: RunHarnessAgentStepOptions<OUTPUT>,\n): Promise<HarnessWorkflowState<OUTPUT>> {\n return runHarnessAgent(options);\n}\n","import type { HarnessWorkflowState } from './harness-workflow-state';\nimport {\n runHarnessAgent,\n type RunHarnessAgentOptions,\n} from './run-harness-agent';\n\n/*\n * Vercel Fluid Compute recycles a function instance at approximately 800\n * seconds. Completing a time slice at 750 seconds leaves a safety buffer so\n * the next invocation can reattach to the still-running sandbox.\n */\nconst DEFAULT_TIME_SLICE_SECONDS = 750;\n\nexport interface RunHarnessAgentTimeSliceOptions<OUTPUT = unknown> extends Omit<\n RunHarnessAgentOptions<OUTPUT>,\n 'timeSliceSeconds'\n> {\n /**\n * Wall-clock budget for one time slice. Defaults to 750 seconds.\n */\n readonly timeSliceSeconds?: number;\n}\n\n/**\n * Run one time-boxed slice of a durable harness agent turn.\n *\n * When the time slice completes before the turn, the returned state has status\n * `ready_for_next_step` and carries the continuation state for the next slice.\n */\nexport async function runHarnessAgentTimeSlice<OUTPUT = unknown>(\n options: RunHarnessAgentTimeSliceOptions<OUTPUT>,\n): Promise<HarnessWorkflowState<OUTPUT>> {\n return runHarnessAgent({\n ...options,\n timeSliceSeconds: options.timeSliceSeconds ?? DEFAULT_TIME_SLICE_SECONDS,\n });\n}\n","import type { HarnessWorkflowState } from './harness-workflow-state';\nimport {\n runHarnessAgentTimeSlice,\n type RunHarnessAgentTimeSliceOptions,\n} from './run-harness-agent-time-slice';\n\nexport interface RunHarnessAgentSliceOptions<OUTPUT = unknown> extends Omit<\n RunHarnessAgentTimeSliceOptions<OUTPUT>,\n 'timeSliceSeconds'\n> {\n readonly timeSliceSeconds?: number;\n /**\n * @deprecated Use `timeSliceSeconds` instead.\n */\n readonly sliceTimeoutSeconds?: number;\n}\n\n/**\n * @deprecated Use {@link runHarnessAgentTimeSlice} instead.\n */\nexport async function runHarnessAgentSlice<OUTPUT = unknown>(\n options: RunHarnessAgentSliceOptions<OUTPUT>,\n): Promise<HarnessWorkflowState<OUTPUT>> {\n const { sliceTimeoutSeconds, timeSliceSeconds, ...timeSliceOptions } =\n options;\n const state = await runHarnessAgentTimeSlice({\n ...timeSliceOptions,\n timeSliceSeconds: timeSliceSeconds ?? sliceTimeoutSeconds,\n });\n\n return state.status === 'ready_for_next_step'\n ? { ...state, status: 'timed_out' }\n : state;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/workflow-harness",
3
- "version": "1.0.107",
3
+ "version": "1.0.108",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -26,7 +26,7 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "@ai-sdk/harness": "1.0.107"
29
+ "@ai-sdk/harness": "1.0.108"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "workflow": "^4.2.1"
@@ -34,7 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@types/node": "22.19.19",
36
36
  "@vercel/ai-tsconfig": "0.0.0",
37
- "ai": "7.0.97",
37
+ "ai": "7.0.98",
38
38
  "tsup": "^8.5.1",
39
39
  "typescript": "5.8.3",
40
40
  "workflow": "4.6.0"
@@ -35,10 +35,15 @@ export interface HarnessWorkflowUsageSummary {
35
35
  readonly outputTokens?: number;
36
36
  }
37
37
 
38
- export interface HarnessWorkflowFinalResult {
38
+ export interface HarnessWorkflowFinalResult<OUTPUT = unknown> {
39
39
  readonly sessionId: string;
40
40
  readonly finishReason: string;
41
41
  readonly usage?: HarnessWorkflowUsageSummary;
42
+ /**
43
+ * The agent's parsed and schema-validated output when the agent has an output
44
+ * specification.
45
+ */
46
+ readonly output?: OUTPUT;
42
47
  }
43
48
 
44
49
  export interface HarnessWorkflowSerializedChunk {
@@ -75,7 +80,7 @@ export interface HarnessWorkflowStreamContext {
75
80
  * - `continueFrom` reattaches to a suspended turn from this same run and
76
81
  * continues it without sending `prompt` again.
77
82
  */
78
- export interface HarnessWorkflowState {
83
+ export interface HarnessWorkflowState<OUTPUT = unknown> {
79
84
  /**
80
85
  * Stable harness session id; doubles as the sandbox name across processes.
81
86
  * Reuse the chat/conversation id so every user turn resumes the same warm
@@ -108,7 +113,7 @@ export interface HarnessWorkflowState {
108
113
  */
109
114
  readonly continueFrom?: HarnessV1ContinueTurnState;
110
115
  readonly streamContext?: HarnessWorkflowStreamContext;
111
- readonly finalResult?: HarnessWorkflowFinalResult;
116
+ readonly finalResult?: HarnessWorkflowFinalResult<OUTPUT>;
112
117
  readonly error?: string;
113
118
  }
114
119
 
@@ -130,9 +135,9 @@ export interface HarnessWorkflowInput {
130
135
  }
131
136
 
132
137
  /** Initial state for one user turn (see {@link HarnessWorkflowInput}). */
133
- export function createHarnessWorkflowState(
138
+ export function createHarnessWorkflowState<OUTPUT = unknown>(
134
139
  input: HarnessWorkflowInput,
135
- ): HarnessWorkflowState {
140
+ ): HarnessWorkflowState<OUTPUT> {
136
141
  return {
137
142
  sessionId: input.sessionId,
138
143
  prompt: input.prompt ?? '',
@@ -147,9 +152,9 @@ export function createHarnessWorkflowState(
147
152
  * Collapse a terminal state into its result. Throws if the run failed; returns
148
153
  * the captured `finalResult` when finished, or a best-effort result otherwise.
149
154
  */
150
- export function finalizeHarnessWorkflow(
151
- state: HarnessWorkflowState,
152
- ): HarnessWorkflowFinalResult {
155
+ export function finalizeHarnessWorkflow<OUTPUT = unknown>(
156
+ state: HarnessWorkflowState<OUTPUT>,
157
+ ): HarnessWorkflowFinalResult<OUTPUT> {
153
158
  if (state.status === 'failed') {
154
159
  throw new Error(state.error ?? 'harness workflow failed');
155
160
  }
@@ -4,8 +4,8 @@ import {
4
4
  type RunHarnessAgentTimeSliceOptions,
5
5
  } from './run-harness-agent-time-slice';
6
6
 
7
- export interface RunHarnessAgentSliceOptions extends Omit<
8
- RunHarnessAgentTimeSliceOptions,
7
+ export interface RunHarnessAgentSliceOptions<OUTPUT = unknown> extends Omit<
8
+ RunHarnessAgentTimeSliceOptions<OUTPUT>,
9
9
  'timeSliceSeconds'
10
10
  > {
11
11
  readonly timeSliceSeconds?: number;
@@ -18,9 +18,9 @@ export interface RunHarnessAgentSliceOptions extends Omit<
18
18
  /**
19
19
  * @deprecated Use {@link runHarnessAgentTimeSlice} instead.
20
20
  */
21
- export async function runHarnessAgentSlice(
22
- options: RunHarnessAgentSliceOptions,
23
- ): Promise<HarnessWorkflowState> {
21
+ export async function runHarnessAgentSlice<OUTPUT = unknown>(
22
+ options: RunHarnessAgentSliceOptions<OUTPUT>,
23
+ ): Promise<HarnessWorkflowState<OUTPUT>> {
24
24
  const { sliceTimeoutSeconds, timeSliceSeconds, ...timeSliceOptions } =
25
25
  options;
26
26
  const state = await runHarnessAgentTimeSlice({
@@ -4,8 +4,8 @@ import {
4
4
  type RunHarnessAgentOptions,
5
5
  } from './run-harness-agent';
6
6
 
7
- export type RunHarnessAgentStepOptions = Omit<
8
- RunHarnessAgentOptions,
7
+ export type RunHarnessAgentStepOptions<OUTPUT = unknown> = Omit<
8
+ RunHarnessAgentOptions<OUTPUT>,
9
9
  'timeSliceSeconds'
10
10
  >;
11
11
 
@@ -17,8 +17,8 @@ export type RunHarnessAgentStepOptions = Omit<
17
17
  * unfinished, the returned state has status `ready_for_next_step` and carries
18
18
  * the continuation state for the next workflow step.
19
19
  */
20
- export async function runHarnessAgentStep(
21
- options: RunHarnessAgentStepOptions,
22
- ): Promise<HarnessWorkflowState> {
20
+ export async function runHarnessAgentStep<OUTPUT = unknown>(
21
+ options: RunHarnessAgentStepOptions<OUTPUT>,
22
+ ): Promise<HarnessWorkflowState<OUTPUT>> {
23
23
  return runHarnessAgent(options);
24
24
  }
@@ -11,8 +11,8 @@ import {
11
11
  */
12
12
  const DEFAULT_TIME_SLICE_SECONDS = 750;
13
13
 
14
- export interface RunHarnessAgentTimeSliceOptions extends Omit<
15
- RunHarnessAgentOptions,
14
+ export interface RunHarnessAgentTimeSliceOptions<OUTPUT = unknown> extends Omit<
15
+ RunHarnessAgentOptions<OUTPUT>,
16
16
  'timeSliceSeconds'
17
17
  > {
18
18
  /**
@@ -27,9 +27,9 @@ export interface RunHarnessAgentTimeSliceOptions extends Omit<
27
27
  * When the time slice completes before the turn, the returned state has status
28
28
  * `ready_for_next_step` and carries the continuation state for the next slice.
29
29
  */
30
- export async function runHarnessAgentTimeSlice(
31
- options: RunHarnessAgentTimeSliceOptions,
32
- ): Promise<HarnessWorkflowState> {
30
+ export async function runHarnessAgentTimeSlice<OUTPUT = unknown>(
31
+ options: RunHarnessAgentTimeSliceOptions<OUTPUT>,
32
+ ): Promise<HarnessWorkflowState<OUTPUT>> {
33
33
  return runHarnessAgent({
34
34
  ...options,
35
35
  timeSliceSeconds: options.timeSliceSeconds ?? DEFAULT_TIME_SLICE_SECONDS,
@@ -25,17 +25,24 @@ export interface HarnessWorkflowChunk {
25
25
  * The subset of a harness `stream()` / `continueStream()` result the runner uses.
26
26
  * `StreamTextResult` satisfies it structurally.
27
27
  */
28
- export interface HarnessWorkflowStreamResult {
28
+ export interface HarnessWorkflowStreamResult<OUTPUT = unknown> {
29
29
  toUIMessageStream(): ReadableStream<HarnessWorkflowChunk>;
30
30
  readonly finishReason: PromiseLike<unknown>;
31
31
  readonly totalUsage: PromiseLike<unknown>;
32
+ /**
33
+ * Parsed and schema-validated final output. Optional because the workflow
34
+ * runner also accepts structural agents that do not expose output.
35
+ */
36
+ readonly output?: PromiseLike<OUTPUT>;
32
37
  }
33
38
 
34
39
  /**
35
40
  * The subset of `HarnessAgent` the runner drives. Declared structurally so
36
41
  * the engine is decoupled from the concrete agent generics and easy to mock.
37
42
  */
38
- export interface HarnessWorkflowAgent {
43
+ export interface HarnessWorkflowAgent<OUTPUT = unknown> {
44
+ /** Whether the agent exposes a parsed output for completed turns. */
45
+ readonly hasOutput?: boolean;
39
46
  createSession(options?: {
40
47
  sessionId?: string;
41
48
  resumeFrom?: HarnessV1ResumeSessionState;
@@ -58,22 +65,22 @@ export interface HarnessWorkflowAgent {
58
65
  prompt?: undefined;
59
66
  messages: HarnessWorkflowModelMessage[];
60
67
  },
61
- ): Promise<HarnessWorkflowStreamResult>;
68
+ ): Promise<HarnessWorkflowStreamResult<OUTPUT>>;
62
69
  continueStream(options: {
63
70
  session: HarnessAgentSession;
64
- }): Promise<HarnessWorkflowStreamResult>;
71
+ }): Promise<HarnessWorkflowStreamResult<OUTPUT>>;
65
72
  }
66
73
 
67
- export interface RunHarnessAgentOptions {
68
- readonly agent: HarnessWorkflowAgent;
74
+ export interface RunHarnessAgentOptions<OUTPUT = unknown> {
75
+ readonly agent: HarnessWorkflowAgent<OUTPUT>;
69
76
  readonly state: HarnessWorkflowState;
70
77
  readonly timeSliceSeconds?: number;
71
78
  /**
72
- * When the turn finishes, whether to destroy the sandbox. Defaults to `false`:
73
- * the session is parked or stopped and a fresh resume state is returned in
74
- * `resumeFrom`, so the next user turn reattaches to the same conversation
75
- * (multi-turn chat). Set `true` for a one-shot run that should release the
76
- * sandbox when the turn completes.
79
+ * When the run finishes or fails, whether to destroy the sandbox. Defaults to
80
+ * `false`: the session is parked or stopped and a fresh resume state is
81
+ * returned in `resumeFrom`, so the next user turn reattaches to the same
82
+ * conversation (multi-turn chat). Set `true` for a one-shot run that should
83
+ * release the sandbox when the run ends.
77
84
  */
78
85
  readonly destroyOnFinish?: boolean;
79
86
  /**
@@ -96,9 +103,9 @@ export interface RunHarnessAgentOptions {
96
103
  * the step's return value — the Workflow DevKit persists it as the durable
97
104
  * checkpoint between workflow steps.
98
105
  */
99
- export async function runHarnessAgent(
100
- options: RunHarnessAgentOptions,
101
- ): Promise<HarnessWorkflowState> {
106
+ export async function runHarnessAgent<OUTPUT = unknown>(
107
+ options: RunHarnessAgentOptions<OUTPUT>,
108
+ ): Promise<HarnessWorkflowState<OUTPUT>> {
102
109
  const { agent, state } = options;
103
110
  const destroyOnFinish = options.destroyOnFinish ?? false;
104
111
 
@@ -115,7 +122,7 @@ export async function runHarnessAgent(
115
122
  })
116
123
  : await agent.createSession({ sessionId: state.sessionId });
117
124
 
118
- let result: HarnessWorkflowStreamResult;
125
+ let result: HarnessWorkflowStreamResult<OUTPUT>;
119
126
  try {
120
127
  result =
121
128
  state.messages != null
@@ -133,15 +140,15 @@ export async function runHarnessAgent(
133
140
  : [state.prompt],
134
141
  });
135
142
  } catch (err) {
136
- await destroyQuietly(session);
143
+ const failedSessionState = await endFailedSession({
144
+ session,
145
+ destroyOnFinish,
146
+ });
137
147
  return {
138
148
  sessionId: state.sessionId,
139
149
  prompt: state.prompt,
140
150
  status: 'failed',
141
- ...(state.resumeFrom != null ? { resumeFrom: state.resumeFrom } : {}),
142
- ...(state.continueFrom != null
143
- ? { continueFrom: state.continueFrom }
144
- : {}),
151
+ ...failedSessionState,
145
152
  error: errorMessage(err),
146
153
  };
147
154
  }
@@ -224,16 +231,22 @@ export async function runHarnessAgent(
224
231
  * errors during suspension were already filtered above.
225
232
  */
226
233
  if (sawError) {
227
- if (suspendPromise != null) await suspendPromise.catch(() => {});
228
- await destroyQuietly(session);
234
+ const continueFrom =
235
+ suspendPromise == null
236
+ ? undefined
237
+ : destroyOnFinish
238
+ ? await suspendPromise.catch(() => undefined)
239
+ : await suspendPromise;
240
+ const failedSessionState = await endFailedSession({
241
+ session,
242
+ destroyOnFinish,
243
+ continueFrom,
244
+ });
229
245
  return {
230
246
  sessionId: state.sessionId,
231
247
  prompt: state.prompt,
232
248
  status: 'failed',
233
- ...(state.resumeFrom != null ? { resumeFrom: state.resumeFrom } : {}),
234
- ...(state.continueFrom != null
235
- ? { continueFrom: state.continueFrom }
236
- : {}),
249
+ ...failedSessionState,
237
250
  error: 'harness turn emitted an error',
238
251
  };
239
252
  }
@@ -302,6 +315,32 @@ export async function runHarnessAgent(
302
315
  };
303
316
  }
304
317
 
318
+ let output: OUTPUT | undefined;
319
+ const shouldCaptureOutput = agent.hasOutput === true;
320
+ if (shouldCaptureOutput) {
321
+ try {
322
+ const outputPromise = result.output;
323
+ if (outputPromise == null) {
324
+ throw new Error(
325
+ 'Harness agent result does not expose structured output.',
326
+ );
327
+ }
328
+ output = await Promise.resolve(outputPromise);
329
+ } catch (err) {
330
+ const failedSessionState = await endFailedSession({
331
+ session,
332
+ destroyOnFinish,
333
+ });
334
+ return {
335
+ sessionId: state.sessionId,
336
+ prompt: state.prompt,
337
+ status: 'failed',
338
+ ...failedSessionState,
339
+ error: errorMessage(err),
340
+ };
341
+ }
342
+ }
343
+
305
344
  // The turn finished on its own: write the single terminal `finish` for the
306
345
  // UI message, then CLOSE the writable. Closing matters: the workflow output
307
346
  // stream (`getWritable()`) is what the run's `readable` is fed from, and the
@@ -339,6 +378,7 @@ export async function runHarnessAgent(
339
378
  sessionId: state.sessionId,
340
379
  finishReason: normalizedFinishReason,
341
380
  usage: toUsageSummary(usage),
381
+ ...(shouldCaptureOutput ? { output } : {}),
342
382
  },
343
383
  };
344
384
  } finally {
@@ -620,6 +660,29 @@ async function destroyQuietly(session: HarnessAgentSession): Promise<void> {
620
660
  await session.destroy().catch(() => {});
621
661
  }
622
662
 
663
+ async function endFailedSession(options: {
664
+ session: HarnessAgentSession;
665
+ destroyOnFinish: boolean;
666
+ continueFrom?: HarnessV1ContinueTurnState;
667
+ }): Promise<{
668
+ resumeFrom?: HarnessV1ResumeSessionState;
669
+ continueFrom?: HarnessV1ContinueTurnState;
670
+ }> {
671
+ if (options.destroyOnFinish) {
672
+ await destroyQuietly(options.session);
673
+ return {};
674
+ }
675
+
676
+ if (options.continueFrom != null) {
677
+ return {
678
+ continueFrom: options.continueFrom,
679
+ resumeFrom: toResumeState({ continueFrom: options.continueFrom }),
680
+ };
681
+ }
682
+
683
+ return { resumeFrom: await options.session.detach() };
684
+ }
685
+
623
686
  function errorMessage(err: unknown): string {
624
687
  return err instanceof Error ? err.message : String(err);
625
688
  }