@ai-sdk/harness 0.0.0 → 1.0.0-canary.2

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 ADDED
@@ -0,0 +1,13 @@
1
+ # @ai-sdk/harness
2
+
3
+ ## 1.0.0-canary.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 6c7a3e5: Start the `1.0.0` canary release line for the experimental harness and sandbox packages. They were unintentionally published as `0.0.0-canary.*` because they were scaffolded with a `0.0.0-canary.0` premajor version, which semver could not advance past on a major bump.
8
+
9
+ ## 0.0.0-canary.1
10
+
11
+ ### Major Changes
12
+
13
+ - 9d6dbe0: feat(harness): add sandbox specific expansion for harness abstraction, add `sandbox-just-bash` and `sandbox-vercel`
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2023 Vercel, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # AI SDK - Harness
2
+
3
+ _This package is **experimental**._
4
+
5
+ Harness abstraction, including an expanded network session sandbox interface to support harness sandbox needs.
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ npm i @ai-sdk/harness
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ TBD.
@@ -0,0 +1,220 @@
1
+ import { AISDKError } from '@ai-sdk/provider';
2
+ import { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
3
+
4
+ declare const symbol$1: unique symbol;
5
+ /**
6
+ * Base error type for failures originating in or signalled by a harness
7
+ * adapter. Specific failure modes (e.g. unsupported capability) extend this
8
+ * class.
9
+ */
10
+ declare class HarnessError extends AISDKError {
11
+ private readonly [symbol$1];
12
+ constructor({ message, cause }: {
13
+ message: string;
14
+ cause?: unknown;
15
+ });
16
+ static isInstance(error: unknown): error is HarnessError;
17
+ }
18
+
19
+ declare const symbol: unique symbol;
20
+ /**
21
+ * Thrown when a caller asks the harness to do something the adapter (or the
22
+ * supplied sandbox) does not support, e.g. requesting manual compaction from
23
+ * an adapter that only auto-compacts, or invoking `getPortUrl` on a sandbox
24
+ * that does not expose one.
25
+ *
26
+ * The caller supplies the full human-readable message. Optional `harnessId`
27
+ * is recorded as structured context for tooling.
28
+ */
29
+ declare class HarnessCapabilityUnsupportedError extends HarnessError {
30
+ private readonly [symbol];
31
+ readonly harnessId?: string;
32
+ constructor({ message, harnessId, cause, }: {
33
+ message: string;
34
+ harnessId?: string;
35
+ cause?: unknown;
36
+ });
37
+ static isInstance(error: unknown): error is HarnessCapabilityUnsupportedError;
38
+ }
39
+
40
+ /**
41
+ * Network sandbox session returned by `HarnessV1SandboxProvider.createSession()`. The
42
+ * harness keeps this for the lifetime of a session. It is itself a
43
+ * {@link SandboxSession} (file I/O, exec, spawn) and adds the infra surface on
44
+ * top: port resolution, lifecycle, and network-policy mutation.
45
+ *
46
+ * Code that should only touch the filesystem and spawn processes receives the
47
+ * reduced view from {@link HarnessV1NetworkSandboxSession.restricted}, never the
48
+ * network sandbox session itself — so it cannot stop the sandbox or change its
49
+ * network policy.
50
+ */
51
+ interface HarnessV1NetworkSandboxSession extends Experimental_SandboxSession {
52
+ /**
53
+ * Stable identifier for the underlying sandbox resource. Used by the
54
+ * harness session manager as the durable lookup key for cross-process
55
+ * resume — the framework persists this on the resume payload so a future
56
+ * process can call `HarnessV1SandboxProvider.resume?({ sessionId })` and
57
+ * reach the same resource. Providers populate it from their native
58
+ * identifier (Vercel: the sandbox name; just-bash: a UUID minted at
59
+ * create time).
60
+ */
61
+ readonly id: string;
62
+ /**
63
+ * The sandbox's default working directory — the absolute path that
64
+ * `run`/`spawn` resolve relative commands against when no `workingDirectory`
65
+ * is given. Read from the live sandbox (it is provider-specific and
66
+ * configurable at create time: Vercel defaults to `/vercel/sandbox`,
67
+ * just-bash to `/home/user`), never hardcoded.
68
+ *
69
+ * The framework composes each session's working directory underneath this
70
+ * path (`<defaultWorkingDirectory>/<harnessId>-<sessionId>`) so adapters do
71
+ * not bake a provider-specific base into their own paths.
72
+ */
73
+ readonly defaultWorkingDirectory: string;
74
+ /** Ports the sandbox exposes; resolvable to public URLs via `getPortUrl`. */
75
+ readonly ports: ReadonlyArray<number>;
76
+ /**
77
+ * Resolve a publicly-reachable URL for a sandbox-exposed port. Bridge-backed
78
+ * adapters call this to open their WebSocket to the in-sandbox bridge.
79
+ */
80
+ readonly getPortUrl: (options: {
81
+ port: number;
82
+ protocol?: 'http' | 'https' | 'ws';
83
+ }) => PromiseLike<string>;
84
+ /** Stop the sandbox. Idempotent. */
85
+ readonly stop: () => PromiseLike<void>;
86
+ /**
87
+ * Destroy/delete the sandbox resource when supported. Optional because some
88
+ * providers only have a stop/dispose concept. Implementations must handle
89
+ * both a still-running sandbox and a previously stopped sandbox.
90
+ */
91
+ readonly destroy?: () => PromiseLike<void>;
92
+ /**
93
+ * Update the sandbox's outbound network policy. Optional — implementations
94
+ * without a local enforcement primitive (e.g. just-bash) omit this. Callers
95
+ * use optional-call (`sandboxSession.setNetworkPolicy?.(policy)`); a
96
+ * missing implementation is a no-op.
97
+ */
98
+ readonly setNetworkPolicy?: (policy: HarnessV1NetworkPolicy) => PromiseLike<void>;
99
+ /**
100
+ * Replace the set of ports exposed by the sandbox. Full-replacement
101
+ * semantics: ports omitted from the array are deregistered. Optional —
102
+ * implementations that cannot expose ports (e.g. just-bash) omit this.
103
+ */
104
+ readonly setPorts?: (ports: ReadonlyArray<number>, options?: {
105
+ abortSignal?: AbortSignal;
106
+ }) => PromiseLike<void>;
107
+ /**
108
+ * Reduced view of this session, typed as the bare {@link SandboxSession}
109
+ * (file I/O, exec, spawn) — nothing that could stop the sandbox or change
110
+ * its network policy. Pass this to user-tool `execute()` calls and other
111
+ * code that must not reach the infra surface.
112
+ *
113
+ * The returned object points at exactly the same underlying sandbox
114
+ * resource as the network sandbox session it was produced from; it is only a
115
+ * narrower surface over the same resource, not a separate sandbox.
116
+ */
117
+ readonly restricted: () => Experimental_SandboxSession;
118
+ }
119
+ /**
120
+ * Outbound network policy applied by the sandbox runtime.
121
+ *
122
+ * `'allow-all'` and `'deny-all'` are convenience presets. `'custom'` is an
123
+ * allow-list with an optional CIDR deny-list that takes precedence:
124
+ *
125
+ * - Reachable hosts are the union of `allowedHosts` and `allowedCIDRs`.
126
+ * - `deniedCIDRs` wins over both, useful for blocking cloud-metadata IPs while
127
+ * otherwise allowing broad access.
128
+ *
129
+ * The two `'custom'` branches share the same discriminator but each requires
130
+ * a different allow field. Specifying `'custom'` with only `deniedCIDRs`
131
+ * (deny-only) is rejected at compile time — functionally it would be
132
+ * equivalent to `'deny-all'`.
133
+ */
134
+ type HarnessV1NetworkPolicy = {
135
+ mode: 'allow-all';
136
+ } | {
137
+ mode: 'deny-all';
138
+ } | {
139
+ mode: 'custom';
140
+ allowedHosts: ReadonlyArray<string>;
141
+ allowedCIDRs?: ReadonlyArray<string>;
142
+ deniedCIDRs?: ReadonlyArray<string>;
143
+ } | {
144
+ mode: 'custom';
145
+ allowedHosts?: ReadonlyArray<string>;
146
+ allowedCIDRs: ReadonlyArray<string>;
147
+ deniedCIDRs?: ReadonlyArray<string>;
148
+ };
149
+
150
+ /**
151
+ * Provider that produces network sandbox sessions for harness sessions. Lives at
152
+ * module scope as a stable, synchronous object — analogous to
153
+ * `LanguageModelV4` providers, no I/O performed at construction. The actual
154
+ * sandbox is created (or wrapped) when `HarnessAgent` calls `createSession()`.
155
+ */
156
+ interface HarnessV1SandboxProvider {
157
+ readonly specificationVersion: 'harness-sandbox-v1';
158
+ readonly providerId: string;
159
+ /**
160
+ * Pool of ports the consumer reserved on a caller-provided sandbox for
161
+ * concurrent harness sessions. The session manager leases one port per
162
+ * session and releases on stop or destroy.
163
+ *
164
+ * Only meaningful when the provider wraps a caller-provided sandbox
165
+ * (the caller pre-declared the ports). In create-new modes the provider
166
+ * mints a fresh sandbox per session, so no leasing is needed; providers
167
+ * leave this undefined.
168
+ */
169
+ readonly bridgePorts?: ReadonlyArray<number>;
170
+ readonly createSession: (options?: {
171
+ /**
172
+ * Stable per-session identifier. When supplied, the provider names the
173
+ * underlying resource deterministically so a future call to `resume`
174
+ * (potentially from a different process) can find the same sandbox.
175
+ * Omitted from prewarm and other paths that don't need a resumable
176
+ * resource — in that case the provider falls back to its native
177
+ * auto-naming.
178
+ */
179
+ sessionId?: string;
180
+ abortSignal?: AbortSignal;
181
+ /**
182
+ * Stable identity for snapshot-based reuse. Providers that support
183
+ * persistence/snapshots use this as part of the persistent sandbox
184
+ * name; subsequent calls with the same identity resume from snapshot.
185
+ *
186
+ * Ignored when the provider is wrapping a caller-provided sandbox.
187
+ */
188
+ identity?: string;
189
+ /**
190
+ * Called exactly once per identity, on fresh creation. Snapshot-capable
191
+ * providers wire this into the platform's one-time-setup hook so the
192
+ * side effects are baked into the snapshot. Providers without snapshot
193
+ * support run it immediately after fresh create.
194
+ *
195
+ * Not called when the provider is wrapping a caller-provided sandbox
196
+ * (the caller owns the sandbox; the framework applies its own
197
+ * idempotent bootstrap post-create instead).
198
+ */
199
+ onFirstCreate?: (session: Experimental_SandboxSession, opts: {
200
+ abortSignal?: AbortSignal;
201
+ }) => Promise<void>;
202
+ }) => PromiseLike<HarnessV1NetworkSandboxSession>;
203
+ /**
204
+ * Reattach to an existing sandbox previously created with the same
205
+ * `sessionId`. Optional — providers that cannot rehydrate by id (e.g.
206
+ * just-bash) omit this; the harness throws
207
+ * `HarnessCapabilityUnsupportedError` when resume is attempted against
208
+ * them.
209
+ *
210
+ * The provider derives the sandbox identifier from `sessionId` using the
211
+ * same deterministic naming scheme it used in `createSession`. Returns a
212
+ * network sandbox session bound to the existing resource.
213
+ */
214
+ readonly resumeSession?: (options: {
215
+ sessionId: string;
216
+ abortSignal?: AbortSignal;
217
+ }) => PromiseLike<HarnessV1NetworkSandboxSession>;
218
+ }
219
+
220
+ export { HarnessCapabilityUnsupportedError, HarnessError, type HarnessV1NetworkPolicy, type HarnessV1NetworkSandboxSession, type HarnessV1SandboxProvider };
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ // src/errors/harness-error.ts
2
+ import { AISDKError } from "@ai-sdk/provider";
3
+ var name = "AI_HarnessError";
4
+ var marker = `vercel.ai.error.${name}`;
5
+ var symbol = Symbol.for(marker);
6
+ var _a, _b;
7
+ var HarnessError = class extends (_b = AISDKError, _a = symbol, _b) {
8
+ constructor({ message, cause }) {
9
+ super({ name, message, cause });
10
+ this[_a] = true;
11
+ }
12
+ static isInstance(error) {
13
+ return AISDKError.hasMarker(error, marker);
14
+ }
15
+ };
16
+
17
+ // src/errors/harness-capability-unsupported-error.ts
18
+ import { AISDKError as AISDKError2 } from "@ai-sdk/provider";
19
+ var name2 = "AI_HarnessCapabilityUnsupportedError";
20
+ var marker2 = `vercel.ai.error.${name2}`;
21
+ var symbol2 = Symbol.for(marker2);
22
+ var _a2, _b2;
23
+ var HarnessCapabilityUnsupportedError = class extends (_b2 = HarnessError, _a2 = symbol2, _b2) {
24
+ constructor({
25
+ message,
26
+ harnessId,
27
+ cause
28
+ }) {
29
+ super({ message, cause });
30
+ this[_a2] = true;
31
+ Object.defineProperty(this, "name", { value: name2 });
32
+ this.harnessId = harnessId;
33
+ }
34
+ static isInstance(error) {
35
+ return AISDKError2.hasMarker(error, marker2);
36
+ }
37
+ };
38
+ export {
39
+ HarnessCapabilityUnsupportedError,
40
+ HarnessError
41
+ };
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors/harness-error.ts","../src/errors/harness-capability-unsupported-error.ts"],"sourcesContent":["import { AISDKError } from '@ai-sdk/provider';\n\nconst name = 'AI_HarnessError';\nconst marker = `vercel.ai.error.${name}`;\nconst symbol = Symbol.for(marker);\n\n/**\n * Base error type for failures originating in or signalled by a harness\n * adapter. Specific failure modes (e.g. unsupported capability) extend this\n * class.\n */\nexport class HarnessError extends AISDKError {\n private readonly [symbol] = true;\n\n constructor({ message, cause }: { message: string; cause?: unknown }) {\n super({ name, message, cause });\n }\n\n static isInstance(error: unknown): error is HarnessError {\n return AISDKError.hasMarker(error, marker);\n }\n}\n","import { AISDKError } from '@ai-sdk/provider';\nimport { HarnessError } from './harness-error';\n\nconst name = 'AI_HarnessCapabilityUnsupportedError';\nconst marker = `vercel.ai.error.${name}`;\nconst symbol = Symbol.for(marker);\n\n/**\n * Thrown when a caller asks the harness to do something the adapter (or the\n * supplied sandbox) does not support, e.g. requesting manual compaction from\n * an adapter that only auto-compacts, or invoking `getPortUrl` on a sandbox\n * that does not expose one.\n *\n * The caller supplies the full human-readable message. Optional `harnessId`\n * is recorded as structured context for tooling.\n */\nexport class HarnessCapabilityUnsupportedError extends HarnessError {\n private readonly [symbol] = true;\n\n readonly harnessId?: string;\n\n constructor({\n message,\n harnessId,\n cause,\n }: {\n message: string;\n harnessId?: string;\n cause?: unknown;\n }) {\n super({ message, cause });\n Object.defineProperty(this, 'name', { value: name });\n this.harnessId = harnessId;\n }\n\n static isInstance(\n error: unknown,\n ): error is HarnessCapabilityUnsupportedError {\n return AISDKError.hasMarker(error, marker);\n }\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAE3B,IAAM,OAAO;AACb,IAAM,SAAS,mBAAmB,IAAI;AACtC,IAAM,SAAS,OAAO,IAAI,MAAM;AAJhC;AAWO,IAAM,eAAN,eAA2B,iBACd,aADc,IAAW;AAAA,EAG3C,YAAY,EAAE,SAAS,MAAM,GAAyC;AACpE,UAAM,EAAE,MAAM,SAAS,MAAM,CAAC;AAHhC,SAAkB,MAAU;AAAA,EAI5B;AAAA,EAEA,OAAO,WAAW,OAAuC;AACvD,WAAO,WAAW,UAAU,OAAO,MAAM;AAAA,EAC3C;AACF;;;ACrBA,SAAS,cAAAA,mBAAkB;AAG3B,IAAMC,QAAO;AACb,IAAMC,UAAS,mBAAmBD,KAAI;AACtC,IAAME,UAAS,OAAO,IAAID,OAAM;AALhC,IAAAE,KAAAC;AAgBO,IAAM,oCAAN,eAAgDA,MAAA,cACnCD,MAAAD,SADmCE,KAAa;AAAA,EAKlE,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAIG;AACD,UAAM,EAAE,SAAS,MAAM,CAAC;AAb1B,SAAkBD,OAAU;AAc1B,WAAO,eAAe,MAAM,QAAQ,EAAE,OAAOH,MAAK,CAAC;AACnD,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,OAAO,WACL,OAC4C;AAC5C,WAAOK,YAAW,UAAU,OAAOJ,OAAM;AAAA,EAC3C;AACF;","names":["AISDKError","name","marker","symbol","_a","_b","AISDKError"]}
package/package.json CHANGED
@@ -1 +1,64 @@
1
- {"name":"@ai-sdk/harness","version":"0.0.0"}
1
+ {
2
+ "name": "@ai-sdk/harness",
3
+ "version": "1.0.0-canary.2",
4
+ "type": "module",
5
+ "license": "Apache-2.0",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "source": "./src/index.ts",
10
+ "files": [
11
+ "dist/**/*",
12
+ "src",
13
+ "!src/**/*.test.ts",
14
+ "!src/**/*.test-d.ts",
15
+ "!src/**/__snapshots__",
16
+ "!src/**/__fixtures__",
17
+ "CHANGELOG.md"
18
+ ],
19
+ "exports": {
20
+ "./package.json": "./package.json",
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
27
+ "dependencies": {
28
+ "@ai-sdk/provider": "4.0.0-canary.18",
29
+ "@ai-sdk/provider-utils": "5.0.0-canary.46"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "22.19.19",
33
+ "tsup": "^8.5.1",
34
+ "typescript": "5.8.3",
35
+ "@vercel/ai-tsconfig": "0.0.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=22"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "provenance": true
43
+ },
44
+ "homepage": "https://ai-sdk.dev/docs",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/vercel/ai",
48
+ "directory": "packages/harness"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/vercel/ai/issues"
52
+ },
53
+ "keywords": [
54
+ "ai",
55
+ "harness",
56
+ "agent"
57
+ ],
58
+ "scripts": {
59
+ "build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
60
+ "build:watch": "pnpm clean && tsup --watch",
61
+ "clean": "del-cli dist *.tsbuildinfo",
62
+ "type-check": "tsc --build"
63
+ }
64
+ }
@@ -0,0 +1,41 @@
1
+ import { AISDKError } from '@ai-sdk/provider';
2
+ import { HarnessError } from './harness-error';
3
+
4
+ const name = 'AI_HarnessCapabilityUnsupportedError';
5
+ const marker = `vercel.ai.error.${name}`;
6
+ const symbol = Symbol.for(marker);
7
+
8
+ /**
9
+ * Thrown when a caller asks the harness to do something the adapter (or the
10
+ * supplied sandbox) does not support, e.g. requesting manual compaction from
11
+ * an adapter that only auto-compacts, or invoking `getPortUrl` on a sandbox
12
+ * that does not expose one.
13
+ *
14
+ * The caller supplies the full human-readable message. Optional `harnessId`
15
+ * is recorded as structured context for tooling.
16
+ */
17
+ export class HarnessCapabilityUnsupportedError extends HarnessError {
18
+ private readonly [symbol] = true;
19
+
20
+ readonly harnessId?: string;
21
+
22
+ constructor({
23
+ message,
24
+ harnessId,
25
+ cause,
26
+ }: {
27
+ message: string;
28
+ harnessId?: string;
29
+ cause?: unknown;
30
+ }) {
31
+ super({ message, cause });
32
+ Object.defineProperty(this, 'name', { value: name });
33
+ this.harnessId = harnessId;
34
+ }
35
+
36
+ static isInstance(
37
+ error: unknown,
38
+ ): error is HarnessCapabilityUnsupportedError {
39
+ return AISDKError.hasMarker(error, marker);
40
+ }
41
+ }
@@ -0,0 +1,22 @@
1
+ import { AISDKError } from '@ai-sdk/provider';
2
+
3
+ const name = 'AI_HarnessError';
4
+ const marker = `vercel.ai.error.${name}`;
5
+ const symbol = Symbol.for(marker);
6
+
7
+ /**
8
+ * Base error type for failures originating in or signalled by a harness
9
+ * adapter. Specific failure modes (e.g. unsupported capability) extend this
10
+ * class.
11
+ */
12
+ export class HarnessError extends AISDKError {
13
+ private readonly [symbol] = true;
14
+
15
+ constructor({ message, cause }: { message: string; cause?: unknown }) {
16
+ super({ name, message, cause });
17
+ }
18
+
19
+ static isInstance(error: unknown): error is HarnessError {
20
+ return AISDKError.hasMarker(error, marker);
21
+ }
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './errors/harness-error';
2
+ export * from './errors/harness-capability-unsupported-error';
3
+ export type { HarnessV1SandboxProvider } from './v1/harness-v1-sandbox-provider';
4
+ export type {
5
+ HarnessV1NetworkPolicy,
6
+ HarnessV1NetworkSandboxSession,
7
+ } from './v1/harness-v1-network-sandbox-session';
@@ -0,0 +1,123 @@
1
+ import type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';
2
+
3
+ /**
4
+ * Network sandbox session returned by `HarnessV1SandboxProvider.createSession()`. The
5
+ * harness keeps this for the lifetime of a session. It is itself a
6
+ * {@link SandboxSession} (file I/O, exec, spawn) and adds the infra surface on
7
+ * top: port resolution, lifecycle, and network-policy mutation.
8
+ *
9
+ * Code that should only touch the filesystem and spawn processes receives the
10
+ * reduced view from {@link HarnessV1NetworkSandboxSession.restricted}, never the
11
+ * network sandbox session itself — so it cannot stop the sandbox or change its
12
+ * network policy.
13
+ */
14
+ export interface HarnessV1NetworkSandboxSession extends SandboxSession {
15
+ /**
16
+ * Stable identifier for the underlying sandbox resource. Used by the
17
+ * harness session manager as the durable lookup key for cross-process
18
+ * resume — the framework persists this on the resume payload so a future
19
+ * process can call `HarnessV1SandboxProvider.resume?({ sessionId })` and
20
+ * reach the same resource. Providers populate it from their native
21
+ * identifier (Vercel: the sandbox name; just-bash: a UUID minted at
22
+ * create time).
23
+ */
24
+ readonly id: string;
25
+
26
+ /**
27
+ * The sandbox's default working directory — the absolute path that
28
+ * `run`/`spawn` resolve relative commands against when no `workingDirectory`
29
+ * is given. Read from the live sandbox (it is provider-specific and
30
+ * configurable at create time: Vercel defaults to `/vercel/sandbox`,
31
+ * just-bash to `/home/user`), never hardcoded.
32
+ *
33
+ * The framework composes each session's working directory underneath this
34
+ * path (`<defaultWorkingDirectory>/<harnessId>-<sessionId>`) so adapters do
35
+ * not bake a provider-specific base into their own paths.
36
+ */
37
+ readonly defaultWorkingDirectory: string;
38
+
39
+ /** Ports the sandbox exposes; resolvable to public URLs via `getPortUrl`. */
40
+ readonly ports: ReadonlyArray<number>;
41
+
42
+ /**
43
+ * Resolve a publicly-reachable URL for a sandbox-exposed port. Bridge-backed
44
+ * adapters call this to open their WebSocket to the in-sandbox bridge.
45
+ */
46
+ readonly getPortUrl: (options: {
47
+ port: number;
48
+ protocol?: 'http' | 'https' | 'ws';
49
+ }) => PromiseLike<string>;
50
+
51
+ /** Stop the sandbox. Idempotent. */
52
+ readonly stop: () => PromiseLike<void>;
53
+
54
+ /**
55
+ * Destroy/delete the sandbox resource when supported. Optional because some
56
+ * providers only have a stop/dispose concept. Implementations must handle
57
+ * both a still-running sandbox and a previously stopped sandbox.
58
+ */
59
+ readonly destroy?: () => PromiseLike<void>;
60
+
61
+ /**
62
+ * Update the sandbox's outbound network policy. Optional — implementations
63
+ * without a local enforcement primitive (e.g. just-bash) omit this. Callers
64
+ * use optional-call (`sandboxSession.setNetworkPolicy?.(policy)`); a
65
+ * missing implementation is a no-op.
66
+ */
67
+ readonly setNetworkPolicy?: (
68
+ policy: HarnessV1NetworkPolicy,
69
+ ) => PromiseLike<void>;
70
+
71
+ /**
72
+ * Replace the set of ports exposed by the sandbox. Full-replacement
73
+ * semantics: ports omitted from the array are deregistered. Optional —
74
+ * implementations that cannot expose ports (e.g. just-bash) omit this.
75
+ */
76
+ readonly setPorts?: (
77
+ ports: ReadonlyArray<number>,
78
+ options?: { abortSignal?: AbortSignal },
79
+ ) => PromiseLike<void>;
80
+
81
+ /**
82
+ * Reduced view of this session, typed as the bare {@link SandboxSession}
83
+ * (file I/O, exec, spawn) — nothing that could stop the sandbox or change
84
+ * its network policy. Pass this to user-tool `execute()` calls and other
85
+ * code that must not reach the infra surface.
86
+ *
87
+ * The returned object points at exactly the same underlying sandbox
88
+ * resource as the network sandbox session it was produced from; it is only a
89
+ * narrower surface over the same resource, not a separate sandbox.
90
+ */
91
+ readonly restricted: () => SandboxSession;
92
+ }
93
+
94
+ /**
95
+ * Outbound network policy applied by the sandbox runtime.
96
+ *
97
+ * `'allow-all'` and `'deny-all'` are convenience presets. `'custom'` is an
98
+ * allow-list with an optional CIDR deny-list that takes precedence:
99
+ *
100
+ * - Reachable hosts are the union of `allowedHosts` and `allowedCIDRs`.
101
+ * - `deniedCIDRs` wins over both, useful for blocking cloud-metadata IPs while
102
+ * otherwise allowing broad access.
103
+ *
104
+ * The two `'custom'` branches share the same discriminator but each requires
105
+ * a different allow field. Specifying `'custom'` with only `deniedCIDRs`
106
+ * (deny-only) is rejected at compile time — functionally it would be
107
+ * equivalent to `'deny-all'`.
108
+ */
109
+ export type HarnessV1NetworkPolicy =
110
+ | { mode: 'allow-all' }
111
+ | { mode: 'deny-all' }
112
+ | {
113
+ mode: 'custom';
114
+ allowedHosts: ReadonlyArray<string>;
115
+ allowedCIDRs?: ReadonlyArray<string>;
116
+ deniedCIDRs?: ReadonlyArray<string>;
117
+ }
118
+ | {
119
+ mode: 'custom';
120
+ allowedHosts?: ReadonlyArray<string>;
121
+ allowedCIDRs: ReadonlyArray<string>;
122
+ deniedCIDRs?: ReadonlyArray<string>;
123
+ };
@@ -0,0 +1,76 @@
1
+ import type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';
2
+ import type { HarnessV1NetworkSandboxSession } from './harness-v1-network-sandbox-session';
3
+
4
+ /**
5
+ * Provider that produces network sandbox sessions for harness sessions. Lives at
6
+ * module scope as a stable, synchronous object — analogous to
7
+ * `LanguageModelV4` providers, no I/O performed at construction. The actual
8
+ * sandbox is created (or wrapped) when `HarnessAgent` calls `createSession()`.
9
+ */
10
+ export interface HarnessV1SandboxProvider {
11
+ readonly specificationVersion: 'harness-sandbox-v1';
12
+ readonly providerId: string;
13
+
14
+ /**
15
+ * Pool of ports the consumer reserved on a caller-provided sandbox for
16
+ * concurrent harness sessions. The session manager leases one port per
17
+ * session and releases on stop or destroy.
18
+ *
19
+ * Only meaningful when the provider wraps a caller-provided sandbox
20
+ * (the caller pre-declared the ports). In create-new modes the provider
21
+ * mints a fresh sandbox per session, so no leasing is needed; providers
22
+ * leave this undefined.
23
+ */
24
+ readonly bridgePorts?: ReadonlyArray<number>;
25
+
26
+ readonly createSession: (options?: {
27
+ /**
28
+ * Stable per-session identifier. When supplied, the provider names the
29
+ * underlying resource deterministically so a future call to `resume`
30
+ * (potentially from a different process) can find the same sandbox.
31
+ * Omitted from prewarm and other paths that don't need a resumable
32
+ * resource — in that case the provider falls back to its native
33
+ * auto-naming.
34
+ */
35
+ sessionId?: string;
36
+ abortSignal?: AbortSignal;
37
+ /**
38
+ * Stable identity for snapshot-based reuse. Providers that support
39
+ * persistence/snapshots use this as part of the persistent sandbox
40
+ * name; subsequent calls with the same identity resume from snapshot.
41
+ *
42
+ * Ignored when the provider is wrapping a caller-provided sandbox.
43
+ */
44
+ identity?: string;
45
+ /**
46
+ * Called exactly once per identity, on fresh creation. Snapshot-capable
47
+ * providers wire this into the platform's one-time-setup hook so the
48
+ * side effects are baked into the snapshot. Providers without snapshot
49
+ * support run it immediately after fresh create.
50
+ *
51
+ * Not called when the provider is wrapping a caller-provided sandbox
52
+ * (the caller owns the sandbox; the framework applies its own
53
+ * idempotent bootstrap post-create instead).
54
+ */
55
+ onFirstCreate?: (
56
+ session: SandboxSession,
57
+ opts: { abortSignal?: AbortSignal },
58
+ ) => Promise<void>;
59
+ }) => PromiseLike<HarnessV1NetworkSandboxSession>;
60
+
61
+ /**
62
+ * Reattach to an existing sandbox previously created with the same
63
+ * `sessionId`. Optional — providers that cannot rehydrate by id (e.g.
64
+ * just-bash) omit this; the harness throws
65
+ * `HarnessCapabilityUnsupportedError` when resume is attempted against
66
+ * them.
67
+ *
68
+ * The provider derives the sandbox identifier from `sessionId` using the
69
+ * same deterministic naming scheme it used in `createSession`. Returns a
70
+ * network sandbox session bound to the existing resource.
71
+ */
72
+ readonly resumeSession?: (options: {
73
+ sessionId: string;
74
+ abortSignal?: AbortSignal;
75
+ }) => PromiseLike<HarnessV1NetworkSandboxSession>;
76
+ }