@ai-sdk/harness 1.0.75 → 1.0.77

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,25 @@
1
1
  # @ai-sdk/harness
2
2
 
3
+ ## 1.0.77
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [e6087c9]
8
+ - Updated dependencies [9566914]
9
+ - Updated dependencies [b181020]
10
+ - Updated dependencies [7054073]
11
+ - Updated dependencies [a828527]
12
+ - Updated dependencies [d3cc3fe]
13
+ - @ai-sdk/provider-utils@5.0.28
14
+ - ai@7.0.70
15
+
16
+ ## 1.0.76
17
+
18
+ ### Patch Changes
19
+
20
+ - fc1970b: feat(harness): allow passing caller-owned `sandboxSession` to `HarnessAgent.createSession()` and in that case allow omitting the then unnecessary `sandbox` arg from `HarnessAgent` constructor
21
+ - ai@7.0.69
22
+
3
23
  ## 1.0.75
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -98,9 +98,9 @@ const agent = new HarnessAgent({
98
98
  });
99
99
  ```
100
100
 
101
- Use `session.detach()` to park a bridge-backed session for later attach, `session.stop()` to save state and stop the sandbox, or `session.destroy()` to clean up without keeping resume state. Bridge-backed adapters such as Claude Code, Codex, OpenCode, and DeepAgents require a sandbox provider that exposes ports — `@ai-sdk/sandbox-vercel` is the supported choice today. `@ai-sdk/sandbox-just-bash` is suitable only for host-runtime or otherwise non-bridge flows, such as Pi.
101
+ Use `session.detach()` to park a bridge-backed session for later attach, `session.stop()` to save state and stop the sandbox, or `session.destroy()` to clean up without keeping resume state. Bridge-backed adapters such as Claude Code, Codex, OpenCode, and DeepAgents require a network sandbox session that exposes ports — `@ai-sdk/sandbox-vercel` is the supported choice today. `@ai-sdk/sandbox-just-bash` is suitable only for host-runtime or otherwise non-bridge flows, such as Pi.
102
102
 
103
- `sandbox` is a required `HarnessV1SandboxProvider` the agent calls `provider.createSession()` when a session starts. Use `sandboxConfig` for agent specific sandbox configuration that works independently from the sandbox provider that is used:
103
+ `sandbox` is an optional `HarnessV1SandboxProvider`. When omitted, pass a `HarnessV1NetworkSandboxSession` to every `agent.createSession({ sandboxSession })` call. Use `sandboxConfig` for agent specific sandbox configuration that works independently from the sandbox provider that is used:
104
104
 
105
105
  - Use `sandboxConfig.onSession` to prepare the acquired sandbox before the harness adapter starts. The hook runs for fresh and resumed sessions, so keep it idempotent.
106
106
  - Use `sandboxConfig.onBootstrap` for expensive sandbox setup that should be baked into a reusable snapshot, such as installing tools or cloning a large repository. Provide `sandboxConfig.bootstrapHash` with it and change that value whenever the bootstrap output should invalidate the cached snapshot.
@@ -116,9 +116,9 @@ bootstrap recipes and `sandboxConfig.onBootstrap`, returns the computed
116
116
  preparation identity and per-harness recipe identities, and leaves snapshotting
117
117
  or stopping the sandbox to your code. Later, create a sandbox from that snapshot
118
118
  and pass the native sandbox object to `createVercelSandbox({ sandbox })` for the
119
- `HarnessAgent`. When you reuse a caller-provided sandbox with a bridge-backed
120
- harness, declare the available port pool, for example
121
- `createVercelSandbox({ sandbox, bridgePorts: [4000] })`.
119
+ `HarnessAgent`. When several bridge-backed harnesses share a caller-provided
120
+ sandbox, create that sandbox with one exposed port for each harness. Then pass
121
+ each harness's assigned port to that harness's `create*` function.
122
122
 
123
123
  ### Available harnesses
124
124
 
@@ -126,7 +126,7 @@ See the [harness adapters documentation](https://ai-sdk.dev/v7/docs/ai-sdk-harne
126
126
 
127
127
  ## Implementing a harness
128
128
 
129
- Implement the `HarnessV1` factory and a `HarnessV1Session` whose `doPromptTurn` emits events; the agent surface, streaming, tool execution, and multi-turn state are handled for you. Read `startOpts.sandboxSession` for the network sandbox session the agent created and will stop on cleanup. Call `sandboxSession.restricted()` for the tool-safe file-IO/exec/spawn surface.
129
+ Implement the `HarnessV1` factory and a `HarnessV1Session` whose `doPromptTurn` emits events; the agent surface, streaming, tool execution, and multi-turn state are handled for you. Read `startOpts.sandboxSession` for the selected network sandbox session. The harness layer stops or destroys sessions it acquires from the provider, while a session passed to `agent.createSession({ sandboxSession })` remains caller-owned. Call `sandboxSession.restricted()` for the tool-safe file-IO/exec/spawn surface.
130
130
 
131
131
  Each prompt and continuation receives an optional `responseFormat`. JSON
132
132
  formats carry a caller-provided JSON Schema plus optional name and description;
@@ -4,75 +4,6 @@ import { OutputInterface, StopCondition, ToolApprovalStatus, TelemetryOptions, A
4
4
  import { z } from 'zod/v4';
5
5
  import { JSONValue, LanguageModelV4ToolCall, LanguageModelV4ToolApprovalRequest, LanguageModelV4ToolResult, LanguageModelV4FinishReason, LanguageModelV4Usage, JSONSchema7, AISDKError } from '@ai-sdk/provider';
6
6
 
7
- /** Severity of a diagnostic. */
8
- type HarnessDebugLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
9
- /**
10
- * Consumer-facing diagnostics configuration. Set on `HarnessAgentSettings` to
11
- * enable bridge log forwarding and the `HARNESS_DEBUG` stderr default in code.
12
- * `HARNESS_DEBUG` / `HARNESS_DEBUG_LEVEL` / `HARNESS_DEBUG_SUBSYSTEMS` env vars
13
- * fill any unset field — a convenience default, never the only path.
14
- */
15
- type HarnessDebugConfig = {
16
- /** Master switch. Nothing is captured or forwarded when false/unset. */
17
- readonly enabled?: boolean;
18
- /** Threshold; events at or above this severity are emitted. Default `debug`. */
19
- readonly level?: HarnessDebugLevel;
20
- /** Dotted-prefix subsystem filter for structured events. */
21
- readonly subsystems?: ReadonlyArray<string>;
22
- };
23
- /**
24
- * A forwarded bridge diagnostic, normalized for host consumers.
25
- *
26
- * The bridge emits two raw frame kinds — captured console lines (`sandbox-log`)
27
- * and structured events (`debug-event`). The framework normalizes both into
28
- * this single shape before handing them to a consumer's `onLog` callback, the
29
- * `HARNESS_DEBUG` stderr default, and observability reporters. Diagnostics are
30
- * kept first-class and per-line — they are never folded into telemetry spans.
31
- */
32
- type HarnessDiagnostic = {
33
- /**
34
- * Severity. Structured events carry their own level; captured console lines
35
- * map `stderr` → `'warn'` and `stdout` → `'info'`.
36
- */
37
- readonly level: HarnessDebugLevel;
38
- /** Human-readable line (console capture) or message (structured event). */
39
- readonly message: string;
40
- /**
41
- * Dotted subsystem. For captured console output this is
42
- * `sandbox.log.<source>`; for structured events it is the adapter-supplied
43
- * subsystem (e.g. `bridge.turn`).
44
- */
45
- readonly subsystem: string;
46
- /** `'log'` = captured console line; `'event'` = structured `bridgeLog`. */
47
- readonly kind: 'log' | 'event';
48
- /** Originating sandbox source label (console capture). */
49
- readonly source?: string;
50
- /** Which standard stream the line came from (console capture). */
51
- readonly stream?: 'stdout' | 'stderr';
52
- /** Structured attributes (structured events only). */
53
- readonly attrs?: Record<string, unknown>;
54
- /** Error payload (structured events only). */
55
- readonly error?: {
56
- name?: string;
57
- message: string;
58
- stack?: string;
59
- };
60
- /** The harness session this diagnostic originated from. */
61
- readonly sessionId?: string;
62
- /** Host receipt time (epoch ms). */
63
- readonly timestamp: number;
64
- };
65
- /**
66
- * A telemetry integration that also wants the per-line diagnostics stream. The
67
- * framework calls `ingestDiagnostic` for every forwarded bridge diagnostic in
68
- * addition to driving the standard `Telemetry` span lifecycle, so a single
69
- * reporter object (e.g. `createFileReporter`) registered in
70
- * `telemetry.integrations` receives both spans and logs.
71
- */
72
- interface HarnessDiagnosticConsumer {
73
- ingestDiagnostic?(diagnostic: HarnessDiagnostic): void;
74
- }
75
-
76
7
  /**
77
8
  * One file to write into the sandbox as part of an adapter's bootstrap recipe.
78
9
  * Absolute paths are used as-is. Relative paths are resolved against the
@@ -1111,17 +1042,6 @@ type HarnessV1BuiltinTool<INPUT = unknown, OUTPUT = unknown> = Tool<INPUT, OUTPU
1111
1042
  interface HarnessV1SandboxProvider {
1112
1043
  readonly specificationVersion: 'harness-sandbox-v1';
1113
1044
  readonly providerId: string;
1114
- /**
1115
- * Pool of ports the consumer reserved on a caller-provided sandbox for
1116
- * concurrent harness sessions. The session manager leases one port per
1117
- * session and releases on stop or destroy.
1118
- *
1119
- * Only meaningful when the provider wraps a caller-provided sandbox
1120
- * (the caller pre-declared the ports). In create-new modes the provider
1121
- * mints a fresh sandbox per session, so no leasing is needed; providers
1122
- * leave this undefined.
1123
- */
1124
- readonly bridgePorts?: ReadonlyArray<number>;
1125
1045
  /**
1126
1046
  * Providers should throw `HarnessSandboxAuthenticationError` when sandbox
1127
1047
  * acquisition fails because credentials are missing, invalid, or not
@@ -1178,6 +1098,75 @@ interface HarnessV1SandboxProvider {
1178
1098
  }) => PromiseLike<HarnessV1NetworkSandboxSession>;
1179
1099
  }
1180
1100
 
1101
+ /** Severity of a diagnostic. */
1102
+ type HarnessDebugLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
1103
+ /**
1104
+ * Consumer-facing diagnostics configuration. Set on `HarnessAgentSettings` to
1105
+ * enable bridge log forwarding and the `HARNESS_DEBUG` stderr default in code.
1106
+ * `HARNESS_DEBUG` / `HARNESS_DEBUG_LEVEL` / `HARNESS_DEBUG_SUBSYSTEMS` env vars
1107
+ * fill any unset field — a convenience default, never the only path.
1108
+ */
1109
+ type HarnessDebugConfig = {
1110
+ /** Master switch. Nothing is captured or forwarded when false/unset. */
1111
+ readonly enabled?: boolean;
1112
+ /** Threshold; events at or above this severity are emitted. Default `debug`. */
1113
+ readonly level?: HarnessDebugLevel;
1114
+ /** Dotted-prefix subsystem filter for structured events. */
1115
+ readonly subsystems?: ReadonlyArray<string>;
1116
+ };
1117
+ /**
1118
+ * A forwarded bridge diagnostic, normalized for host consumers.
1119
+ *
1120
+ * The bridge emits two raw frame kinds — captured console lines (`sandbox-log`)
1121
+ * and structured events (`debug-event`). The framework normalizes both into
1122
+ * this single shape before handing them to a consumer's `onLog` callback, the
1123
+ * `HARNESS_DEBUG` stderr default, and observability reporters. Diagnostics are
1124
+ * kept first-class and per-line — they are never folded into telemetry spans.
1125
+ */
1126
+ type HarnessDiagnostic = {
1127
+ /**
1128
+ * Severity. Structured events carry their own level; captured console lines
1129
+ * map `stderr` → `'warn'` and `stdout` → `'info'`.
1130
+ */
1131
+ readonly level: HarnessDebugLevel;
1132
+ /** Human-readable line (console capture) or message (structured event). */
1133
+ readonly message: string;
1134
+ /**
1135
+ * Dotted subsystem. For captured console output this is
1136
+ * `sandbox.log.<source>`; for structured events it is the adapter-supplied
1137
+ * subsystem (e.g. `bridge.turn`).
1138
+ */
1139
+ readonly subsystem: string;
1140
+ /** `'log'` = captured console line; `'event'` = structured `bridgeLog`. */
1141
+ readonly kind: 'log' | 'event';
1142
+ /** Originating sandbox source label (console capture). */
1143
+ readonly source?: string;
1144
+ /** Which standard stream the line came from (console capture). */
1145
+ readonly stream?: 'stdout' | 'stderr';
1146
+ /** Structured attributes (structured events only). */
1147
+ readonly attrs?: Record<string, unknown>;
1148
+ /** Error payload (structured events only). */
1149
+ readonly error?: {
1150
+ name?: string;
1151
+ message: string;
1152
+ stack?: string;
1153
+ };
1154
+ /** The harness session this diagnostic originated from. */
1155
+ readonly sessionId?: string;
1156
+ /** Host receipt time (epoch ms). */
1157
+ readonly timestamp: number;
1158
+ };
1159
+ /**
1160
+ * A telemetry integration that also wants the per-line diagnostics stream. The
1161
+ * framework calls `ingestDiagnostic` for every forwarded bridge diagnostic in
1162
+ * addition to driving the standard `Telemetry` span lifecycle, so a single
1163
+ * reporter object (e.g. `createFileReporter`) registered in
1164
+ * `telemetry.integrations` receives both spans and logs.
1165
+ */
1166
+ interface HarnessDiagnosticConsumer {
1167
+ ingestDiagnostic?(diagnostic: HarnessDiagnostic): void;
1168
+ }
1169
+
1181
1170
  type HarnessAgentAdapter<TBuiltinTools extends ToolSet = ToolSet> = HarnessV1<TBuiltinTools>;
1182
1171
  type HarnessAgentBuiltinTool<INPUT = unknown, OUTPUT = unknown> = HarnessV1BuiltinTool<INPUT, OUTPUT>;
1183
1172
  type HarnessAgentBuiltinToolName = HarnessV1BuiltinToolName;
@@ -1333,12 +1322,11 @@ type HarnessAgentSettings<THarness extends HarnessAgentAdapter<any> = HarnessAge
1333
1322
  */
1334
1323
  readonly toolApproval?: HarnessAgentToolApprovalConfiguration;
1335
1324
  /**
1336
- * Sandbox provider whose `create()` produces the network sandbox session the
1337
- * harness runs against. Its `restricted()` view is also propagated to user
1338
- * tool `execute()` calls (as the `experimental_sandbox` field), typed as
1339
- * `Experimental_SandboxSession` so tools cannot reach the infra surface.
1325
+ * Optional sandbox provider used to create or resume network sandbox
1326
+ * sessions. When omitted, every `createSession()` call must provide an
1327
+ * existing network sandbox session.
1340
1328
  */
1341
- readonly sandbox: HarnessV1SandboxProvider;
1329
+ readonly sandbox?: HarnessV1SandboxProvider;
1342
1330
  /**
1343
1331
  * Sandbox working-directory and lifecycle hook configuration.
1344
1332
  */
@@ -1414,9 +1402,7 @@ type HarnessAgentTurnState = 'idle' | 'running' | 'awaiting-approval' | 'awaitin
1414
1402
  * Live harness session held by the caller.
1415
1403
  *
1416
1404
  * Created by {@link import('./harness-agent').HarnessAgent.createSession}.
1417
- * Owns the underlying adapter session, the network sandbox session, and the
1418
- * bridge-port lease (when the provider wraps a caller-provided sandbox with a
1419
- * port pool).
1405
+ * Owns the underlying adapter session and the network sandbox session.
1420
1406
  *
1421
1407
  * Pass the instance back to `agent.generate` / `agent.stream` on every
1422
1408
  * call; end the local handle with `detach()`, `stop()`, or `destroy()`.
@@ -1432,11 +1418,10 @@ declare class HarnessAgentSession {
1432
1418
  */
1433
1419
  readonly sessionId: string;
1434
1420
  private readonly harness;
1435
- private readonly sandboxProvider;
1436
1421
  private readonly sessionWorkDir;
1422
+ private readonly ownsSandboxLifecycle;
1437
1423
  private underlyingSession;
1438
1424
  private sandboxSession;
1439
- private leasedBridgePort;
1440
1425
  private readonly toolApproval;
1441
1426
  private readonly pendingToolApprovals;
1442
1427
  private readonly pendingToolResults;
@@ -1455,8 +1440,7 @@ declare class HarnessAgentSession {
1455
1440
  harness: HarnessAgentAdapter;
1456
1441
  underlyingSession: HarnessAgentAdapterSession;
1457
1442
  sandboxSession: HarnessV1NetworkSandboxSession;
1458
- sandboxProvider: HarnessV1SandboxProvider;
1459
- leasedBridgePort?: number;
1443
+ ownsSandboxLifecycle?: boolean;
1460
1444
  sessionWorkDir: string;
1461
1445
  toolApproval: HarnessAgentToolApprovalConfiguration | undefined;
1462
1446
  pendingToolApprovals?: readonly HarnessAgentPendingToolApproval[];
@@ -1512,14 +1496,15 @@ declare class HarnessAgentSession {
1512
1496
  */
1513
1497
  detach(): Promise<HarnessAgentResumeSessionState>;
1514
1498
  /**
1515
- * Persist enough state to resume later, then stop the runtime and sandbox.
1499
+ * Persist enough state to resume later, then stop the runtime and any
1500
+ * harness-owned sandbox.
1516
1501
  * Returns the resume state for a future
1517
1502
  * `agent.createSession({ sessionId, resumeFrom })` call.
1518
1503
  */
1519
1504
  stop(): Promise<HarnessAgentResumeSessionState>;
1520
1505
  /**
1521
- * Stop the runtime and discard resumability. The sandbox is destroyed when
1522
- * the provider supports destruction; otherwise it is stopped.
1506
+ * Stop the runtime and discard resumability. A harness-owned sandbox is
1507
+ * destroyed when supported; otherwise it is stopped.
1523
1508
  */
1524
1509
  destroy(): Promise<void>;
1525
1510
  /**
@@ -1531,9 +1516,8 @@ declare class HarnessAgentSession {
1531
1516
  *
1532
1517
  * After this call the session is detached. This in-process handle no
1533
1518
  * longer drives turns; a future slice creates a fresh session from the
1534
- * returned state. The sandbox is **not** stopped and no port lease is
1535
- * released, because bridge-backed adapters may still have a live bridge on
1536
- * that port.
1519
+ * returned state. The sandbox is **not** stopped because bridge-backed
1520
+ * adapters may still have a live bridge.
1537
1521
  */
1538
1522
  suspendTurn(): Promise<HarnessAgentContinueTurnState>;
1539
1523
  private getPendingToolApprovals;
@@ -1549,7 +1533,6 @@ declare class HarnessAgentSession {
1549
1533
  private startTrackedTurn;
1550
1534
  private finishTrackedTurn;
1551
1535
  private endLocalHandle;
1552
- private releasePortLease;
1553
1536
  private requireReusableSession;
1554
1537
  }
1555
1538
 
@@ -1592,12 +1575,13 @@ interface HarnessAgentCallExtensions {
1592
1575
  * the result is fed back to the harness via `submitToolResult`.
1593
1576
  * Adapter builtin tools (e.g. Claude Code's `Bash`) pass through
1594
1577
  * untouched.
1595
- * - **Sandbox propagation.** `settings.sandbox` is a sandbox provider.
1596
- * On `createSession`, the agent calls `provider.createSession()` (or
1597
- * `resumeSession()`) and passes the resulting network sandbox session into
1598
- * `doStart`. Its `restricted()` view (a tool-safe
1578
+ * - **Sandbox propagation.** On `createSession`, the agent uses a
1579
+ * caller-provided network sandbox session when present; otherwise it calls
1580
+ * the configured provider's `createSession()` (or `resumeSession()`). It
1581
+ * passes the selected session into `doStart`. Its `restricted()` view (a tool-safe
1599
1582
  * `Experimental_SandboxSession`) is handed to user-tool `execute()` calls
1600
- * via `experimental_sandbox`.
1583
+ * via `experimental_sandbox`. Caller-provided sandboxes remain owned by the
1584
+ * caller and are not stopped or destroyed by the harness layer.
1601
1585
  */
1602
1586
  declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAgentAdapter, TUserTools extends ToolSet = {}, RUNTIME_CONTEXT extends Context = Context, OUTPUT extends OutputInterface = never> implements Agent<never, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT> {
1603
1587
  readonly version: "agent-v1";
@@ -1646,6 +1630,11 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
1646
1630
  * handing it to the adapter.
1647
1631
  */
1648
1632
  continueFrom?: HarnessAgentContinueTurnState;
1633
+ /**
1634
+ * Existing network sandbox session to run the harness in. When provided,
1635
+ * the caller retains ownership of the sandbox lifecycle.
1636
+ */
1637
+ sandboxSession?: HarnessV1NetworkSandboxSession;
1649
1638
  abortSignal?: AbortSignal;
1650
1639
  }): Promise<HarnessAgentSession>;
1651
1640
  generate(options: AgentCallParameters<never, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT> & HarnessAgentCallExtensions): Promise<GenerateTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT>>;
@@ -1676,7 +1665,6 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
1676
1665
  abortSignal?: AbortSignal;
1677
1666
  }): Promise<StreamTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT>>;
1678
1667
  private _startTurn;
1679
- private _acquireSandbox;
1680
1668
  private _resolveTurnInput;
1681
1669
  private _toToolSpecs;
1682
1670
  private _toGenerateResult;