@ai-sdk/sandbox-just-bash 1.0.123 → 1.0.126

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,32 @@
1
1
  # @ai-sdk/sandbox-just-bash
2
2
 
3
+ ## 1.0.126
4
+
5
+ ### Patch Changes
6
+
7
+ - 31742b9: feat(harness): refactor sandbox APIs for cleaner separation of concerns from harness layer, more intuitive DX regardless of how your sandbox is configured, and streamlined handling of sandbox templates/snapshots
8
+ - Updated dependencies [af9597b]
9
+ - Updated dependencies [31742b9]
10
+ - Updated dependencies [bc49f78]
11
+ - @ai-sdk/harness@1.0.126
12
+ - @ai-sdk/provider-utils@5.0.49
13
+
14
+ ## 1.0.125
15
+
16
+ ### Patch Changes
17
+
18
+ - 5482b08: fix(sandbox-just-bash): preserve command resolution when providing realpath on non-synchronous filesystems
19
+ - Updated dependencies [be877ff]
20
+ - @ai-sdk/provider-utils@5.0.48
21
+ - @ai-sdk/harness@1.0.125
22
+
23
+ ## 1.0.124
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [40231b6]
28
+ - @ai-sdk/harness@1.0.124
29
+
3
30
  ## 1.0.123
4
31
 
5
32
  ### Patch Changes
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  _This package is **experimental**._
4
4
 
