@ai-sdk/sandbox-just-bash 1.0.117 → 1.0.119

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,21 @@
1
1
  # @ai-sdk/sandbox-just-bash
2
2
 
3
+ ## 1.0.119
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [125f493]
8
+ - @ai-sdk/harness@1.0.119
9
+
10
+ ## 1.0.118
11
+
12
+ ### Patch Changes
13
+
14
+ - f39733b: fix(sandbox-just-bash): seed `realpath` binary via `readlink` so that it is available to consumers that require it, such as the Pi harness
15
+ - Updated dependencies [d975097]
16
+ - Updated dependencies [b2baeba]
17
+ - @ai-sdk/harness@1.0.118
18
+
3
19
  ## 1.0.117
4
20
 
5
21
  ### Patch Changes
package/dist/index.js CHANGED
@@ -262,6 +262,74 @@ var JustBashNetworkSandboxSession = class extends JustBashSandboxSession {
262
262
 
263
263
  // src/just-bash-sandbox.ts
264
264
  var JUST_BASH_PROVIDER_ID2 = "just-bash-sandbox";
265
+ var REALPATH_PATH = "/usr/bin/realpath";
266
+ var REALPATH_SCRIPT = `#!/usr/bin/env bash
267
+ pending=\${1:?}
268
+ resolved=
269
+ link_count=0
270
+ link_marker=__AI_SDK_REALPATH_LINK_END__
271
+ case "$pending" in
272
+ /*) ;;
273
+ *) pending=$PWD/$pending ;;
274
+ esac
275
+ while [ -n "$pending" ]; do
276
+ pending=\${pending#/}
277
+ [ -n "$pending" ] || break
278
+ component=\${pending%%/*}
279
+ if [ "$pending" = "$component" ]; then
280
+ pending=
281
+ else
282
+ pending=\${pending#*/}
283
+ fi
284
+ case "$component" in
285
+ ""|.) continue ;;
286
+ ..)
287
+ resolved=\${resolved%/*}
288
+ continue
289
+ ;;
290
+ esac
291
+ candidate=$resolved/$component
292
+ if [ -L "$candidate" ]; then
293
+ link_count=$((link_count + 1))
294
+ [ "$link_count" -le 64 ] || exit 1
295
+ link_target_framed=$(readlink "$candidate"; readlink_status=$?; printf '%s' "$link_marker"; exit "$readlink_status")
296
+ readlink_status=$?
297
+ [ "$readlink_status" -eq 0 ] || exit 1
298
+ target=\${link_target_framed%$link_marker}
299
+ target=\${target%$'
300
+ '}
301
+ case "$target" in
302
+ /*) pending=$target\${pending:+/$pending} ;;
303
+ *) pending=\${candidate%/*}/$target\${pending:+/$pending} ;;
304
+ esac
305
+ resolved=
306
+ else
307
+ resolved=$candidate
308
+ fi
309
+ done
310
+ printf '%s
311
+ ' "\${resolved:-/}"
312
+ `;
313
+ async function ensureRealpath(sandbox) {
314
+ const fs = sandbox.bashEnvInstance.fs;
315
+ try {
316
+ await fs.lstat(REALPATH_PATH);
317
+ return;
318
+ } catch (error) {
319
+ if (!isFileNotFoundError2(error)) {
320
+ throw error;
321
+ }
322
+ }
323
+ await sandbox.writeFiles({ [REALPATH_PATH]: REALPATH_SCRIPT });
324
+ await fs.chmod(REALPATH_PATH, 493);
325
+ }
326
+ function isFileNotFoundError2(error) {
327
+ if (error == null || typeof error !== "object") return false;
328
+ const code = error.code;
329
+ if (code === "ENOENT") return true;
330
+ const message = error.message;
331
+ return typeof message === "string" && /no such file|not found|ENOENT/i.test(message);
332
+ }
265
333
  function createJustBashSandbox(settings = {}) {
266
334
  return new JustBashSandboxProvider(settings);
267
335
  }
@@ -273,17 +341,12 @@ var JustBashSandboxProvider = class {
273
341
  this.createSession = async (options) => {
274
342
  var _a;
275
343
  (_a = options == null ? void 0 : options.abortSignal) == null ? void 0 : _a.throwIfAborted();
276
- if ("sandbox" in this.settings && this.settings.sandbox) {
277
- return new JustBashNetworkSandboxSession({
278
- sandbox: this.settings.sandbox,
279
- ownsLifecycle: false
280
- });
281
- }
282
- const createParams = this.settings;
283
- const sandbox = await Sandbox.create(createParams);
344
+ const ownsLifecycle = !("sandbox" in this.settings && this.settings.sandbox);
345
+ const sandbox = ownsLifecycle ? await Sandbox.create(this.settings) : this.settings.sandbox;
346
+ await ensureRealpath(sandbox);
284
347
  const sandboxSession = new JustBashNetworkSandboxSession({
285
348
  sandbox,
286
- ownsLifecycle: true
349
+ ownsLifecycle
287
350
  });
288
351
  if ((options == null ? void 0 : options.onFirstCreate) != null) {
289
352
  await options.onFirstCreate(sandboxSession.restricted(), {
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';\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 if ('sandbox' in this.settings && this.settings.sandbox) {\n return new JustBashNetworkSandboxSession({\n sandbox: this.settings.sandbox,\n ownsLifecycle: false,\n });\n }\n\n const createParams = this.settings as JustBashSandboxCreateParams;\n\n const sandbox = await Sandbox.create(createParams);\n const sandboxSession = new JustBashNetworkSandboxSession({\n sandbox,\n ownsLifecycle: true,\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 private readonly ownsLifecycle: boolean;\n\n constructor(input: { sandbox: Sandbox; ownsLifecycle: boolean }) {\n super(input.sandbox);\n this.ownsLifecycle = input.ownsLifecycle;\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 if (!this.ownsLifecycle) return;\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"]}
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 ownsLifecycle,\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 private readonly ownsLifecycle: boolean;\n\n constructor(input: { sandbox: Sandbox; ownsLifecycle: boolean }) {\n super(input.sandbox);\n this.ownsLifecycle = input.ownsLifecycle;\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 if (!this.ownsLifecycle) return;\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/sandbox-just-bash",
3
- "version": "1.0.117",
3
+ "version": "1.0.119",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -26,7 +26,7 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "@ai-sdk/harness": "1.0.117",
29
+ "@ai-sdk/harness": "1.0.119",
30
30
  "@ai-sdk/provider-utils": "5.0.45",
31
31
  "just-bash": "^2.14.5"
32
32
  },
@@ -32,6 +32,78 @@ export type JustBashSandboxSettings =
32
32
  | (JustBashSandboxCreateParams & { sandbox?: never });
33
33
 
34
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
+ `;
81
+
82
+ async function ensureRealpath(sandbox: Sandbox): Promise<void> {
83
+ const fs = sandbox.bashEnvInstance.fs;
84
+ try {
85
+ await fs.lstat(REALPATH_PATH);
86
+ return;
87
+ } catch (error) {
88
+ if (!isFileNotFoundError(error)) {
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ await sandbox.writeFiles({ [REALPATH_PATH]: REALPATH_SCRIPT });
94
+ await fs.chmod(REALPATH_PATH, 0o755);
95
+ }
96
+
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
+ );
106
+ }
35
107
 
36
108
  export function createJustBashSandbox(
37
109
  settings: JustBashSandboxSettings = {} as JustBashSandboxSettings,
@@ -65,19 +137,16 @@ export class JustBashSandboxProvider implements HarnessV1SandboxProvider {
65
137
  }): Promise<HarnessV1NetworkSandboxSession> => {
66
138
  options?.abortSignal?.throwIfAborted();
67
139
 
68
- if ('sandbox' in this.settings && this.settings.sandbox) {
69
- return new JustBashNetworkSandboxSession({
70
- sandbox: this.settings.sandbox,
71
- ownsLifecycle: false,
72
- });
73
- }
74
-
75
- const createParams = this.settings as JustBashSandboxCreateParams;
76
-
77
- const sandbox = await Sandbox.create(createParams);
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);
78
147
  const sandboxSession = new JustBashNetworkSandboxSession({
79
148
  sandbox,
80
- ownsLifecycle: true,
149
+ ownsLifecycle,
81
150
  });
82
151
 
83
152
  if (options?.onFirstCreate != null) {