@ai-sdk/workflow-harness 1.0.44 → 1.0.45

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,12 @@
1
1
  # @ai-sdk/workflow-harness
2
2
 
3
+ ## 1.0.45
4
+
5
+ ### Patch Changes
6
+
7
+ - 214ea9f: feat(workflow-harness): add utility functions for agent-step based workflow step definitions
8
+ - @ai-sdk/harness@1.0.45
9
+
3
10
  ## 1.0.44
4
11
 
5
12
  ### Patch Changes
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # @ai-sdk/workflow-harness
2
2
 
3
3
  Run an AI SDK `HarnessAgent` (Claude Code, Codex, Pi) as a **durable workflow**
4
- using the [Workflow DevKit](https://www.npmjs.com/package/workflow).
4
+ using the [Workflow DevKit](https://www.npmjs.com/package/workflow). A turn can
5
+ be divided into time slices or semantic agent steps.
5
6
 
6
- A long agent turn is sliced into short, time-boxed steps so it survives a Fluid
7
- Compute function recycle (~800s). Between slices the agent is frozen
8
- non-destructively the sandbox keeps running and the next slice reattaches to
9
- the in-flight turn (`attach`) and a serializable state object is persisted as
10
- the durable step return value.
7
+ Time slices let a long agent turn survive a Fluid Compute function recycle
8
+ (~800s). Semantic steps let a workflow persist after each agent step, typically
9
+ by configuring the agent with `stopWhen: isStepCount(1)`. At either boundary the
10
+ agent is frozen non-destructively and a serializable state object is persisted
11
+ as the durable step return value.
11
12
 
12
13
  This package ships plain helpers + a serializable state machine; you own the
13
14
  thin `'use workflow'` / `'use step'` wrappers (the Workflow DevKit compiles
@@ -32,21 +33,21 @@ export const agent = new HarnessAgent({
32
33
  });
33
34
  ```
34
35
 
35
- `run-slice-step.ts`:
36
+ `time-slice-step.ts`:
36
37
 
37
38
  ```ts
38
39
  import {
39
- runHarnessAgentSlice,
40
+ runHarnessAgentTimeSlice,
40
41
  type HarnessWorkflowState,
41
42
  } from '@ai-sdk/workflow-harness';
42
43
 
43
- export async function runSlice(
44
+ export async function timeSliceStep(
44
45
  state: HarnessWorkflowState,
45
46
  ): Promise<HarnessWorkflowState> {
46
47
  'use step';
47
48
 
48
49
  const { agent } = await import('./agent');
49
- return runHarnessAgentSlice({ agent, state });
50
+ return runHarnessAgentTimeSlice({ agent, state });
50
51
  }
51
52
  ```
52
53
 
@@ -58,18 +59,78 @@ import {
58
59
  finalizeHarnessWorkflow,
59
60
  type HarnessWorkflowInput,
60
61
  } from '@ai-sdk/workflow-harness';
61
- import { runSlice } from './run-slice-step';
62
+ import { timeSliceStep } from './time-slice-step';
62
63
 
63
- export async function codingWorkflow(input: {
64
+ export async function timeSliceWorkflow(input: {
64
65
  prompt: HarnessWorkflowInput['prompt'];
65
66
  sessionId: string;
66
67
  }) {
67
68
  'use workflow';
68
69
 
69
70
  let state = createHarnessWorkflowState(input);
70
- while (state.status === 'running' || state.status === 'timed_out') {
71
- state = await runSlice(state);
72
- }
71
+ do {
72
+ state = await timeSliceStep(state);
73
+ } while (state.status === 'ready_for_next_step');
74
+ return finalizeHarnessWorkflow(state);
75
+ }
76
+ ```
77
+
78
+ For a semantic stepped workflow, configure the agent with
79
+ `stopWhen: isStepCount(1)`, call `runHarnessAgentStep()` from the step module,
80
+ and continue while the status is `ready_for_next_step`:
81
+
82
+ `stepped-agent.ts`:
83
+
84
+ ```ts
85
+ import { HarnessAgent } from '@ai-sdk/harness/agent';
86
+ import { claudeCode } from '@ai-sdk/harness-claude-code';
87
+ import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
88
+ import { isStepCount } from 'ai';
89
+
90
+ export const steppedAgent = new HarnessAgent({
91
+ harness: claudeCode,
92
+ sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000] }),
93
+ stopWhen: isStepCount(1),
94
+ });
95
+ ```
96
+
97
+ `stepped-agent-step.ts`:
98
+
99
+ ```ts
100
+ import {
101
+ runHarnessAgentStep,
102
+ type HarnessWorkflowState,
103
+ } from '@ai-sdk/workflow-harness';
104
+
105
+ export async function agentStep(
106
+ state: HarnessWorkflowState,
107
+ ): Promise<HarnessWorkflowState> {
108
+ 'use step';
109
+
110
+ const { steppedAgent } = await import('./stepped-agent');
111
+ return runHarnessAgentStep({ agent: steppedAgent, state });
112
+ }
113
+ ```
114
+
115
+ `stepped-workflow.ts`:
116
+
117
+ ```ts
118
+ import {
119
+ createHarnessWorkflowState,
120
+ finalizeHarnessWorkflow,
121
+ type HarnessWorkflowInput,
122
+ } from '@ai-sdk/workflow-harness';
123
+ import { agentStep } from './stepped-agent-step';
124
+
125
+ export async function agentWorkflow(
126
+ input: Pick<HarnessWorkflowInput, 'messages' | 'sessionId'>,
127
+ ) {
128
+ 'use workflow';
129
+
130
+ let state = createHarnessWorkflowState(input);
131
+ do {
132
+ state = await agentStep(state);
133
+ } while (state.status === 'ready_for_next_step');
73
134
  return finalizeHarnessWorkflow(state);
74
135
  }
75
136
  ```
@@ -78,14 +139,14 @@ export async function codingWorkflow(input: {
78
139
 
79
140
  ```ts
80
141
  import { start } from 'workflow/api';
81
- import { codingWorkflow } from './workflow';
142
+ import { timeSliceWorkflow } from './workflow';
82
143
 
83
144
  export async function POST(request: Request) {
84
145
  const body = (await request.json()) as {
85
146
  prompt: string;
86
147
  sessionId: string;
87
148
  };
88
- const run = await start(codingWorkflow, [body]);
149
+ const run = await start(timeSliceWorkflow, [body]);
89
150
 
90
151
  return new Response(run.readable);
91
152
  }
package/dist/index.d.ts CHANGED
@@ -15,17 +15,19 @@ type HarnessWorkflowModelMessage = {
15
15
  readonly content: any;
16
16
  };
17
17
  /**
18
- * Where a workflow-driven harness run is in its slice loop.
18
+ * Where a workflow-driven harness run is in its execution loop.
19
19
  *
20
- * - `running` — fresh state, no slice has run yet.
21
- * - `timed_out` — a slice hit its wall-clock budget; `continueFrom` carries
22
- * the cursor to continue the same turn.
20
+ * - `not_started` — fresh state, no workflow step has run yet.
21
+ * - `ready_for_next_step` — the turn remains unfinished and `continueFrom`
22
+ * carries the cursor for the next workflow step.
23
23
  * - `awaiting_tool_approval` — the turn emitted one or more tool approval
24
24
  * requests and `continueFrom` carries the suspended turn.
25
- * - `finished` — the agent turn completed on its own; `finalResult` is set.
26
- * - `failed` — the turn errored; `error` is set.
25
+ * - `finished` — the agent turn completed on its own; `finalResult` is set.
26
+ * - `failed` — the turn errored; `error` is set.
27
27
  */
28
- type HarnessWorkflowStatus = 'running' | 'timed_out' | 'awaiting_tool_approval' | 'finished' | 'failed';
28
+ type HarnessWorkflowStatus = 'not_started' | 'ready_for_next_step' | 'awaiting_tool_approval' | 'finished' | 'failed'
29
+ /** @deprecated Use `ready_for_next_step` instead. */
30
+ | 'timed_out';
29
31
  interface HarnessWorkflowUsageSummary {
30
32
  readonly inputTokens?: number;
31
33
  readonly outputTokens?: number;
@@ -45,7 +47,7 @@ interface HarnessWorkflowStreamContext {
45
47
  readonly pendingToolInputs?: Record<string, HarnessWorkflowSerializedChunk>;
46
48
  }
47
49
  /**
48
- * Serializable state machine threaded between workflow slices. A `'use step'`
50
+ * Serializable state machine threaded between workflow steps. A `'use step'`
49
51
  * returns the next value of this object, and the Workflow DevKit persists that
50
52
  * return value — so this is the entire durable state of a harness run. Every
51
53
  * field must be JSON-serializable.
@@ -68,13 +70,13 @@ interface HarnessWorkflowState {
68
70
  * The new user turn for this run — a plain string or a single
69
71
  * `UserModelMessage` (the harness's own {@link HarnessV1Prompt}), so
70
72
  * structured content survives instead of being flattened to text. Sent once,
71
- * on the slice that starts the turn.
73
+ * on the execution that starts the turn.
72
74
  */
73
75
  readonly prompt: HarnessV1Prompt;
74
76
  /**
75
77
  * Full AI SDK model messages for continuing a suspended approval turn. When
76
- * present, the next slice sends these to `HarnessAgent.stream()` so approval
77
- * responses can resume the suspended turn.
78
+ * present, the next execution sends these to `HarnessAgent.stream()` so
79
+ * approval responses can resume the suspended turn.
78
80
  */
79
81
  readonly messages?: HarnessWorkflowModelMessage[];
80
82
  readonly status: HarnessWorkflowStatus;
@@ -85,8 +87,8 @@ interface HarnessWorkflowState {
85
87
  readonly resumeFrom?: HarnessV1ResumeSessionState;
86
88
  /**
87
89
  * Continuation coordinates for this run's current suspended turn. When
88
- * present, the next slice must call `continueTurn` rather than sending
89
- * `prompt` again.
90
+ * present, the next execution continues the turn rather than sending `prompt`
91
+ * again.
90
92
  */
91
93
  readonly continueFrom?: HarnessV1ContinueTurnState;
92
94
  readonly streamContext?: HarnessWorkflowStreamContext;
@@ -125,7 +127,7 @@ interface HarnessWorkflowChunk {
125
127
  readonly [key: string]: unknown;
126
128
  }
127
129
  /**
128
- * The subset of a harness `stream()` / `continueStream()` result the slice loop uses.
130
+ * The subset of a harness `stream()` / `continueStream()` result the runner uses.
129
131
  * `StreamTextResult` satisfies it structurally.
130
132
  */
131
133
  interface HarnessWorkflowStreamResult {
@@ -134,7 +136,7 @@ interface HarnessWorkflowStreamResult {
134
136
  readonly totalUsage: PromiseLike<unknown>;
135
137
  }
136
138
  /**
137
- * The subset of `HarnessAgent` the slice loop drives. Declared structurally so
139
+ * The subset of `HarnessAgent` the runner drives. Declared structurally so
138
140
  * the engine is decoupled from the concrete agent generics and easy to mock.
139
141
  */
140
142
  interface HarnessWorkflowAgent {
@@ -161,11 +163,10 @@ interface HarnessWorkflowAgent {
161
163
  session: HarnessAgentSession;
162
164
  }): Promise<HarnessWorkflowStreamResult>;
163
165
  }
164
- interface RunHarnessAgentSliceOptions {
166
+ interface RunHarnessAgentOptions {
165
167
  readonly agent: HarnessWorkflowAgent;
166
168
  readonly state: HarnessWorkflowState;
167
- /** Wall-clock budget for this slice. Defaults to {@link DEFAULT_SLICE_TIMEOUT_SECONDS}. */
168
- readonly sliceTimeoutSeconds?: number;
169
+ readonly timeSliceSeconds?: number;
169
170
  /**
170
171
  * When the turn finishes, whether to destroy the sandbox. Defaults to `false`:
171
172
  * the session is parked or stopped and a fresh resume state is returned in
@@ -181,20 +182,42 @@ interface RunHarnessAgentSliceOptions {
181
182
  */
182
183
  readonly writable?: WritableStream<HarnessWorkflowChunk>;
183
184
  }
185
+
186
+ type RunHarnessAgentStepOptions = Omit<RunHarnessAgentOptions, 'timeSliceSeconds'>;
184
187
  /**
185
- * Run one durable slice of a harness agent turn.
188
+ * Run a harness agent until its next semantic step boundary.
186
189
  *
187
- * Intended to be the body of a consumer's `'use step'`: it resumes (or starts)
188
- * the session, streams the turn's chunks to the workflow output, and races the
189
- * turn against a wall-clock budget. If the budget fires first it freezes the
190
- * turn with `session.suspendTurn()` (the sandbox keeps running) and returns a
191
- * `timed_out` state carrying continuation state for the next slice; if the
192
- * turn finishes first it returns a `finished` state with the result.
190
+ * Configure the agent with a `stopWhen` condition such as `isStepCount(1)`.
191
+ * When that condition completes a result while the underlying turn remains
192
+ * unfinished, the returned state has status `ready_for_next_step` and carries
193
+ * the continuation state for the next workflow step.
194
+ */
195
+ declare function runHarnessAgentStep(options: RunHarnessAgentStepOptions): Promise<HarnessWorkflowState>;
196
+
197
+ interface RunHarnessAgentTimeSliceOptions extends Omit<RunHarnessAgentOptions, 'timeSliceSeconds'> {
198
+ /**
199
+ * Wall-clock budget for one time slice. Defaults to 750 seconds.
200
+ */
201
+ readonly timeSliceSeconds?: number;
202
+ }
203
+ /**
204
+ * Run one time-boxed slice of a durable harness agent turn.
193
205
  *
194
- * The returned {@link HarnessWorkflowState} is serializable and is meant to be
195
- * the step's return value — the Workflow DevKit persists it as the durable
196
- * checkpoint between slices.
206
+ * When the time slice completes before the turn, the returned state has status
207
+ * `ready_for_next_step` and carries the continuation state for the next slice.
208
+ */
209
+ declare function runHarnessAgentTimeSlice(options: RunHarnessAgentTimeSliceOptions): Promise<HarnessWorkflowState>;
210
+
211
+ interface RunHarnessAgentSliceOptions extends Omit<RunHarnessAgentTimeSliceOptions, 'timeSliceSeconds'> {
212
+ readonly timeSliceSeconds?: number;
213
+ /**
214
+ * @deprecated Use `timeSliceSeconds` instead.
215
+ */
216
+ readonly sliceTimeoutSeconds?: number;
217
+ }
218
+ /**
219
+ * @deprecated Use {@link runHarnessAgentTimeSlice} instead.
197
220
  */
198
221
  declare function runHarnessAgentSlice(options: RunHarnessAgentSliceOptions): Promise<HarnessWorkflowState>;
199
222
 
200
- export { type HarnessWorkflowAgent, type HarnessWorkflowChunk, type HarnessWorkflowFinalResult, type HarnessWorkflowInput, type HarnessWorkflowModelMessage, type HarnessWorkflowState, type HarnessWorkflowStatus, type HarnessWorkflowStreamResult, type HarnessWorkflowUsageSummary, type RunHarnessAgentSliceOptions, createHarnessWorkflowState, finalizeHarnessWorkflow, runHarnessAgentSlice };
223
+ 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
@@ -4,7 +4,7 @@ function createHarnessWorkflowState(input) {
4
4
  sessionId: input.sessionId,
5
5
  prompt: input.prompt ?? "",
6
6
  ...input.messages != null ? { messages: input.messages } : {},
7
- status: "running",
7
+ status: "not_started",
8
8
  ...input.resumeFrom != null ? { resumeFrom: input.resumeFrom } : {},
9
9
  ...input.continueFrom != null ? { continueFrom: input.continueFrom } : {}
10
10
  };
@@ -19,11 +19,9 @@ function finalizeHarnessWorkflow(state) {
19
19
  return { sessionId: state.sessionId, finishReason: "unknown" };
20
20
  }
21
21
 
22
- // src/run-harness-agent-slice.ts
23
- var DEFAULT_SLICE_TIMEOUT_SECONDS = 750;
24
- async function runHarnessAgentSlice(options) {
22
+ // src/run-harness-agent.ts
23
+ async function runHarnessAgent(options) {
25
24
  const { agent, state } = options;
26
- const sliceTimeoutSeconds = options.sliceTimeoutSeconds ?? DEFAULT_SLICE_TIMEOUT_SECONDS;
27
25
  const destroyOnFinish = options.destroyOnFinish ?? false;
28
26
  const session = state.continueFrom != null ? await agent.createSession({
29
27
  sessionId: state.sessionId,
@@ -55,12 +53,12 @@ async function runHarnessAgentSlice(options) {
55
53
  const writable = options.writable ?? await resolveWorkflowWritable();
56
54
  const writer = writable.getWriter();
57
55
  const streamContext = createMutableStreamContext(state.streamContext);
58
- const slicePartState = createSlicePartState();
56
+ const executionPartState = createExecutionPartState();
59
57
  let suspendPromise;
60
- const timer = setTimeout(() => {
58
+ const timer = options.timeSliceSeconds == null ? void 0 : setTimeout(() => {
61
59
  suspendPromise = session.suspendTurn();
62
- }, sliceTimeoutSeconds * 1e3);
63
- timer.unref?.();
60
+ }, options.timeSliceSeconds * 1e3);
61
+ timer?.unref?.();
64
62
  let sawError = false;
65
63
  let writerClosed = false;
66
64
  try {
@@ -83,11 +81,11 @@ async function runHarnessAgentSlice(options) {
83
81
  chunk: value,
84
82
  writer,
85
83
  streamContext,
86
- slicePartState
84
+ executionPartState
87
85
  });
88
86
  }
89
87
  } finally {
90
- clearTimeout(timer);
88
+ if (timer != null) clearTimeout(timer);
91
89
  reader.releaseLock();
92
90
  }
93
91
  if (sawError) {
@@ -105,15 +103,15 @@ async function runHarnessAgentSlice(options) {
105
103
  }
106
104
  if (suspendPromise != null) {
107
105
  const continueFrom = await suspendPromise;
108
- await closeOpenSliceParts({
106
+ await closeOpenExecutionParts({
109
107
  writer,
110
108
  streamContext,
111
- slicePartState
109
+ executionPartState
112
110
  });
113
111
  return {
114
112
  sessionId: state.sessionId,
115
113
  prompt: state.prompt,
116
- status: "timed_out",
114
+ status: "ready_for_next_step",
117
115
  continueFrom,
118
116
  ...serializeStreamContextField(streamContext)
119
117
  };
@@ -123,16 +121,39 @@ async function runHarnessAgentSlice(options) {
123
121
  Promise.resolve(result.totalUsage).catch(() => void 0)
124
122
  ]);
125
123
  const normalizedFinishReason = toFinishReasonString(finishReason);
126
- if (normalizedFinishReason === "tool-calls") {
124
+ if (session.hasUnfinishedTurn()) {
127
125
  const continueFrom = await session.suspendTurn();
128
- await writer.write({ type: "finish", finishReason: "tool-calls" });
126
+ if (!hasPendingHostInput(continueFrom)) {
127
+ await closeOpenExecutionParts({
128
+ writer,
129
+ streamContext,
130
+ executionPartState
131
+ });
132
+ return {
133
+ sessionId: state.sessionId,
134
+ prompt: state.prompt,
135
+ status: "ready_for_next_step",
136
+ continueFrom,
137
+ ...serializeStreamContextField(streamContext)
138
+ };
139
+ }
140
+ await writer.write({
141
+ type: "finish",
142
+ finishReason: normalizedFinishReason
143
+ });
129
144
  await writer.close();
130
145
  writerClosed = true;
131
146
  return {
132
147
  sessionId: state.sessionId,
133
148
  prompt: state.prompt,
134
149
  status: "awaiting_tool_approval",
135
- continueFrom
150
+ continueFrom,
151
+ resumeFrom: toResumeState({ continueFrom }),
152
+ finalResult: {
153
+ sessionId: state.sessionId,
154
+ finishReason: normalizedFinishReason,
155
+ usage: toUsageSummary(usage)
156
+ }
136
157
  };
137
158
  }
138
159
  await writer.write({ type: "finish" });
@@ -167,7 +188,7 @@ function createMutableStreamContext(context) {
167
188
  pendingToolInputs: { ...context?.pendingToolInputs ?? {} }
168
189
  };
169
190
  }
170
- function createSlicePartState() {
191
+ function createExecutionPartState() {
171
192
  return {
172
193
  openedTextParts: /* @__PURE__ */ new Set(),
173
194
  openedReasoningParts: /* @__PURE__ */ new Set(),
@@ -180,74 +201,74 @@ async function writeWorkflowChunk(options) {
180
201
  recordWorkflowChunk(options);
181
202
  }
182
203
  async function writeRequiredPrelude(options) {
183
- const { chunk, writer, streamContext, slicePartState } = options;
204
+ const { chunk, writer, streamContext, executionPartState } = options;
184
205
  const id = stringProperty({ chunk, key: "id" });
185
- if ((chunk.type === "text-delta" || chunk.type === "text-end") && id != null && streamContext.activeTextParts[id] != null && !slicePartState.openedTextParts.has(id)) {
206
+ if ((chunk.type === "text-delta" || chunk.type === "text-end") && id != null && streamContext.activeTextParts[id] != null && !executionPartState.openedTextParts.has(id)) {
186
207
  await writer.write(streamContext.activeTextParts[id]);
187
- slicePartState.openedTextParts.add(id);
208
+ executionPartState.openedTextParts.add(id);
188
209
  }
189
- if ((chunk.type === "reasoning-delta" || chunk.type === "reasoning-end") && id != null && streamContext.activeReasoningParts[id] != null && !slicePartState.openedReasoningParts.has(id)) {
210
+ if ((chunk.type === "reasoning-delta" || chunk.type === "reasoning-end") && id != null && streamContext.activeReasoningParts[id] != null && !executionPartState.openedReasoningParts.has(id)) {
190
211
  await writer.write(streamContext.activeReasoningParts[id]);
191
- slicePartState.openedReasoningParts.add(id);
212
+ executionPartState.openedReasoningParts.add(id);
192
213
  }
193
214
  const toolCallId = stringProperty({ chunk, key: "toolCallId" });
194
- if (toolCallId != null && needsToolInputPrelude(chunk) && streamContext.pendingToolInputs[toolCallId] != null && !slicePartState.writtenToolInputs.has(toolCallId)) {
215
+ if (toolCallId != null && needsToolInputPrelude(chunk) && streamContext.pendingToolInputs[toolCallId] != null && !executionPartState.writtenToolInputs.has(toolCallId)) {
195
216
  await writer.write(streamContext.pendingToolInputs[toolCallId]);
196
- slicePartState.writtenToolInputs.add(toolCallId);
217
+ executionPartState.writtenToolInputs.add(toolCallId);
197
218
  }
198
219
  }
199
220
  function recordWorkflowChunk(options) {
200
- const { chunk, streamContext, slicePartState } = options;
221
+ const { chunk, streamContext, executionPartState } = options;
201
222
  const id = stringProperty({ chunk, key: "id" });
202
223
  const toolCallId = stringProperty({ chunk, key: "toolCallId" });
203
224
  if (chunk.type === "text-start" && id != null) {
204
225
  streamContext.activeTextParts[id] = cloneChunk(chunk);
205
- slicePartState.openedTextParts.add(id);
226
+ executionPartState.openedTextParts.add(id);
206
227
  return;
207
228
  }
208
229
  if (chunk.type === "text-end" && id != null) {
209
230
  delete streamContext.activeTextParts[id];
210
- slicePartState.openedTextParts.delete(id);
231
+ executionPartState.openedTextParts.delete(id);
211
232
  return;
212
233
  }
213
234
  if (chunk.type === "reasoning-start" && id != null) {
214
235
  streamContext.activeReasoningParts[id] = cloneChunk(chunk);
215
- slicePartState.openedReasoningParts.add(id);
236
+ executionPartState.openedReasoningParts.add(id);
216
237
  return;
217
238
  }
218
239
  if (chunk.type === "reasoning-end" && id != null) {
219
240
  delete streamContext.activeReasoningParts[id];
220
- slicePartState.openedReasoningParts.delete(id);
241
+ executionPartState.openedReasoningParts.delete(id);
221
242
  return;
222
243
  }
223
244
  if (chunk.type === "tool-input-available" && toolCallId != null) {
224
245
  streamContext.pendingToolInputs[toolCallId] = cloneChunk(chunk);
225
- slicePartState.writtenToolInputs.add(toolCallId);
246
+ executionPartState.writtenToolInputs.add(toolCallId);
226
247
  return;
227
248
  }
228
249
  if (chunk.type === "tool-input-error" && toolCallId != null) {
229
250
  delete streamContext.pendingToolInputs[toolCallId];
230
- slicePartState.writtenToolInputs.add(toolCallId);
251
+ executionPartState.writtenToolInputs.add(toolCallId);
231
252
  return;
232
253
  }
233
254
  if ((chunk.type === "tool-output-error" || chunk.type === "tool-output-denied" || chunk.type === "tool-output-available" && chunk.preliminary !== true) && toolCallId != null) {
234
255
  delete streamContext.pendingToolInputs[toolCallId];
235
256
  }
236
257
  }
237
- async function closeOpenSliceParts(options) {
238
- const { writer, streamContext, slicePartState } = options;
239
- for (const id of slicePartState.openedTextParts) {
258
+ async function closeOpenExecutionParts(options) {
259
+ const { writer, streamContext, executionPartState } = options;
260
+ for (const id of executionPartState.openedTextParts) {
240
261
  if (streamContext.activeTextParts[id] != null) {
241
262
  await writer.write({ type: "text-end", id });
242
263
  }
243
264
  }
244
- slicePartState.openedTextParts.clear();
245
- for (const id of slicePartState.openedReasoningParts) {
265
+ executionPartState.openedTextParts.clear();
266
+ for (const id of executionPartState.openedReasoningParts) {
246
267
  if (streamContext.activeReasoningParts[id] != null) {
247
268
  await writer.write({ type: "reasoning-end", id });
248
269
  }
249
270
  }
250
- slicePartState.openedReasoningParts.clear();
271
+ executionPartState.openedReasoningParts.clear();
251
272
  }
252
273
  function serializeStreamContextField(context) {
253
274
  const streamContext = {
@@ -260,6 +281,19 @@ function serializeStreamContextField(context) {
260
281
  function needsToolInputPrelude(chunk) {
261
282
  return chunk.type === "tool-approval-request" || chunk.type === "tool-output-available" || chunk.type === "tool-output-error" || chunk.type === "tool-output-denied";
262
283
  }
284
+ function hasPendingHostInput(state) {
285
+ return (state.pendingToolApprovals?.length ?? 0) > 0 || (state.pendingToolResults?.length ?? 0) > 0;
286
+ }
287
+ function toResumeState(options) {
288
+ const { continueFrom } = options;
289
+ return {
290
+ type: "resume-session",
291
+ harnessId: continueFrom.harnessId,
292
+ specificationVersion: continueFrom.specificationVersion,
293
+ data: continueFrom.data,
294
+ continueFrom
295
+ };
296
+ }
263
297
  function stringProperty(options) {
264
298
  const value = options.chunk[options.key];
265
299
  return typeof value === "string" ? value : void 0;
@@ -304,9 +338,35 @@ function toUsageSummary(usage) {
304
338
  ...outputTokens != null ? { outputTokens } : {}
305
339
  };
306
340
  }
341
+
342
+ // src/run-harness-agent-step.ts
343
+ async function runHarnessAgentStep(options) {
344
+ return runHarnessAgent(options);
345
+ }
346
+
347
+ // src/run-harness-agent-time-slice.ts
348
+ var DEFAULT_TIME_SLICE_SECONDS = 750;
349
+ async function runHarnessAgentTimeSlice(options) {
350
+ return runHarnessAgent({
351
+ ...options,
352
+ timeSliceSeconds: options.timeSliceSeconds ?? DEFAULT_TIME_SLICE_SECONDS
353
+ });
354
+ }
355
+
356
+ // src/run-harness-agent-slice.ts
357
+ async function runHarnessAgentSlice(options) {
358
+ const { sliceTimeoutSeconds, timeSliceSeconds, ...timeSliceOptions } = options;
359
+ const state = await runHarnessAgentTimeSlice({
360
+ ...timeSliceOptions,
361
+ timeSliceSeconds: timeSliceSeconds ?? sliceTimeoutSeconds
362
+ });
363
+ return state.status === "ready_for_next_step" ? { ...state, status: "timed_out" } : state;
364
+ }
307
365
  export {
308
366
  createHarnessWorkflowState,
309
367
  finalizeHarnessWorkflow,
310
- runHarnessAgentSlice
368
+ runHarnessAgentSlice,
369
+ runHarnessAgentStep,
370
+ runHarnessAgentTimeSlice
311
371
  };
312
372
  //# sourceMappingURL=index.js.map