5
- `HarnessV1SandboxProvider` implementation for [`just-bash`](https://github.com/vercel-labs/just-bash), an in-process JavaScript bash environment with a virtual filesystem. File operations and spawned processes share the same in-memory state.
5
+ Sandbox session implementation for [`just-bash`](https://github.com/vercel-labs/just-bash), an in-process JavaScript bash environment with a virtual filesystem.
6
6
 
7
- This provider does not expose ports, so it cannot be used with features that require actual network sandboxes. It is primarily useful for handing a local `Experimental_SandboxSession` to AI SDK tools that accept `experimental_sandbox`.
7
+ `just-bash` sandboxes do not expose ports, so they cannot be used with features that require actual network sandboxes.
8
8
 
9
9
  ## Setup
10
10
 
@@ -14,14 +14,14 @@ npm i @ai-sdk/sandbox-just-bash
14
14
 
15
15
  ## Usage
16
16
 
17
- The factory is synchronous. The returned provider is stable; the actual `just-bash` `Sandbox` is created on demand inside `provider.createSession()`.
17
+ Each creation produces a fresh in-memory sandbox and applies an optional harness template.
18
18
 
19
19
  ```ts
20
- import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash';
20
+ import { createJustBashNetworkSandboxSession } from '@ai-sdk/sandbox-just-bash';
21
21
 
22
- const justBashSandbox = createJustBashSandbox({ cwd: '/work' });
23
-
24
- const networkSandboxSession = await justBashSandbox.createSession();
22
+ const networkSandboxSession = await createJustBashNetworkSandboxSession({
23
+ cwd: '/work',
24
+ });
25
25
  const sandboxSession = networkSandboxSession.restricted();
26
26
 
27
27
  await sandboxSession.writeTextFile({ path: '/work/hello.txt', content: 'hi' });
@@ -30,18 +30,11 @@ const { stdout } = await sandboxSession.run({
30
30
  command: 'cat /work/hello.txt',
31
31
  });
32
32
  console.log(stdout); // "hi"
33
+ await networkSandboxSession.destroy();
33
34
  ```
34
35
 
35
- `networkSandboxSession.restricted()` is typed as `Experimental_SandboxSession` and is the surface to hand to user tools. The network sandbox session's `getPortEndpoint` throws (just-bash has no port story), `getPortUrl` remains as a deprecated compatibility wrapper, and `setNetworkPolicy` is omitted (no local enforcement primitive).
36
+ ### Use cases
36
37
 
37
- To wrap an already-created `just-bash` `Sandbox` (e.g. with a custom `fs`), pass it via `sandbox`. Install `just-bash` directly if your application imports `Sandbox`, `OverlayFs`, or other `just-bash` exports:
38
-
39
- ```ts
40
- import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash';
41
- import { OverlayFs, Sandbox } from 'just-bash';
42
-
43
- const overlay = new OverlayFs({ root: process.cwd() });
44
- const sandbox = createJustBashSandbox({
45
- sandbox: await Sandbox.create({ fs: overlay, cwd: overlay.getMountPoint() }),
46
- });
47
- ```
38
+ - Create a fresh in-memory session with `createJustBashNetworkSandboxSession()` and pass it to `HarnessAgent.createSession({ sandboxSession })`. An optional `sandboxId` only labels its wrapper and cannot enforce uniqueness.
39
+ - Take a network sandbox session's process-restricted subset with `networkSandboxSession.restricted()` and use it with AI SDK tools that accept `experimental_sandbox`.
40
+ - Adapt an existing native `Sandbox` with `createJustBashSandboxSessionFromNativeSandbox(nativeSandbox)` for AI SDK tools. `resumeJustBashNetworkSandboxSession()` reports that cross-process resume is unsupported.
package/dist/index.d.ts CHANGED
@@ -1,15 +1,13 @@
1
- import { HarnessV1SandboxProvider, HarnessV1NetworkSandboxSession } from '@ai-sdk/harness';
1
+ import { HarnessV1SandboxProvider, HarnessV1NetworkSandboxSession, HarnessV1SandboxSessionCreateOptions, HarnessV1SandboxSessionResumeOptions } from '@ai-sdk/harness';
2
2
  import { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
3
3
  import { Sandbox } from 'just-bash';
4
4
 
5
+ type JustBashSandboxCreateParams$1 = NonNullable<Parameters<typeof Sandbox.create>[0]>;
6
+ type JustBashCreationSettings$1 = JustBashSandboxCreateParams$1 & {
7
+ sandbox?: never;
8
+ };
5
9
  /**
6
- * Parameters forwarded to `just-bash`'s `Sandbox.create` when creating a
7
- * sandbox from scratch. Aliased directly from the underlying SDK so the full
8
- * surface is available without us re-declaring it.
9
- */
10
- type JustBashSandboxCreateParams = NonNullable<Parameters<typeof Sandbox.create>[0]>;
11
- /**
12
- * Settings for {@link createJustBashSandbox}. Two mutually-exclusive shapes:
10
+ * Settings for `createJustBashSandbox`. Two mutually-exclusive shapes:
13
11
  *
14
12
  * - `{ sandbox }` — wrap an already-created `just-bash` `Sandbox`. The caller
15
13
  * owns its lifecycle.
@@ -19,12 +17,13 @@ type JustBashSandboxCreateParams = NonNullable<Parameters<typeof Sandbox.create>
19
17
  * just-bash has no port exposure and no snapshot mechanism, so image
20
18
  * management is a per-call no-op: if an adapter declares a bootstrap recipe
21
19
  * the provider runs it once on the freshly-created sandbox before returning.
20
+ *
21
+ * @deprecated Use `JustBashNetworkSandboxSessionCreateOptions` for new sessions.
22
22
  */
23
23
  type JustBashSandboxSettings = {
24
24
  sandbox: Sandbox;
25
- } | (JustBashSandboxCreateParams & {
26
- sandbox?: never;
27
- });
25
+ } | JustBashCreationSettings$1;
26
+ /** @deprecated Use `createJustBashNetworkSandboxSession` instead. */
28
27
  declare function createJustBashSandbox(settings?: JustBashSandboxSettings): HarnessV1SandboxProvider;
29
28
  /**
30
29
  * `HarnessV1SandboxProvider` implementation backed by `just-bash`. Useful for
@@ -35,6 +34,7 @@ declare function createJustBashSandbox(settings?: JustBashSandboxSettings): Harn
35
34
  * Note: just-bash cannot expose ports, so bridge-backed harness adapters
36
35
  * (claude-code, codex) will reject this provider at start.
37
36
  */
37
+ /** @deprecated Use `createJustBashNetworkSandboxSession` instead. */
38
38
  declare class JustBashSandboxProvider implements HarnessV1SandboxProvider {
39
39
  private readonly settings;
40
40
  readonly specificationVersion: "harness-sandbox-v1";
@@ -50,4 +50,30 @@ declare class JustBashSandboxProvider implements HarnessV1SandboxProvider {
50
50
  }) => Promise<HarnessV1NetworkSandboxSession>;
51
51
  }
52
52
 
53
- export { JustBashSandboxProvider, type JustBashSandboxSettings, createJustBashSandbox };
53
+ /**
54
+ * Flattens an intersection of object types into a single object type so the
55
+ * resolved shape displays as its named properties rather than a chain of
56
+ * `A & B & C`.
57
+ */
58
+ type Prettify<T> = {
59
+ [K in keyof T]: T[K];
60
+ } & {};
61
+ type JustBashNativeSandboxSession = Sandbox;
62
+ /**
63
+ * Parameters forwarded to `just-bash`'s `Sandbox.create` when creating a
64
+ * sandbox from scratch. Aliased directly from the underlying SDK so the full
65
+ * surface is available without us re-declaring it.
66
+ */
67
+ type JustBashSandboxCreateParams = NonNullable<Parameters<typeof Sandbox.create>[0]>;
68
+ type JustBashCreationSettings = Omit<JustBashSandboxCreateParams, 'sandbox'>;
69
+ type JustBashNetworkSandboxSessionCreateOptions = Prettify<HarnessV1SandboxSessionCreateOptions<JustBashCreationSettings> & {
70
+ sandbox?: never;
71
+ }>;
72
+ type JustBashLookupSettings = Record<never, never>;
73
+ type JustBashNetworkSandboxSessionResumeOptions = HarnessV1SandboxSessionResumeOptions<JustBashLookupSettings>;
74
+ declare function createJustBashNetworkSandboxSession(options?: JustBashNetworkSandboxSessionCreateOptions): Promise<HarnessV1NetworkSandboxSession>;
75
+ declare function resumeJustBashNetworkSandboxSession(options: JustBashNetworkSandboxSessionResumeOptions): Promise<HarnessV1NetworkSandboxSession>;
76
+ declare function createJustBashSandboxSessionFromNativeSandbox(nativeSandbox: JustBashNativeSandboxSession): Experimental_SandboxSession;
77
+ declare function createJustBashNetworkSandboxSessionFromNativeSandbox(nativeSandbox: JustBashNativeSandboxSession): HarnessV1NetworkSandboxSession;
78
+
79
+ export { type JustBashNativeSandboxSession, type JustBashNetworkSandboxSessionCreateOptions, type JustBashNetworkSandboxSessionResumeOptions, JustBashSandboxProvider, type JustBashSandboxSettings, createJustBashNetworkSandboxSession, createJustBashNetworkSandboxSessionFromNativeSandbox, createJustBashSandbox, createJustBashSandboxSessionFromNativeSandbox, resumeJustBashNetworkSandboxSession };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // src/just-bash-sandbox.ts
1
+ // src/just-bash-legacy-sandbox-provider.ts
2
2
  import { Sandbox } from "just-bash";
3
3
 
4
4
  // src/just-bash-network-sandbox-session.ts
@@ -12,6 +12,103 @@ import { isAbsolute, posix } from "path";
12
12
  import {
13
13
  extractLines
14
14
  } from "@ai-sdk/provider-utils";
15
+
16
+ // src/utils.ts
17
+ import { defineCommand } from "just-bash";
18
+ var preparations = /* @__PURE__ */ new WeakMap();
19
+ async function ensureRealpath(sandbox) {
20
+ let preparation = preparations.get(sandbox);
21
+ if (preparation == null) {
22
+ preparation = prepareRealpath(sandbox);
23
+ preparations.set(sandbox, preparation);
24
+ }
25
+ try {
26
+ await preparation;
27
+ } catch (error) {
28
+ if (preparations.get(sandbox) === preparation) {
29
+ preparations.delete(sandbox);
30
+ }
31
+ throw error;
32
+ }
33
+ }
34
+ async function prepareRealpath(sandbox) {
35
+ const realpathType = await sandbox.bashEnvInstance.exec("type realpath");
36
+ if (realpathType.exitCode === 0) {
37
+ return;
38
+ }
39
+ sandbox.bashEnvInstance.registerCommand(
40
+ defineCommand("realpath", executeRealpath)
41
+ );
42
+ }
43
+ async function executeRealpath(args, context) {
44
+ if (args.length === 0 || args[0] === "") {
45
+ return {
46
+ stdout: "",
47
+ stderr: "realpath: missing operand\n",
48
+ exitCode: 1
49
+ };
50
+ }
51
+ let pending = args[0].startsWith("/") ? args[0] : `${context.cwd}/${args[0]}`;
52
+ let resolved = "";
53
+ let linkCount = 0;
54
+ while (pending.length > 0) {
55
+ pending = pending.replace(/^\//, "");
56
+ if (pending.length === 0) {
57
+ break;
58
+ }
59
+ const separatorIndex = pending.indexOf("/");
60
+ const component = separatorIndex === -1 ? pending : pending.slice(0, separatorIndex);
61
+ pending = separatorIndex === -1 ? "" : pending.slice(separatorIndex + 1);
62
+ if (component === "" || component === ".") {
63
+ continue;
64
+ }
65
+ if (component === "..") {
66
+ resolved = resolved.slice(0, resolved.lastIndexOf("/"));
67
+ continue;
68
+ }
69
+ const candidate = `${resolved}/${component}`;
70
+ let isSymbolicLink = false;
71
+ try {
72
+ isSymbolicLink = (await context.fs.lstat(candidate)).isSymbolicLink;
73
+ } catch (error) {
74
+ if (!isFileNotFoundError(error)) {
75
+ throw error;
76
+ }
77
+ }
78
+ if (!isSymbolicLink) {
79
+ resolved = candidate;
80
+ continue;
81
+ }
82
+ linkCount += 1;
83
+ if (linkCount > 64) {
84
+ return { stdout: "", stderr: "", exitCode: 1 };
85
+ }
86
+ let target;
87
+ try {
88
+ target = await context.fs.readlink(candidate);
89
+ } catch {
90
+ return { stdout: "", stderr: "", exitCode: 1 };
91
+ }
92
+ const remainder = pending.length > 0 ? `/${pending}` : "";
93
+ pending = target.startsWith("/") ? `${target}${remainder}` : `${candidate.slice(0, candidate.lastIndexOf("/"))}/${target}${remainder}`;
94
+ resolved = "";
95
+ }
96
+ return {
97
+ stdout: `${resolved || "/"}
98
+ `,
99
+ stderr: "",
100
+ exitCode: 0
101
+ };
102
+ }
103
+ function isFileNotFoundError(error) {
104
+ if (error == null || typeof error !== "object") return false;
105
+ const code = error.code;
106
+ if (code === "ENOENT") return true;
107
+ const message = error.message;
108
+ return typeof message === "string" && /no such file|not found|ENOENT/i.test(message);
109
+ }
110
+
111
+ // src/just-bash-sandbox-session.ts
15
112
  var JustBashSandboxSession = class {
16
113
  constructor(sandbox) {
17
114
  this.sandbox = sandbox;
@@ -32,7 +129,9 @@ var JustBashSandboxSession = class {
32
129
  env,
33
130
  abortSignal
34
131
  }) {
35
- abortSignal == null ? void 0 : abortSignal.throwIfAborted();
132
+ abortSignal?.throwIfAborted();
133
+ await ensureRealpath(this.sandbox);
134
+ abortSignal?.throwIfAborted();
36
135
  const finished = await this.sandbox.runCommand({
37
136
  cmd: "bash",
38
137
  args: createBashArgs(command, env),
@@ -55,7 +154,9 @@ var JustBashSandboxSession = class {
55
154
  env,
56
155
  abortSignal
57
156
  }) {
58
- abortSignal == null ? void 0 : abortSignal.throwIfAborted();
157
+ abortSignal?.throwIfAborted();
158
+ await ensureRealpath(this.sandbox);
159
+ abortSignal?.throwIfAborted();
59
160
  const live = await this.sandbox.runCommand({
60
161
  cmd: "bash",
61
162
  args: createBashArgs(command, env),
@@ -77,13 +178,13 @@ var JustBashSandboxSession = class {
77
178
  path,
78
179
  abortSignal
79
180
  }) {
80
- abortSignal == null ? void 0 : abortSignal.throwIfAborted();
181
+ abortSignal?.throwIfAborted();
81
182
  const resolved = this.resolvePath(path);
82
183
  try {
83
184
  const bytes = await this.sandbox.bashEnvInstance.fs.readFileBuffer(resolved);
84
185
  return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
85
186
  } catch (error) {
86
- if (isFileNotFoundError(error)) return null;
187
+ if (isFileNotFoundError2(error)) return null;
87
188
  throw error;
88
189
  }
89
190
  }
@@ -112,7 +213,7 @@ var JustBashSandboxSession = class {
112
213
  content,
113
214
  abortSignal
114
215
  }) {
115
- abortSignal == null ? void 0 : abortSignal.throwIfAborted();
216
+ abortSignal?.throwIfAborted();
116
217
  const resolved = this.resolvePath(path);
117
218
  await this.sandbox.bashEnvInstance.fs.writeFile(resolved, content);
118
219
  }
@@ -161,28 +262,26 @@ function createSandboxProcess(command, abortSignal) {
161
262
  }
162
263
  });
163
264
  const drained = (async () => {
164
- var _a, _b, _c, _d;
165
265
  try {
166
266
  for await (const message of command.logs()) {
167
267
  const target = message.type === "stdout" ? controllers.stdout : controllers.stderr;
168
- target == null ? void 0 : target.enqueue(encoder.encode(message.data));
268
+ target?.enqueue(encoder.encode(message.data));
169
269
  }
170
- (_a = controllers.stdout) == null ? void 0 : _a.close();
171
- (_b = controllers.stderr) == null ? void 0 : _b.close();
270
+ controllers.stdout?.close();
271
+ controllers.stderr?.close();
172
272
  } catch (error) {
173
- (_c = controllers.stdout) == null ? void 0 : _c.error(error);
174
- (_d = controllers.stderr) == null ? void 0 : _d.error(error);
273
+ controllers.stdout?.error(error);
274
+ controllers.stderr?.error(error);
175
275
  }
176
276
  })();
177
277
  return {
178
278
  stdout,
179
279
  stderr,
180
280
  async wait() {
181
- var _a;
182
281
  const finished = await command.wait();
183
282
  await drained;
184
- if (abortSignal == null ? void 0 : abortSignal.aborted) {
185
- throw (_a = abortSignal.reason) != null ? _a : new DOMException("Aborted", "AbortError");
283
+ if (abortSignal?.aborted) {
284
+ throw abortSignal.reason ?? new DOMException("Aborted", "AbortError");
186
285
  }
187
286
  return { exitCode: finished.exitCode };
188
287
  },
@@ -219,7 +318,7 @@ async function collectStream(stream) {
219
318
  }
220
319
  return out;
221
320
  }
222
- function isFileNotFoundError(error) {
321
+ function isFileNotFoundError2(error) {
223
322
  if (error == null || typeof error !== "object") return false;
224
323
  const code = error.code;
225
324
  if (code === "ENOENT") return true;
@@ -246,11 +345,12 @@ var JustBashNetworkSandboxSession = class extends JustBashSandboxSession {
246
345
  return (await this.getPortEndpoint(options)).url;
247
346
  };
248
347
  this.stop = async () => {
348
+ await this.sandbox.stop();
249
349
  };
250
350
  this.destroy = async () => {
251
351
  await this.stop();
252
352
  };
253
- this.id = randomUUID();
353
+ this.id = input.sandboxId ?? randomUUID();
254
354
  this.defaultWorkingDirectory = input.sandbox.bashEnvInstance.getCwd();
255
355
  }
256
356
  restricted() {
@@ -258,77 +358,12 @@ var JustBashNetworkSandboxSession = class extends JustBashSandboxSession {
258
358
  }
259
359
  };
260
360
 
261
- // src/just-bash-sandbox.ts
361
+ // src/just-bash-legacy-sandbox-provider.ts
262
362
  var JUST_BASH_PROVIDER_ID2 = "just-bash-sandbox";
263
- var REALPATH_PATH = "/usr/bin/realpath";
264
- var REALPATH_SCRIPT = `#!/usr/bin/env bash
265
- pending=\${1:?}
266
- resolved=
267
- link_count=0
268
- link_marker=__AI_SDK_REALPATH_LINK_END__
269
- case "$pending" in
270
- /*) ;;
271
- *) pending=$PWD/$pending ;;
272
- esac
273
- while [ -n "$pending" ]; do
274
- pending=\${pending#/}
275
- [ -n "$pending" ] || break
276
- component=\${pending%%/*}
277
- if [ "$pending" = "$component" ]; then
278
- pending=
279
- else
280
- pending=\${pending#*/}
281
- fi
282
- case "$component" in
283
- ""|.) continue ;;
284
- ..)
285
- resolved=\${resolved%/*}
286
- continue
287
- ;;
288
- esac
289
- candidate=$resolved/$component
290
- if [ -L "$candidate" ]; then
291
- link_count=$((link_count + 1))
292
- [ "$link_count" -le 64 ] || exit 1
293
- link_target_framed=$(readlink "$candidate"; readlink_status=$?; printf '%s' "$link_marker"; exit "$readlink_status")
294
- readlink_status=$?
295
- [ "$readlink_status" -eq 0 ] || exit 1
296
- target=\${link_target_framed%$link_marker}
297
- target=\${target%$'
298
- '}
299
- case "$target" in
300
- /*) pending=$target\${pending:+/$pending} ;;
301
- *) pending=\${candidate%/*}/$target\${pending:+/$pending} ;;
302
- esac
303
- resolved=
304
- else
305
- resolved=$candidate
306
- fi
307
- done
308
- printf '%s
309
- ' "\${resolved:-/}"
310
- `;
311
- async function ensureRealpath(sandbox) {
312
- const fs = sandbox.bashEnvInstance.fs;
313
- try {
314
- await fs.lstat(REALPATH_PATH);
315
- return;
316
- } catch (error) {
317
- if (!isFileNotFoundError2(error)) {
318
- throw error;
319
- }
320
- }
321
- await sandbox.writeFiles({ [REALPATH_PATH]: REALPATH_SCRIPT });
322
- await fs.chmod(REALPATH_PATH, 493);
323
- }
324
- function isFileNotFoundError2(error) {
325
- if (error == null || typeof error !== "object") return false;
326
- const code = error.code;
327
- if (code === "ENOENT") return true;
328
- const message = error.message;
329
- return typeof message === "string" && /no such file|not found|ENOENT/i.test(message);
330
- }
331
363
  function createJustBashSandbox(settings = {}) {
364
+ console.warn(
365
+ "createJustBashSandbox is deprecated. Use createJustBashNetworkSandboxSession and HarnessAgent.createSession({ sandboxSession }) instead."
366
+ );
332
367
  return new JustBashSandboxProvider(settings);
333
368
  }
334
369
  var JustBashSandboxProvider = class {
@@ -337,25 +372,80 @@ var JustBashSandboxProvider = class {
337
372
  this.specificationVersion = "harness-sandbox-v1";
338
373
  this.providerId = JUST_BASH_PROVIDER_ID2;
339
374
  this.createSession = async (options) => {
340
- var _a;
341
- (_a = options == null ? void 0 : options.abortSignal) == null ? void 0 : _a.throwIfAborted();
375
+ options?.abortSignal?.throwIfAborted();
342
376
  const ownsLifecycle = !("sandbox" in this.settings && this.settings.sandbox);
343
- const sandbox = ownsLifecycle ? await Sandbox.create(this.settings) : this.settings.sandbox;
377
+ const sandbox = ownsLifecycle ? await Sandbox.create(
378
+ this.settings
379
+ ) : this.settings.sandbox;
344
380
  await ensureRealpath(sandbox);
345
381
  const sandboxSession = new JustBashNetworkSandboxSession({
346
382
  sandbox
347
383
  });
348
- if ((options == null ? void 0 : options.onFirstCreate) != null) {
384
+ if (options?.onFirstCreate != null) {
349
385
  await options.onFirstCreate(sandboxSession.restricted(), {
350
- abortSignal: options == null ? void 0 : options.abortSignal
386
+ abortSignal: options?.abortSignal
351
387
  });
352
388
  }
353
389
  return sandboxSession;
354
390
  };
391
+ console.warn(
392
+ "JustBashSandboxProvider is deprecated. Use createJustBashNetworkSandboxSession instead."
393
+ );
355
394
  }
356
395
  };
396
+
397
+ // src/just-bash-sandbox.ts
398
+ import {
399
+ HarnessCapabilityUnsupportedError as HarnessCapabilityUnsupportedError2
400
+ } from "@ai-sdk/harness";
401
+ import { Sandbox as Sandbox2 } from "just-bash";
402
+ async function createJustBashNetworkSandboxSession(options = {}) {
403
+ if ("sandbox" in options) {
404
+ throw new Error(
405
+ "createJustBashNetworkSandboxSession: use createJustBashNetworkSandboxSessionFromNativeSandbox for an existing sandbox."
406
+ );
407
+ }
408
+ const {
409
+ template,
410
+ abortSignal,
411
+ sandboxId,
412
+ sandbox: _sandbox,
413
+ ...nativeOptions
414
+ } = options;
415
+ abortSignal?.throwIfAborted();
416
+ const nativeSandbox = await Sandbox2.create(nativeOptions);
417
+ try {
418
+ await ensureRealpath(nativeSandbox);
419
+ const session = new JustBashNetworkSandboxSession({
420
+ sandbox: nativeSandbox,
421
+ sandboxId
422
+ });
423
+ await template?.prepare({ session: session.restricted(), abortSignal });
424
+ return session;
425
+ } catch (error) {
426
+ await nativeSandbox.stop().catch(() => {
427
+ });
428
+ throw error;
429
+ }
430
+ }
431
+ async function resumeJustBashNetworkSandboxSession(options) {
432
+ options.abortSignal?.throwIfAborted();
433
+ throw new HarnessCapabilityUnsupportedError2({
434
+ message: "just-bash sandboxes run in-process and cannot be resumed by sandboxId."
435
+ });
436
+ }
437
+ function createJustBashSandboxSessionFromNativeSandbox(nativeSandbox) {
438
+ return new JustBashSandboxSession(nativeSandbox);
439
+ }
440
+ function createJustBashNetworkSandboxSessionFromNativeSandbox(nativeSandbox) {
441
+ return new JustBashNetworkSandboxSession({ sandbox: nativeSandbox });
442
+ }
357
443
  export {
358
444
  JustBashSandboxProvider,
359
- createJustBashSandbox
445
+ createJustBashNetworkSandboxSession,
446
+ createJustBashNetworkSandboxSessionFromNativeSandbox,
447
+ createJustBashSandbox,
448
+ createJustBashSandboxSessionFromNativeSandbox,
449
+ resumeJustBashNetworkSandboxSession
360
450
  };
361
451
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/just-bash-sandbox.ts","../src/just-bash-network-sandbox-session.ts","../src/just-bash-sandbox-session.ts"],"sourcesContent":["import type {\n HarnessV1NetworkSandboxSession,\n HarnessV1SandboxProvider,\n} from '@ai-sdk/harness';\nimport type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';\nimport { Sandbox } from 'just-bash';\nimport { JustBashNetworkSandboxSession } from './just-bash-network-sandbox-session';\n\n/**\n * Parameters forwarded to `just-bash`'s `Sandbox.create` when creating a\n * sandbox from scratch. Aliased directly from the underlying SDK so the full\n * surface is available without us re-declaring it.\n */\ntype JustBashSandboxCreateParams = NonNullable<\n Parameters<typeof Sandbox.create>[0]\n>;\n\n/**\n * Settings for {@link createJustBashSandbox}. Two mutually-exclusive shapes:\n *\n * - `{ sandbox }` — wrap an already-created `just-bash` `Sandbox`. The caller\n * owns its lifecycle.\n * - {@link JustBashSandboxCreateParams} fields — provider calls\n * `Sandbox.create(settings)` on every `createSession()`.\n *\n * just-bash has no port exposure and no snapshot mechanism, so image\n * management is a per-call no-op: if an adapter declares a bootstrap recipe\n * the provider runs it once on the freshly-created sandbox before returning.\n */\nexport type JustBashSandboxSettings =\n | { sandbox: Sandbox }\n | (JustBashSandboxCreateParams & { sandbox?: never });\n\nconst JUST_BASH_PROVIDER_ID = 'just-bash-sandbox';\nconst REALPATH_PATH = '/usr/bin/realpath';\nconst REALPATH_SCRIPT = `#!/usr/bin/env bash\npending=\\${1:?}\nresolved=\nlink_count=0\nlink_marker=__AI_SDK_REALPATH_LINK_END__\ncase \"$pending\" in\n /*) ;;\n *) pending=$PWD/$pending ;;\nesac\nwhile [ -n \"$pending\" ]; do\n pending=\\${pending#/}\n [ -n \"$pending\" ] || break\n component=\\${pending%%/*}\n if [ \"$pending\" = \"$component\" ]; then\n pending=\n else\n pending=\\${pending#*/}\n fi\n case \"$component\" in\n \"\"|.) continue ;;\n ..)\n resolved=\\${resolved%/*}\n continue\n ;;\n esac\n candidate=$resolved/$component\n if [ -L \"$candidate\" ]; then\n link_count=$((link_count + 1))\n [ \"$link_count\" -le 64 ] || exit 1\n link_target_framed=$(readlink \"$candidate\"; readlink_status=$?; printf '%s' \"$link_marker\"; exit \"$readlink_status\")\n readlink_status=$?\n [ \"$readlink_status\" -eq 0 ] || exit 1\n target=\\${link_target_framed%$link_marker}\n target=\\${target%$'\\n'}\n case \"$target\" in\n /*) pending=$target\\${pending:+/$pending} ;;\n *) pending=\\${candidate%/*}/$target\\${pending:+/$pending} ;;\n esac\n resolved=\n else\n resolved=$candidate\n fi\ndone\nprintf '%s\\n' \"\\${resolved:-/}\"\n`;\n\nasync function ensureRealpath(sandbox: Sandbox): Promise<void> {\n const fs = sandbox.bashEnvInstance.fs;\n try {\n await fs.lstat(REALPATH_PATH);\n return;\n } catch (error) {\n if (!isFileNotFoundError(error)) {\n throw error;\n }\n }\n\n await sandbox.writeFiles({ [REALPATH_PATH]: REALPATH_SCRIPT });\n await fs.chmod(REALPATH_PATH, 0o755);\n}\n\nfunction isFileNotFoundError(error: unknown): boolean {\n if (error == null || typeof error !== 'object') return false;\n const code = (error as { code?: unknown }).code;\n if (code === 'ENOENT') return true;\n const message = (error as { message?: unknown }).message;\n return (\n typeof message === 'string' &&\n /no such file|not found|ENOENT/i.test(message)\n );\n}\n\nexport function createJustBashSandbox(\n settings: JustBashSandboxSettings = {} as JustBashSandboxSettings,\n): HarnessV1SandboxProvider {\n return new JustBashSandboxProvider(settings);\n}\n\n/**\n * `HarnessV1SandboxProvider` implementation backed by `just-bash`. Useful for\n * non-bridge harness flows and for handing a local `Experimental_SandboxSession`\n * to AI SDK tools — use `provider.createSession()` then\n * `sandboxSession.restricted()` to get the latter.\n *\n * Note: just-bash cannot expose ports, so bridge-backed harness adapters\n * (claude-code, codex) will reject this provider at start.\n */\nexport class JustBashSandboxProvider implements HarnessV1SandboxProvider {\n readonly specificationVersion = 'harness-sandbox-v1' as const;\n readonly providerId = JUST_BASH_PROVIDER_ID;\n\n constructor(private readonly settings: JustBashSandboxSettings) {}\n\n createSession = async (options?: {\n sessionId?: string;\n abortSignal?: AbortSignal;\n identity?: string;\n onFirstCreate?: (\n session: SandboxSession,\n opts: { abortSignal?: AbortSignal },\n ) => Promise<void>;\n }): Promise<HarnessV1NetworkSandboxSession> => {\n options?.abortSignal?.throwIfAborted();\n\n const ownsLifecycle = !(\n 'sandbox' in this.settings && this.settings.sandbox\n );\n const sandbox = ownsLifecycle\n ? await Sandbox.create(this.settings as JustBashSandboxCreateParams)\n : this.settings.sandbox;\n await ensureRealpath(sandbox);\n const sandboxSession = new JustBashNetworkSandboxSession({\n sandbox,\n });\n\n if (options?.onFirstCreate != null) {\n await options.onFirstCreate(sandboxSession.restricted(), {\n abortSignal: options?.abortSignal,\n });\n }\n\n return sandboxSession;\n };\n}\n","import {\n HarnessCapabilityUnsupportedError,\n type HarnessV1NetworkSandboxSession,\n type HarnessV1PortEndpoint,\n} from '@ai-sdk/harness';\nimport type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';\nimport { randomUUID } from 'node:crypto';\nimport type { Sandbox } from 'just-bash';\nimport { JustBashSandboxSession } from './just-bash-sandbox-session';\n\nconst JUST_BASH_PROVIDER_ID = 'just-bash-sandbox';\n\n/**\n * `HarnessV1NetworkSandboxSession` backed by a `just-bash` `Sandbox`. It extends\n * {@link JustBashSandboxSession} with the infra surface. Exposes no ports —\n * there is no network namespace and no way to publish an HTTP endpoint\n * reachable from outside the host process. Bridge-backed harness adapters that\n * need `getPortEndpoint` will fail with `HarnessCapabilityUnsupportedError` at\n * start.\n *\n * Network policy is not implementable locally either — `setNetworkPolicy` is\n * omitted.\n */\nexport class JustBashNetworkSandboxSession\n extends JustBashSandboxSession\n implements HarnessV1NetworkSandboxSession\n{\n /**\n * Minted at construct time. just-bash has no native identifier and no\n * cross-process reattach surface; the value exists only to satisfy the\n * `HarnessV1NetworkSandboxSession.id` contract.\n */\n readonly id: string;\n readonly defaultWorkingDirectory: string;\n\n constructor(input: { sandbox: Sandbox }) {\n super(input.sandbox);\n this.id = randomUUID();\n this.defaultWorkingDirectory = input.sandbox.bashEnvInstance.getCwd();\n }\n\n readonly ports: ReadonlyArray<number> = [];\n\n restricted(): SandboxSession {\n return new JustBashSandboxSession(this.sandbox);\n }\n\n getPortEndpoint = async (_options: {\n port: number;\n protocol?: 'http' | 'https' | 'ws';\n }): Promise<HarnessV1PortEndpoint> => {\n throw new HarnessCapabilityUnsupportedError({\n harnessId: JUST_BASH_PROVIDER_ID,\n message:\n 'just-bash sandboxes run in-process and cannot expose a port URL. ' +\n 'Use a hosted sandbox (e.g. @ai-sdk/sandbox-vercel) for bridge-backed harness adapters.',\n });\n };\n\n /**\n * @deprecated Use `getPortEndpoint` instead.\n */\n getPortUrl = async (options: {\n port: number;\n protocol?: 'http' | 'https' | 'ws';\n }): Promise<string> => {\n return (await this.getPortEndpoint(options)).url;\n };\n\n stop = async (): Promise<void> => {\n // just-bash has no explicit shutdown; the sandbox is garbage-collected\n // along with its in-memory filesystem once references drop.\n };\n\n destroy = async (): Promise<void> => {\n await this.stop();\n };\n}\n","import { isAbsolute, posix } from 'node:path';\nimport {\n extractLines,\n type Experimental_SandboxSession,\n type Experimental_SandboxProcess,\n} from '@ai-sdk/provider-utils';\nimport type { Sandbox, SandboxCommand } from 'just-bash';\n\n/**\n * `Experimental_SandboxSession` implementation backed by a `just-bash`\n * `Sandbox`. File operations and spawned processes share the same in-memory\n * filesystem, so a bridge process spawned via `spawn` can read files the host\n * wrote via `writeTextFile` (and vice versa).\n *\n * This is the tool-safe surface returned by\n * `JustBashNetworkSandboxSession.restricted()` — not constructed directly by\n * consumers.\n */\nexport class JustBashSandboxSession implements Experimental_SandboxSession {\n constructor(protected readonly sandbox: Sandbox) {}\n\n get description(): string {\n return [\n 'just-bash JavaScript bash environment with a virtual filesystem.',\n 'Filesystem changes and spawned processes share the same in-memory state.',\n `Current working directory: ${this.sandbox.bashEnvInstance.getCwd()}`,\n ].join('\\n');\n }\n\n private resolvePath(path: string): string {\n return isAbsolute(path)\n ? path\n : posix.join(this.sandbox.bashEnvInstance.getCwd(), path);\n }\n\n async run({\n command,\n workingDirectory,\n env,\n abortSignal,\n }: {\n command: string;\n workingDirectory?: string;\n env?: Record<string, string>;\n abortSignal?: AbortSignal;\n }): Promise<{ exitCode: number; stdout: string; stderr: string }> {\n abortSignal?.throwIfAborted();\n\n const finished = await this.sandbox.runCommand({\n cmd: 'bash',\n args: createBashArgs(command, env),\n ...(workingDirectory !== undefined ? { cwd: workingDirectory } : {}),\n ...(abortSignal !== undefined ? { signal: abortSignal } : {}),\n });\n\n const [stdout, stderr] = await Promise.all([\n finished.stdout(),\n finished.stderr(),\n ]);\n\n return {\n exitCode: finished.exitCode,\n stdout,\n stderr,\n };\n }\n\n async spawn({\n command,\n workingDirectory,\n env,\n abortSignal,\n }: {\n command: string;\n workingDirectory?: string;\n env?: Record<string, string>;\n abortSignal?: AbortSignal;\n }): Promise<Experimental_SandboxProcess> {\n abortSignal?.throwIfAborted();\n\n const live = await this.sandbox.runCommand({\n cmd: 'bash',\n args: createBashArgs(command, env),\n detached: true,\n ...(workingDirectory !== undefined ? { cwd: workingDirectory } : {}),\n ...(abortSignal !== undefined ? { signal: abortSignal } : {}),\n });\n\n return createSandboxProcess(live, abortSignal);\n }\n\n async readFile({\n path,\n abortSignal,\n }: {\n path: string;\n abortSignal?: AbortSignal;\n }): Promise<ReadableStream<Uint8Array> | null> {\n const bytes = await this.readBinaryFile({ path, abortSignal });\n if (bytes == null) return null;\n return bytesToStream(bytes);\n }\n\n async readBinaryFile({\n path,\n abortSignal,\n }: {\n path: string;\n abortSignal?: AbortSignal;\n }): Promise<Uint8Array | null> {\n abortSignal?.throwIfAborted();\n const resolved = this.resolvePath(path);\n try {\n const bytes =\n await this.sandbox.bashEnvInstance.fs.readFileBuffer(resolved);\n return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n } catch (error) {\n if (isFileNotFoundError(error)) return null;\n throw error;\n }\n }\n\n async readTextFile({\n path,\n encoding = 'utf-8',\n startLine,\n endLine,\n abortSignal,\n }: {\n path: string;\n encoding?: string;\n startLine?: number;\n endLine?: number;\n abortSignal?: AbortSignal;\n }): Promise<string | null> {\n const bytes = await this.readBinaryFile({ path, abortSignal });\n if (bytes == null) return null;\n const text = Buffer.from(bytes).toString(encoding as BufferEncoding);\n return extractLines({ text, startLine, endLine });\n }\n\n async writeFile({\n path,\n content,\n abortSignal,\n }: {\n path: string;\n content: ReadableStream<Uint8Array>;\n abortSignal?: AbortSignal;\n }): Promise<void> {\n const bytes = await collectStream(content);\n await this.writeBinaryFile({ path, content: bytes, abortSignal });\n }\n\n async writeBinaryFile({\n path,\n content,\n abortSignal,\n }: {\n path: string;\n content: Uint8Array;\n abortSignal?: AbortSignal;\n }): Promise<void> {\n abortSignal?.throwIfAborted();\n const resolved = this.resolvePath(path);\n await this.sandbox.bashEnvInstance.fs.writeFile(resolved, content);\n }\n\n async writeTextFile({\n path,\n content,\n encoding = 'utf-8',\n abortSignal,\n }: {\n path: string;\n content: string;\n encoding?: string;\n abortSignal?: AbortSignal;\n }): Promise<void> {\n const buffer = Buffer.from(content, encoding as BufferEncoding);\n await this.writeBinaryFile({\n path,\n content: new Uint8Array(\n buffer.buffer,\n buffer.byteOffset,\n buffer.byteLength,\n ),\n abortSignal,\n });\n }\n}\n\nfunction createBashArgs(\n command: string,\n env: Record<string, string> | undefined,\n): string[] {\n // just-bash applies runCommand's env to the outer execution, but its `bash`\n // command only inherits exported variables. Export assignments passed as\n // positional arguments so values are not interpolated into the script.\n const assignments =\n env == null\n ? []\n : Object.entries(env).map(([name, value]) => `${name}=${value}`);\n\n if (assignments.length === 0) {\n return ['-c', command];\n }\n\n return [\n '-c',\n `${assignments.map(() => 'export -- \"$1\"; shift').join('\\n')}\\n${command}`,\n 'bash',\n ...assignments,\n ];\n}\n\nfunction createSandboxProcess(\n command: SandboxCommand,\n abortSignal: AbortSignal | undefined,\n): Experimental_SandboxProcess {\n const encoder = new TextEncoder();\n const controllers: {\n stdout?: ReadableStreamDefaultController<Uint8Array>;\n stderr?: ReadableStreamDefaultController<Uint8Array>;\n } = {};\n\n const stdout = new ReadableStream<Uint8Array>({\n start(controller) {\n controllers.stdout = controller;\n },\n });\n\n const stderr = new ReadableStream<Uint8Array>({\n start(controller) {\n controllers.stderr = controller;\n },\n });\n\n const drained = (async () => {\n try {\n for await (const message of command.logs()) {\n const target =\n message.type === 'stdout' ? controllers.stdout : controllers.stderr;\n target?.enqueue(encoder.encode(message.data));\n }\n controllers.stdout?.close();\n controllers.stderr?.close();\n } catch (error) {\n controllers.stdout?.error(error);\n controllers.stderr?.error(error);\n }\n })();\n\n return {\n stdout,\n stderr,\n async wait(): Promise<{ exitCode: number }> {\n const finished = await command.wait();\n await drained;\n if (abortSignal?.aborted) {\n throw abortSignal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n return { exitCode: finished.exitCode };\n },\n async kill(): Promise<void> {\n await command.kill();\n },\n };\n}\n\nfunction bytesToStream(bytes: Uint8Array): ReadableStream<Uint8Array> {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n },\n });\n}\n\nasync function collectStream(\n stream: ReadableStream<Uint8Array>,\n): Promise<Uint8Array> {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n total += value.byteLength;\n }\n }\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nfunction isFileNotFoundError(error: unknown): boolean {\n if (error == null || typeof error !== 'object') return false;\n const code = (error as { code?: unknown }).code;\n if (code === 'ENOENT') return true;\n const message = (error as { message?: unknown }).message;\n return (\n typeof message === 'string' &&\n /no such file|not found|does not exist|ENOENT/i.test(message)\n );\n}\n"],"mappings":"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","names":["JUST_BASH_PROVIDER_ID","isFileNotFoundError"]}
1
+ {"version":3,"sources":["../src/just-bash-legacy-sandbox-provider.ts","../src/just-bash-network-sandbox-session.ts","../src/just-bash-sandbox-session.ts","../src/utils.ts","../src/just-bash-sandbox.ts"],"sourcesContent":["import type {\n HarnessV1NetworkSandboxSession,\n HarnessV1SandboxProvider,\n} from '@ai-sdk/harness';\nimport type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';\nimport { Sandbox } from 'just-bash';\nimport { JustBashNetworkSandboxSession } from './just-bash-network-sandbox-session';\nimport { ensureRealpath } from './utils';\n\ntype JustBashSandboxCreateParams = NonNullable<\n Parameters<typeof Sandbox.create>[0]\n>;\n\ntype JustBashCreationSettings = JustBashSandboxCreateParams & {\n sandbox?: never;\n};\n\n/**\n * Settings for `createJustBashSandbox`. Two mutually-exclusive shapes:\n *\n * - `{ sandbox }` — wrap an already-created `just-bash` `Sandbox`. The caller\n * owns its lifecycle.\n * - {@link JustBashSandboxCreateParams} fields — provider calls\n * `Sandbox.create(settings)` on every `createSession()`.\n *\n * just-bash has no port exposure and no snapshot mechanism, so image\n * management is a per-call no-op: if an adapter declares a bootstrap recipe\n * the provider runs it once on the freshly-created sandbox before returning.\n *\n * @deprecated Use `JustBashNetworkSandboxSessionCreateOptions` for new sessions.\n */\nexport type JustBashSandboxSettings =\n | { sandbox: Sandbox }\n | JustBashCreationSettings;\n\nconst JUST_BASH_PROVIDER_ID = 'just-bash-sandbox';\n\n/** @deprecated Use `createJustBashNetworkSandboxSession` instead. */\nexport function createJustBashSandbox(\n settings: JustBashSandboxSettings = {} as JustBashSandboxSettings,\n): HarnessV1SandboxProvider {\n console.warn(\n 'createJustBashSandbox is deprecated. Use createJustBashNetworkSandboxSession and HarnessAgent.createSession({ sandboxSession }) instead.',\n );\n return new JustBashSandboxProvider(settings);\n}\n\n/**\n * `HarnessV1SandboxProvider` implementation backed by `just-bash`. Useful for\n * non-bridge harness flows and for handing a local `Experimental_SandboxSession`\n * to AI SDK tools — use `provider.createSession()` then\n * `sandboxSession.restricted()` to get the latter.\n *\n * Note: just-bash cannot expose ports, so bridge-backed harness adapters\n * (claude-code, codex) will reject this provider at start.\n */\n/** @deprecated Use `createJustBashNetworkSandboxSession` instead. */\nexport class JustBashSandboxProvider implements HarnessV1SandboxProvider {\n readonly specificationVersion = 'harness-sandbox-v1' as const;\n readonly providerId = JUST_BASH_PROVIDER_ID;\n\n constructor(private readonly settings: JustBashSandboxSettings) {\n console.warn(\n 'JustBashSandboxProvider is deprecated. Use createJustBashNetworkSandboxSession instead.',\n );\n }\n\n createSession = async (options?: {\n sessionId?: string;\n abortSignal?: AbortSignal;\n identity?: string;\n onFirstCreate?: (\n session: SandboxSession,\n opts: { abortSignal?: AbortSignal },\n ) => Promise<void>;\n }): Promise<HarnessV1NetworkSandboxSession> => {\n options?.abortSignal?.throwIfAborted();\n\n const ownsLifecycle = !(\n 'sandbox' in this.settings && this.settings.sandbox\n );\n const sandbox = ownsLifecycle\n ? await Sandbox.create(\n this.settings as NonNullable<Parameters<typeof Sandbox.create>[0]>,\n )\n : this.settings.sandbox;\n await ensureRealpath(sandbox);\n const sandboxSession = new JustBashNetworkSandboxSession({\n sandbox,\n });\n\n if (options?.onFirstCreate != null) {\n await options.onFirstCreate(sandboxSession.restricted(), {\n abortSignal: options?.abortSignal,\n });\n }\n\n return sandboxSession;\n };\n}\n","import {\n HarnessCapabilityUnsupportedError,\n type HarnessV1NetworkSandboxSession,\n type HarnessV1PortEndpoint,\n} from '@ai-sdk/harness';\nimport type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';\nimport { randomUUID } from 'node:crypto';\nimport type { Sandbox } from 'just-bash';\nimport { JustBashSandboxSession } from './just-bash-sandbox-session';\n\nconst JUST_BASH_PROVIDER_ID = 'just-bash-sandbox';\n\n/**\n * `HarnessV1NetworkSandboxSession` backed by a `just-bash` `Sandbox`. It extends\n * {@link JustBashSandboxSession} with the infra surface. Exposes no ports —\n * there is no network namespace and no way to publish an HTTP endpoint\n * reachable from outside the host process. Bridge-backed harness adapters that\n * need `getPortEndpoint` will fail with `HarnessCapabilityUnsupportedError` at\n * start.\n *\n * Network policy is not implementable locally either — `setNetworkPolicy` is\n * omitted.\n */\nexport class JustBashNetworkSandboxSession\n extends JustBashSandboxSession\n implements HarnessV1NetworkSandboxSession\n{\n /**\n * Caller-supplied label or a UUID minted at construct time. just-bash has\n * no native identifier and cannot reattach across processes.\n */\n readonly id: string;\n readonly defaultWorkingDirectory: string;\n\n constructor(input: { sandbox: Sandbox; sandboxId?: string }) {\n super(input.sandbox);\n this.id = input.sandboxId ?? randomUUID();\n this.defaultWorkingDirectory = input.sandbox.bashEnvInstance.getCwd();\n }\n\n readonly ports: ReadonlyArray<number> = [];\n\n restricted(): SandboxSession {\n return new JustBashSandboxSession(this.sandbox);\n }\n\n getPortEndpoint = async (_options: {\n port: number;\n protocol?: 'http' | 'https' | 'ws';\n }): Promise<HarnessV1PortEndpoint> => {\n throw new HarnessCapabilityUnsupportedError({\n harnessId: JUST_BASH_PROVIDER_ID,\n message:\n 'just-bash sandboxes run in-process and cannot expose a port URL. ' +\n 'Use a hosted sandbox (e.g. @ai-sdk/sandbox-vercel) for bridge-backed harness adapters.',\n });\n };\n\n /**\n * @deprecated Use `getPortEndpoint` instead.\n */\n getPortUrl = async (options: {\n port: number;\n protocol?: 'http' | 'https' | 'ws';\n }): Promise<string> => {\n return (await this.getPortEndpoint(options)).url;\n };\n\n stop = async (): Promise<void> => {\n // just-bash has no explicit shutdown; the sandbox is garbage-collected\n // along with its in-memory filesystem once references drop.\n await this.sandbox.stop();\n };\n\n destroy = async (): Promise<void> => {\n await this.stop();\n };\n}\n","import { isAbsolute, posix } from 'node:path';\nimport {\n extractLines,\n type Experimental_SandboxSession as SandboxSession,\n type Experimental_SandboxProcess as SandboxProcess,\n} from '@ai-sdk/provider-utils';\nimport type { Sandbox, SandboxCommand } from 'just-bash';\nimport { ensureRealpath } from './utils';\n\n/**\n * `Experimental_SandboxSession` implementation backed by a `just-bash`\n * `Sandbox`. File operations and spawned processes share the same in-memory\n * filesystem, so a bridge process spawned via `spawn` can read files the host\n * wrote via `writeTextFile` (and vice versa).\n *\n * This is the tool-safe surface returned by\n * `JustBashNetworkSandboxSession.restricted()` — not constructed directly by\n * consumers.\n */\nexport class JustBashSandboxSession implements SandboxSession {\n constructor(protected readonly sandbox: Sandbox) {}\n\n get description(): string {\n return [\n 'just-bash JavaScript bash environment with a virtual filesystem.',\n 'Filesystem changes and spawned processes share the same in-memory state.',\n `Current working directory: ${this.sandbox.bashEnvInstance.getCwd()}`,\n ].join('\\n');\n }\n\n private resolvePath(path: string): string {\n return isAbsolute(path)\n ? path\n : posix.join(this.sandbox.bashEnvInstance.getCwd(), path);\n }\n\n async run({\n command,\n workingDirectory,\n env,\n abortSignal,\n }: {\n command: string;\n workingDirectory?: string;\n env?: Record<string, string>;\n abortSignal?: AbortSignal;\n }): Promise<{ exitCode: number; stdout: string; stderr: string }> {\n abortSignal?.throwIfAborted();\n await ensureRealpath(this.sandbox);\n abortSignal?.throwIfAborted();\n\n const finished = await this.sandbox.runCommand({\n cmd: 'bash',\n args: createBashArgs(command, env),\n ...(workingDirectory !== undefined ? { cwd: workingDirectory } : {}),\n ...(abortSignal !== undefined ? { signal: abortSignal } : {}),\n });\n\n const [stdout, stderr] = await Promise.all([\n finished.stdout(),\n finished.stderr(),\n ]);\n\n return {\n exitCode: finished.exitCode,\n stdout,\n stderr,\n };\n }\n\n async spawn({\n command,\n workingDirectory,\n env,\n abortSignal,\n }: {\n command: string;\n workingDirectory?: string;\n env?: Record<string, string>;\n abortSignal?: AbortSignal;\n }): Promise<SandboxProcess> {\n abortSignal?.throwIfAborted();\n await ensureRealpath(this.sandbox);\n abortSignal?.throwIfAborted();\n\n const live = await this.sandbox.runCommand({\n cmd: 'bash',\n args: createBashArgs(command, env),\n detached: true,\n ...(workingDirectory !== undefined ? { cwd: workingDirectory } : {}),\n ...(abortSignal !== undefined ? { signal: abortSignal } : {}),\n });\n\n return createSandboxProcess(live, abortSignal);\n }\n\n async readFile({\n path,\n abortSignal,\n }: {\n path: string;\n abortSignal?: AbortSignal;\n }): Promise<ReadableStream<Uint8Array> | null> {\n const bytes = await this.readBinaryFile({ path, abortSignal });\n if (bytes == null) return null;\n return bytesToStream(bytes);\n }\n\n async readBinaryFile({\n path,\n abortSignal,\n }: {\n path: string;\n abortSignal?: AbortSignal;\n }): Promise<Uint8Array | null> {\n abortSignal?.throwIfAborted();\n const resolved = this.resolvePath(path);\n try {\n const bytes =\n await this.sandbox.bashEnvInstance.fs.readFileBuffer(resolved);\n return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n } catch (error) {\n if (isFileNotFoundError(error)) return null;\n throw error;\n }\n }\n\n async readTextFile({\n path,\n encoding = 'utf-8',\n startLine,\n endLine,\n abortSignal,\n }: {\n path: string;\n encoding?: string;\n startLine?: number;\n endLine?: number;\n abortSignal?: AbortSignal;\n }): Promise<string | null> {\n const bytes = await this.readBinaryFile({ path, abortSignal });\n if (bytes == null) return null;\n const text = Buffer.from(bytes).toString(encoding as BufferEncoding);\n return extractLines({ text, startLine, endLine });\n }\n\n async writeFile({\n path,\n content,\n abortSignal,\n }: {\n path: string;\n content: ReadableStream<Uint8Array>;\n abortSignal?: AbortSignal;\n }): Promise<void> {\n const bytes = await collectStream(content);\n await this.writeBinaryFile({ path, content: bytes, abortSignal });\n }\n\n async writeBinaryFile({\n path,\n content,\n abortSignal,\n }: {\n path: string;\n content: Uint8Array;\n abortSignal?: AbortSignal;\n }): Promise<void> {\n abortSignal?.throwIfAborted();\n const resolved = this.resolvePath(path);\n await this.sandbox.bashEnvInstance.fs.writeFile(resolved, content);\n }\n\n async writeTextFile({\n path,\n content,\n encoding = 'utf-8',\n abortSignal,\n }: {\n path: string;\n content: string;\n encoding?: string;\n abortSignal?: AbortSignal;\n }): Promise<void> {\n const buffer = Buffer.from(content, encoding as BufferEncoding);\n await this.writeBinaryFile({\n path,\n content: new Uint8Array(\n buffer.buffer,\n buffer.byteOffset,\n buffer.byteLength,\n ),\n abortSignal,\n });\n }\n}\n\nfunction createBashArgs(\n command: string,\n env: Record<string, string> | undefined,\n): string[] {\n // just-bash applies runCommand's env to the outer execution, but its `bash`\n // command only inherits exported variables. Export assignments passed as\n // positional arguments so values are not interpolated into the script.\n const assignments =\n env == null\n ? []\n : Object.entries(env).map(([name, value]) => `${name}=${value}`);\n\n if (assignments.length === 0) {\n return ['-c', command];\n }\n\n return [\n '-c',\n `${assignments.map(() => 'export -- \"$1\"; shift').join('\\n')}\\n${command}`,\n 'bash',\n ...assignments,\n ];\n}\n\nfunction createSandboxProcess(\n command: SandboxCommand,\n abortSignal: AbortSignal | undefined,\n): SandboxProcess {\n const encoder = new TextEncoder();\n const controllers: {\n stdout?: ReadableStreamDefaultController<Uint8Array>;\n stderr?: ReadableStreamDefaultController<Uint8Array>;\n } = {};\n\n const stdout = new ReadableStream<Uint8Array>({\n start(controller) {\n controllers.stdout = controller;\n },\n });\n\n const stderr = new ReadableStream<Uint8Array>({\n start(controller) {\n controllers.stderr = controller;\n },\n });\n\n const drained = (async () => {\n try {\n for await (const message of command.logs()) {\n const target =\n message.type === 'stdout' ? controllers.stdout : controllers.stderr;\n target?.enqueue(encoder.encode(message.data));\n }\n controllers.stdout?.close();\n controllers.stderr?.close();\n } catch (error) {\n controllers.stdout?.error(error);\n controllers.stderr?.error(error);\n }\n })();\n\n return {\n stdout,\n stderr,\n async wait(): Promise<{ exitCode: number }> {\n const finished = await command.wait();\n await drained;\n if (abortSignal?.aborted) {\n throw abortSignal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n return { exitCode: finished.exitCode };\n },\n async kill(): Promise<void> {\n await command.kill();\n },\n };\n}\n\nfunction bytesToStream(bytes: Uint8Array): ReadableStream<Uint8Array> {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n },\n });\n}\n\nasync function collectStream(\n stream: ReadableStream<Uint8Array>,\n): Promise<Uint8Array> {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n total += value.byteLength;\n }\n }\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nfunction isFileNotFoundError(error: unknown): boolean {\n if (error == null || typeof error !== 'object') return false;\n const code = (error as { code?: unknown }).code;\n if (code === 'ENOENT') return true;\n const message = (error as { message?: unknown }).message;\n return (\n typeof message === 'string' &&\n /no such file|not found|does not exist|ENOENT/i.test(message)\n );\n}\n","import { defineCommand, type CommandContext, type Sandbox } from 'just-bash';\n\nconst preparations = new WeakMap<Sandbox, Promise<void>>();\n\nexport async function ensureRealpath(sandbox: Sandbox): Promise<void> {\n let preparation = preparations.get(sandbox);\n if (preparation == null) {\n preparation = prepareRealpath(sandbox);\n preparations.set(sandbox, preparation);\n }\n\n try {\n await preparation;\n } catch (error) {\n if (preparations.get(sandbox) === preparation) {\n preparations.delete(sandbox);\n }\n throw error;\n }\n}\n\nasync function prepareRealpath(sandbox: Sandbox): Promise<void> {\n const realpathType = await sandbox.bashEnvInstance.exec('type realpath');\n if (realpathType.exitCode === 0) {\n return;\n }\n\n sandbox.bashEnvInstance.registerCommand(\n defineCommand('realpath', executeRealpath),\n );\n}\n\nasync function executeRealpath(\n args: string[],\n context: CommandContext,\n): Promise<{ stdout: string; stderr: string; exitCode: number }> {\n if (args.length === 0 || args[0] === '') {\n return {\n stdout: '',\n stderr: 'realpath: missing operand\\n',\n exitCode: 1,\n };\n }\n\n let pending = args[0].startsWith('/') ? args[0] : `${context.cwd}/${args[0]}`;\n let resolved = '';\n let linkCount = 0;\n\n while (pending.length > 0) {\n pending = pending.replace(/^\\//, '');\n if (pending.length === 0) {\n break;\n }\n\n const separatorIndex = pending.indexOf('/');\n const component =\n separatorIndex === -1 ? pending : pending.slice(0, separatorIndex);\n pending = separatorIndex === -1 ? '' : pending.slice(separatorIndex + 1);\n\n if (component === '' || component === '.') {\n continue;\n }\n if (component === '..') {\n resolved = resolved.slice(0, resolved.lastIndexOf('/'));\n continue;\n }\n\n const candidate = `${resolved}/${component}`;\n let isSymbolicLink = false;\n try {\n isSymbolicLink = (await context.fs.lstat(candidate)).isSymbolicLink;\n } catch (error) {\n if (!isFileNotFoundError(error)) {\n throw error;\n }\n }\n\n if (!isSymbolicLink) {\n resolved = candidate;\n continue;\n }\n\n linkCount += 1;\n if (linkCount > 64) {\n return { stdout: '', stderr: '', exitCode: 1 };\n }\n\n let target: string;\n try {\n target = await context.fs.readlink(candidate);\n } catch {\n return { stdout: '', stderr: '', exitCode: 1 };\n }\n\n const remainder = pending.length > 0 ? `/${pending}` : '';\n pending = target.startsWith('/')\n ? `${target}${remainder}`\n : `${candidate.slice(0, candidate.lastIndexOf('/'))}/${target}${remainder}`;\n resolved = '';\n }\n\n return {\n stdout: `${resolved || '/'}\\n`,\n stderr: '',\n exitCode: 0,\n };\n}\n\nfunction isFileNotFoundError(error: unknown): boolean {\n if (error == null || typeof error !== 'object') return false;\n const code = (error as { code?: unknown }).code;\n if (code === 'ENOENT') return true;\n const message = (error as { message?: unknown }).message;\n return (\n typeof message === 'string' &&\n /no such file|not found|ENOENT/i.test(message)\n );\n}\n","import {\n HarnessCapabilityUnsupportedError,\n type HarnessV1NetworkSandboxSession,\n type HarnessV1SandboxSessionCreateOptions,\n type HarnessV1SandboxSessionResumeOptions,\n} from '@ai-sdk/harness';\nimport type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';\nimport { Sandbox } from 'just-bash';\nimport { JustBashNetworkSandboxSession } from './just-bash-network-sandbox-session';\nimport { JustBashSandboxSession } from './just-bash-sandbox-session';\nimport { ensureRealpath } from './utils';\n\n/**\n * Flattens an intersection of object types into a single object type so the\n * resolved shape displays as its named properties rather than a chain of\n * `A & B & C`.\n */\ntype Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type JustBashNativeSandboxSession = Sandbox;\n\n/**\n * Parameters forwarded to `just-bash`'s `Sandbox.create` when creating a\n * sandbox from scratch. Aliased directly from the underlying SDK so the full\n * surface is available without us re-declaring it.\n */\ntype JustBashSandboxCreateParams = NonNullable<\n Parameters<typeof Sandbox.create>[0]\n>;\n\ntype JustBashCreationSettings = Omit<JustBashSandboxCreateParams, 'sandbox'>;\n\nexport type JustBashNetworkSandboxSessionCreateOptions = Prettify<\n HarnessV1SandboxSessionCreateOptions<JustBashCreationSettings> & {\n sandbox?: never;\n }\n>;\n\ntype JustBashLookupSettings = Record<never, never>;\n\nexport type JustBashNetworkSandboxSessionResumeOptions =\n HarnessV1SandboxSessionResumeOptions<JustBashLookupSettings>;\n\nexport async function createJustBashNetworkSandboxSession(\n options: JustBashNetworkSandboxSessionCreateOptions = {},\n): Promise<HarnessV1NetworkSandboxSession> {\n if ('sandbox' in options) {\n throw new Error(\n 'createJustBashNetworkSandboxSession: use createJustBashNetworkSandboxSessionFromNativeSandbox for an existing sandbox.',\n );\n }\n const {\n template,\n abortSignal,\n sandboxId,\n sandbox: _sandbox,\n ...nativeOptions\n } = options;\n abortSignal?.throwIfAborted();\n const nativeSandbox = await Sandbox.create(nativeOptions);\n try {\n await ensureRealpath(nativeSandbox);\n const session = new JustBashNetworkSandboxSession({\n sandbox: nativeSandbox,\n sandboxId,\n });\n await template?.prepare({ session: session.restricted(), abortSignal });\n return session;\n } catch (error) {\n await nativeSandbox.stop().catch(() => {});\n throw error;\n }\n}\n\nexport async function resumeJustBashNetworkSandboxSession(\n options: JustBashNetworkSandboxSessionResumeOptions,\n): Promise<HarnessV1NetworkSandboxSession> {\n options.abortSignal?.throwIfAborted();\n throw new HarnessCapabilityUnsupportedError({\n message:\n 'just-bash sandboxes run in-process and cannot be resumed by sandboxId.',\n });\n}\n\nexport function createJustBashSandboxSessionFromNativeSandbox(\n nativeSandbox: JustBashNativeSandboxSession,\n): SandboxSession {\n return new JustBashSandboxSession(nativeSandbox);\n}\n\nexport function createJustBashNetworkSandboxSessionFromNativeSandbox(\n nativeSandbox: JustBashNativeSandboxSession,\n): HarnessV1NetworkSandboxSession {\n return new JustBashNetworkSandboxSession({ sandbox: nativeSandbox });\n}\n"],"mappings":"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","names":["isFileNotFoundError","JUST_BASH_PROVIDER_ID","HarnessCapabilityUnsupportedError","Sandbox","Sandbox","HarnessCapabilityUnsupportedError"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/sandbox-just-bash",
3
- "version": "1.0.123",
3
+ "version": "1.0.126",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -26,8 +26,8 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "@ai-sdk/harness": "1.0.123",
30
- "@ai-sdk/provider-utils": "5.0.47",
29
+ "@ai-sdk/harness": "1.0.126",
30
+ "@ai-sdk/provider-utils": "5.0.49",
31
31
  "just-bash": "^2.14.5"
32
32
  },
33
33
  "devDependencies": {
package/src/index.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  export {
2
+ type JustBashSandboxSettings,
2
3
  createJustBashSandbox,
3
4
  JustBashSandboxProvider,
4
- type JustBashSandboxSettings,
5
+ } from './just-bash-legacy-sandbox-provider';
6
+ export {
7
+ createJustBashNetworkSandboxSession,
8
+ resumeJustBashNetworkSandboxSession,
9
+ createJustBashSandboxSessionFromNativeSandbox,
10
+ createJustBashNetworkSandboxSessionFromNativeSandbox,
11
+ type JustBashNativeSandboxSession,
12
+ type JustBashNetworkSandboxSessionCreateOptions,
13
+ type JustBashNetworkSandboxSessionResumeOptions,
5
14
  } from './just-bash-sandbox';
@@ -0,0 +1,100 @@
1
+ import type {
2
+ HarnessV1NetworkSandboxSession,
3
+ HarnessV1SandboxProvider,
4
+ } from '@ai-sdk/harness';
5
+ import type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';
6
+ import { Sandbox } from 'just-bash';
7
+ import { JustBashNetworkSandboxSession } from './just-bash-network-sandbox-session';
8
+ import { ensureRealpath } from './utils';
9
+
10
+ type JustBashSandboxCreateParams = NonNullable<
11
+ Parameters<typeof Sandbox.create>[0]
12
+ >;
13
+
14
+ type JustBashCreationSettings = JustBashSandboxCreateParams & {
15
+ sandbox?: never;
16
+ };
17
+
18
+ /**
19
+ * Settings for `createJustBashSandbox`. Two mutually-exclusive shapes:
20
+ *
21
+ * - `{ sandbox }` — wrap an already-created `just-bash` `Sandbox`. The caller
22
+ * owns its lifecycle.
23
+ * - {@link JustBashSandboxCreateParams} fields — provider calls
24
+ * `Sandbox.create(settings)` on every `createSession()`.
25
+ *
26
+ * just-bash has no port exposure and no snapshot mechanism, so image
27
+ * management is a per-call no-op: if an adapter declares a bootstrap recipe
28
+ * the provider runs it once on the freshly-created sandbox before returning.
29
+ *
30
+ * @deprecated Use `JustBashNetworkSandboxSessionCreateOptions` for new sessions.
31
+ */
32
+ export type JustBashSandboxSettings =
33
+ | { sandbox: Sandbox }
34
+ | JustBashCreationSettings;
35
+
36
+ const JUST_BASH_PROVIDER_ID = 'just-bash-sandbox';
37
+
38
+ /** @deprecated Use `createJustBashNetworkSandboxSession` instead. */
39
+ export function createJustBashSandbox(
40
+ settings: JustBashSandboxSettings = {} as JustBashSandboxSettings,
41
+ ): HarnessV1SandboxProvider {
42
+ console.warn(
43
+ 'createJustBashSandbox is deprecated. Use createJustBashNetworkSandboxSession and HarnessAgent.createSession({ sandboxSession }) instead.',
44
+ );
45
+ return new JustBashSandboxProvider(settings);
46
+ }
47
+
48
+ /**
49
+ * `HarnessV1SandboxProvider` implementation backed by `just-bash`. Useful for
50
+ * non-bridge harness flows and for handing a local `Experimental_SandboxSession`
51
+ * to AI SDK tools — use `provider.createSession()` then
52
+ * `sandboxSession.restricted()` to get the latter.
53
+ *
54
+ * Note: just-bash cannot expose ports, so bridge-backed harness adapters
55
+ * (claude-code, codex) will reject this provider at start.
56
+ */
57
+ /** @deprecated Use `createJustBashNetworkSandboxSession` instead. */
58
+ export class JustBashSandboxProvider implements HarnessV1SandboxProvider {
59
+ readonly specificationVersion = 'harness-sandbox-v1' as const;
60
+ readonly providerId = JUST_BASH_PROVIDER_ID;
61
+
62
+ constructor(private readonly settings: JustBashSandboxSettings) {
63
+ console.warn(
64
+ 'JustBashSandboxProvider is deprecated. Use createJustBashNetworkSandboxSession instead.',
65
+ );
66
+ }
67
+
68
+ createSession = async (options?: {
69
+ sessionId?: string;
70
+ abortSignal?: AbortSignal;
71
+ identity?: string;
72
+ onFirstCreate?: (
73
+ session: SandboxSession,
74
+ opts: { abortSignal?: AbortSignal },
75
+ ) => Promise<void>;
76
+ }): Promise<HarnessV1NetworkSandboxSession> => {
77
+ options?.abortSignal?.throwIfAborted();
78
+
79
+ const ownsLifecycle = !(
80
+ 'sandbox' in this.settings && this.settings.sandbox
81
+ );
82
+ const sandbox = ownsLifecycle
83
+ ? await Sandbox.create(
84
+ this.settings as NonNullable<Parameters<typeof Sandbox.create>[0]>,
85
+ )
86
+ : this.settings.sandbox;
87
+ await ensureRealpath(sandbox);
88
+ const sandboxSession = new JustBashNetworkSandboxSession({
89
+ sandbox,
90
+ });
91
+
92
+ if (options?.onFirstCreate != null) {
93
+ await options.onFirstCreate(sandboxSession.restricted(), {
94
+ abortSignal: options?.abortSignal,
95
+ });
96
+ }
97
+
98
+ return sandboxSession;
99
+ };
100
+ }
@@ -26,16 +26,15 @@ export class JustBashNetworkSandboxSession
26
26
  implements HarnessV1NetworkSandboxSession
27
27
  {
28
28
  /**
29
- * Minted at construct time. just-bash has no native identifier and no
30
- * cross-process reattach surface; the value exists only to satisfy the
31
- * `HarnessV1NetworkSandboxSession.id` contract.
29
+ * Caller-supplied label or a UUID minted at construct time. just-bash has
30
+ * no native identifier and cannot reattach across processes.
32
31
  */
33
32
  readonly id: string;
34
33
  readonly defaultWorkingDirectory: string;
35
34
 
36
- constructor(input: { sandbox: Sandbox }) {
35
+ constructor(input: { sandbox: Sandbox; sandboxId?: string }) {
37
36
  super(input.sandbox);
38
- this.id = randomUUID();
37
+ this.id = input.sandboxId ?? randomUUID();
39
38
  this.defaultWorkingDirectory = input.sandbox.bashEnvInstance.getCwd();
40
39
  }
41
40
 
@@ -70,6 +69,7 @@ export class JustBashNetworkSandboxSession
70
69
  stop = async (): Promise<void> => {
71
70
  // just-bash has no explicit shutdown; the sandbox is garbage-collected
72
71
  // along with its in-memory filesystem once references drop.
72
+ await this.sandbox.stop();
73
73
  };
74
74
 
75
75
  destroy = async (): Promise<void> => {
@@ -1,10 +1,11 @@
1
1
  import { isAbsolute, posix } from 'node:path';
2
2
  import {
3
3
  extractLines,
4
- type Experimental_SandboxSession,
5
- type Experimental_SandboxProcess,
4
+ type Experimental_SandboxSession as SandboxSession,
5
+ type Experimental_SandboxProcess as SandboxProcess,
6
6
  } from '@ai-sdk/provider-utils';
7
7
  import type { Sandbox, SandboxCommand } from 'just-bash';
8
+ import { ensureRealpath } from './utils';
8
9
 
9
10
  /**
10
11
  * `Experimental_SandboxSession` implementation backed by a `just-bash`
@@ -16,7 +17,7 @@ import type { Sandbox, SandboxCommand } from 'just-bash';
16
17
  * `JustBashNetworkSandboxSession.restricted()` — not constructed directly by
17
18
  * consumers.
18
19
  */
19
- export class JustBashSandboxSession implements Experimental_SandboxSession {
20
+ export class JustBashSandboxSession implements SandboxSession {
20
21
  constructor(protected readonly sandbox: Sandbox) {}
21
22
 
22
23
  get description(): string {
@@ -45,6 +46,8 @@ export class JustBashSandboxSession implements Experimental_SandboxSession {
45
46
  abortSignal?: AbortSignal;
46
47
  }): Promise<{ exitCode: number; stdout: string; stderr: string }> {
47
48
  abortSignal?.throwIfAborted();
49
+ await ensureRealpath(this.sandbox);
50
+ abortSignal?.throwIfAborted();
48
51
 
49
52
  const finished = await this.sandbox.runCommand({
50
53
  cmd: 'bash',
@@ -75,7 +78,9 @@ export class JustBashSandboxSession implements Experimental_SandboxSession {
75
78
  workingDirectory?: string;
76
79
  env?: Record<string, string>;
77
80
  abortSignal?: AbortSignal;
78
- }): Promise<Experimental_SandboxProcess> {
81
+ }): Promise<SandboxProcess> {
82
+ abortSignal?.throwIfAborted();
83
+ await ensureRealpath(this.sandbox);
79
84
  abortSignal?.throwIfAborted();
80
85
 
81
86
  const live = await this.sandbox.runCommand({
@@ -217,7 +222,7 @@ function createBashArgs(
217
222
  function createSandboxProcess(
218
223
  command: SandboxCommand,
219
224
  abortSignal: AbortSignal | undefined,
220
- ): Experimental_SandboxProcess {
225
+ ): SandboxProcess {
221
226
  const encoder = new TextEncoder();
222
227
  const controllers: {
223
228
  stdout?: ReadableStreamDefaultController<Uint8Array>;
@@ -1,10 +1,23 @@
1
- import type {
2
- HarnessV1NetworkSandboxSession,
3
- HarnessV1SandboxProvider,
1
+ import {
2
+ HarnessCapabilityUnsupportedError,
3
+ type HarnessV1NetworkSandboxSession,
4
+ type HarnessV1SandboxSessionCreateOptions,
5
+ type HarnessV1SandboxSessionResumeOptions,
4
6
  } from '@ai-sdk/harness';
5
7
  import type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';
6
8
  import { Sandbox } from 'just-bash';
7
9
  import { JustBashNetworkSandboxSession } from './just-bash-network-sandbox-session';
10
+ import { JustBashSandboxSession } from './just-bash-sandbox-session';
11
+ import { ensureRealpath } from './utils';
12
+
13
+ /**
14
+ * Flattens an intersection of object types into a single object type so the
15
+ * resolved shape displays as its named properties rather than a chain of
16
+ * `A & B & C`.
17
+ */
18
+ type Prettify<T> = { [K in keyof T]: T[K] } & {};
19
+
20
+ export type JustBashNativeSandboxSession = Sandbox;
8
21
 
9
22
  /**
10
23
  * Parameters forwarded to `just-bash`'s `Sandbox.create` when creating a
@@ -15,145 +28,68 @@ type JustBashSandboxCreateParams = NonNullable<
15
28
  Parameters<typeof Sandbox.create>[0]
16
29
  >;
17
30
 
18
- /**
19
- * Settings for {@link createJustBashSandbox}. Two mutually-exclusive shapes:
20
- *
21
- * - `{ sandbox }` — wrap an already-created `just-bash` `Sandbox`. The caller
22
- * owns its lifecycle.
23
- * - {@link JustBashSandboxCreateParams} fields — provider calls
24
- * `Sandbox.create(settings)` on every `createSession()`.
25
- *
26
- * just-bash has no port exposure and no snapshot mechanism, so image
27
- * management is a per-call no-op: if an adapter declares a bootstrap recipe
28
- * the provider runs it once on the freshly-created sandbox before returning.
29
- */
30
- export type JustBashSandboxSettings =
31
- | { sandbox: Sandbox }
32
- | (JustBashSandboxCreateParams & { sandbox?: never });
31
+ type JustBashCreationSettings = Omit<JustBashSandboxCreateParams, 'sandbox'>;
33
32
 
34
- const JUST_BASH_PROVIDER_ID = 'just-bash-sandbox';
35
- const REALPATH_PATH = '/usr/bin/realpath';
36
- const REALPATH_SCRIPT = `#!/usr/bin/env bash
37
- pending=\${1:?}
38
- resolved=
39
- link_count=0
40
- link_marker=__AI_SDK_REALPATH_LINK_END__
41
- case "$pending" in
42
- /*) ;;
43
- *) pending=$PWD/$pending ;;
44
- esac
45
- while [ -n "$pending" ]; do
46
- pending=\${pending#/}
47
- [ -n "$pending" ] || break
48
- component=\${pending%%/*}
49
- if [ "$pending" = "$component" ]; then
50
- pending=
51
- else
52
- pending=\${pending#*/}
53
- fi
54
- case "$component" in
55
- ""|.) continue ;;
56
- ..)
57
- resolved=\${resolved%/*}
58
- continue
59
- ;;
60
- esac
61
- candidate=$resolved/$component
62
- if [ -L "$candidate" ]; then
63
- link_count=$((link_count + 1))
64
- [ "$link_count" -le 64 ] || exit 1
65
- link_target_framed=$(readlink "$candidate"; readlink_status=$?; printf '%s' "$link_marker"; exit "$readlink_status")
66
- readlink_status=$?
67
- [ "$readlink_status" -eq 0 ] || exit 1
68
- target=\${link_target_framed%$link_marker}
69
- target=\${target%$'\n'}
70
- case "$target" in
71
- /*) pending=$target\${pending:+/$pending} ;;
72
- *) pending=\${candidate%/*}/$target\${pending:+/$pending} ;;
73
- esac
74
- resolved=
75
- else
76
- resolved=$candidate
77
- fi
78
- done
79
- printf '%s\n' "\${resolved:-/}"
80
- `;
33
+ export type JustBashNetworkSandboxSessionCreateOptions = Prettify<
34
+ HarnessV1SandboxSessionCreateOptions<JustBashCreationSettings> & {
35
+ sandbox?: never;
36
+ }
37
+ >;
38
+
39
+ type JustBashLookupSettings = Record<never, never>;
81
40
 
82
- async function ensureRealpath(sandbox: Sandbox): Promise<void> {
83
- const fs = sandbox.bashEnvInstance.fs;
41
+ export type JustBashNetworkSandboxSessionResumeOptions =
42
+ HarnessV1SandboxSessionResumeOptions<JustBashLookupSettings>;
43
+
44
+ export async function createJustBashNetworkSandboxSession(
45
+ options: JustBashNetworkSandboxSessionCreateOptions = {},
46
+ ): Promise<HarnessV1NetworkSandboxSession> {
47
+ if ('sandbox' in options) {
48
+ throw new Error(
49
+ 'createJustBashNetworkSandboxSession: use createJustBashNetworkSandboxSessionFromNativeSandbox for an existing sandbox.',
50
+ );
51
+ }
52
+ const {
53
+ template,
54
+ abortSignal,
55
+ sandboxId,
56
+ sandbox: _sandbox,
57
+ ...nativeOptions
58
+ } = options;
59
+ abortSignal?.throwIfAborted();
60
+ const nativeSandbox = await Sandbox.create(nativeOptions);
84
61
  try {
85
- await fs.lstat(REALPATH_PATH);
86
- return;
62
+ await ensureRealpath(nativeSandbox);
63
+ const session = new JustBashNetworkSandboxSession({
64
+ sandbox: nativeSandbox,
65
+ sandboxId,
66
+ });
67
+ await template?.prepare({ session: session.restricted(), abortSignal });
68
+ return session;
87
69
  } catch (error) {
88
- if (!isFileNotFoundError(error)) {
89
- throw error;
90
- }
70
+ await nativeSandbox.stop().catch(() => {});
71
+ throw error;
91
72
  }
92
-
93
- await sandbox.writeFiles({ [REALPATH_PATH]: REALPATH_SCRIPT });
94
- await fs.chmod(REALPATH_PATH, 0o755);
95
73
  }
96
74
 
97
- function isFileNotFoundError(error: unknown): boolean {
98
- if (error == null || typeof error !== 'object') return false;
99
- const code = (error as { code?: unknown }).code;
100
- if (code === 'ENOENT') return true;
101
- const message = (error as { message?: unknown }).message;
102
- return (
103
- typeof message === 'string' &&
104
- /no such file|not found|ENOENT/i.test(message)
105
- );
75
+ export async function resumeJustBashNetworkSandboxSession(
76
+ options: JustBashNetworkSandboxSessionResumeOptions,
77
+ ): Promise<HarnessV1NetworkSandboxSession> {
78
+ options.abortSignal?.throwIfAborted();
79
+ throw new HarnessCapabilityUnsupportedError({
80
+ message:
81
+ 'just-bash sandboxes run in-process and cannot be resumed by sandboxId.',
82
+ });
106
83
  }
107
84
 
108
- export function createJustBashSandbox(
109
- settings: JustBashSandboxSettings = {} as JustBashSandboxSettings,
110
- ): HarnessV1SandboxProvider {
111
- return new JustBashSandboxProvider(settings);
85
+ export function createJustBashSandboxSessionFromNativeSandbox(
86
+ nativeSandbox: JustBashNativeSandboxSession,
87
+ ): SandboxSession {
88
+ return new JustBashSandboxSession(nativeSandbox);
112
89
  }
113
90
 
114
- /**
115
- * `HarnessV1SandboxProvider` implementation backed by `just-bash`. Useful for
116
- * non-bridge harness flows and for handing a local `Experimental_SandboxSession`
117
- * to AI SDK tools — use `provider.createSession()` then
118
- * `sandboxSession.restricted()` to get the latter.
119
- *
120
- * Note: just-bash cannot expose ports, so bridge-backed harness adapters
121
- * (claude-code, codex) will reject this provider at start.
122
- */
123
- export class JustBashSandboxProvider implements HarnessV1SandboxProvider {
124
- readonly specificationVersion = 'harness-sandbox-v1' as const;
125
- readonly providerId = JUST_BASH_PROVIDER_ID;
126
-
127
- constructor(private readonly settings: JustBashSandboxSettings) {}
128
-
129
- createSession = async (options?: {
130
- sessionId?: string;
131
- abortSignal?: AbortSignal;
132
- identity?: string;
133
- onFirstCreate?: (
134
- session: SandboxSession,
135
- opts: { abortSignal?: AbortSignal },
136
- ) => Promise<void>;
137
- }): Promise<HarnessV1NetworkSandboxSession> => {
138
- options?.abortSignal?.throwIfAborted();
139
-
140
- const ownsLifecycle = !(
141
- 'sandbox' in this.settings && this.settings.sandbox
142
- );
143
- const sandbox = ownsLifecycle
144
- ? await Sandbox.create(this.settings as JustBashSandboxCreateParams)
145
- : this.settings.sandbox;
146
- await ensureRealpath(sandbox);
147
- const sandboxSession = new JustBashNetworkSandboxSession({
148
- sandbox,
149
- });
150
-
151
- if (options?.onFirstCreate != null) {
152
- await options.onFirstCreate(sandboxSession.restricted(), {
153
- abortSignal: options?.abortSignal,
154
- });
155
- }
156
-
157
- return sandboxSession;
158
- };
91
+ export function createJustBashNetworkSandboxSessionFromNativeSandbox(
92
+ nativeSandbox: JustBashNativeSandboxSession,
93
+ ): HarnessV1NetworkSandboxSession {
94
+ return new JustBashNetworkSandboxSession({ sandbox: nativeSandbox });
159
95
  }
package/src/utils.ts ADDED
@@ -0,0 +1,118 @@
1
+ import { defineCommand, type CommandContext, type Sandbox } from 'just-bash';
2
+
3
+ const preparations = new WeakMap<Sandbox, Promise<void>>();
4
+
5
+ export async function ensureRealpath(sandbox: Sandbox): Promise<void> {
6
+ let preparation = preparations.get(sandbox);
7
+ if (preparation == null) {
8
+ preparation = prepareRealpath(sandbox);
9
+ preparations.set(sandbox, preparation);
10
+ }
11
+
12
+ try {
13
+ await preparation;
14
+ } catch (error) {
15
+ if (preparations.get(sandbox) === preparation) {
16
+ preparations.delete(sandbox);
17
+ }
18
+ throw error;
19
+ }
20
+ }
21
+
22
+ async function prepareRealpath(sandbox: Sandbox): Promise<void> {
23
+ const realpathType = await sandbox.bashEnvInstance.exec('type realpath');
24
+ if (realpathType.exitCode === 0) {
25
+ return;
26
+ }
27
+
28
+ sandbox.bashEnvInstance.registerCommand(
29
+ defineCommand('realpath', executeRealpath),
30
+ );
31
+ }
32
+
33
+ async function executeRealpath(
34
+ args: string[],
35
+ context: CommandContext,
36
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
37
+ if (args.length === 0 || args[0] === '') {
38
+ return {
39
+ stdout: '',
40
+ stderr: 'realpath: missing operand\n',
41
+ exitCode: 1,
42
+ };
43
+ }
44
+
45
+ let pending = args[0].startsWith('/') ? args[0] : `${context.cwd}/${args[0]}`;
46
+ let resolved = '';
47
+ let linkCount = 0;
48
+
49
+ while (pending.length > 0) {
50
+ pending = pending.replace(/^\//, '');
51
+ if (pending.length === 0) {
52
+ break;
53
+ }
54
+
55
+ const separatorIndex = pending.indexOf('/');
56
+ const component =
57
+ separatorIndex === -1 ? pending : pending.slice(0, separatorIndex);
58
+ pending = separatorIndex === -1 ? '' : pending.slice(separatorIndex + 1);
59
+
60
+ if (component === '' || component === '.') {
61
+ continue;
62
+ }
63
+ if (component === '..') {
64
+ resolved = resolved.slice(0, resolved.lastIndexOf('/'));
65
+ continue;
66
+ }
67
+
68
+ const candidate = `${resolved}/${component}`;
69
+ let isSymbolicLink = false;
70
+ try {
71
+ isSymbolicLink = (await context.fs.lstat(candidate)).isSymbolicLink;
72
+ } catch (error) {
73
+ if (!isFileNotFoundError(error)) {
74
+ throw error;
75
+ }
76
+ }
77
+
78
+ if (!isSymbolicLink) {
79
+ resolved = candidate;
80
+ continue;
81
+ }
82
+
83
+ linkCount += 1;
84
+ if (linkCount > 64) {
85
+ return { stdout: '', stderr: '', exitCode: 1 };
86
+ }
87
+
88
+ let target: string;
89
+ try {
90
+ target = await context.fs.readlink(candidate);
91
+ } catch {
92
+ return { stdout: '', stderr: '', exitCode: 1 };
93
+ }
94
+
95
+ const remainder = pending.length > 0 ? `/${pending}` : '';
96
+ pending = target.startsWith('/')
97
+ ? `${target}${remainder}`
98
+ : `${candidate.slice(0, candidate.lastIndexOf('/'))}/${target}${remainder}`;
99
+ resolved = '';
100
+ }
101
+
102
+ return {
103
+ stdout: `${resolved || '/'}\n`,
104
+ stderr: '',
105
+ exitCode: 0,
106
+ };
107
+ }
108
+
109
+ function isFileNotFoundError(error: unknown): boolean {
110
+ if (error == null || typeof error !== 'object') return false;
111
+ const code = (error as { code?: unknown }).code;
112
+ if (code === 'ENOENT') return true;
113
+ const message = (error as { message?: unknown }).message;
114
+ return (
115
+ typeof message === 'string' &&
116
+ /no such file|not found|ENOENT/i.test(message)
117
+ );
118
+ }