@coder/ai-sdk-sandbox 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +199 -31
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -11,12 +11,13 @@ you pass it as the `sandbox` to a `HarnessAgent` exactly like
11
11
  `@ai-sdk/sandbox-vercel`.
12
12
 
13
13
  > **Status:** experimental. This provider tracks the stable AI SDK v7 harness
14
- > packages (`@ai-sdk/harness@^1.0.23`).
14
+ > packages (see the `@ai-sdk/harness` peer range in
15
+ > [`package.json`](./package.json)).
15
16
 
16
17
  ## Install
17
18
 
18
19
  ```bash
19
- npm add @coder/ai-sdk-sandbox @ai-sdk/harness @ai-sdk/harness-claude-code @ai-sdk/provider-utils
20
+ pnpm add @coder/ai-sdk-sandbox @ai-sdk/harness @ai-sdk/harness-claude-code @ai-sdk/provider-utils
20
21
  ```
21
22
 
22
23
  Choose one host transport:
@@ -38,7 +39,7 @@ import { createClaudeCode } from "@ai-sdk/harness-claude-code";
38
39
  import { createCoderWorkspace } from "@coder/ai-sdk-sandbox";
39
40
 
40
41
  const agent = new HarnessAgent({
41
- harness: createClaudeCode({ thinking: "adaptive" }),
42
+ harness: createClaudeCode({ thinking: { type: "adaptive" } }),
42
43
  sandbox: createCoderWorkspace({ workspace: "my-dev-workspace" }),
43
44
  instructions: "You are a careful coding assistant.",
44
45
  });
@@ -92,7 +93,7 @@ the session ends. Add a `create` block:
92
93
 
93
94
  ```ts
94
95
  const agent = new HarnessAgent({
95
- harness: createClaudeCode({ thinking: "adaptive" }),
96
+ harness: createClaudeCode({ thinking: { type: "adaptive" } }),
96
97
  sandbox: createCoderWorkspace({
97
98
  create: {
98
99
  template: "docker", // required: the template to create from
@@ -201,7 +202,7 @@ For an interactive chat in your terminal instead of one-shot `generate()` calls,
201
202
  wrap the same agent with the AI SDK terminal UI ([`@ai-sdk/tui`](https://ai-sdk.dev/v7/docs/ai-sdk-harnesses/terminal-ui)):
202
203
 
203
204
  ```bash
204
- npm add @ai-sdk/tui
205
+ pnpm add @ai-sdk/tui
205
206
  ```
206
207
 
207
208
  The TUI drives a session-less agent, so adapt the `HarnessAgent` (whose
@@ -215,7 +216,7 @@ import { runAgentTUI, type AgentTUIAgent } from "@ai-sdk/tui";
215
216
  import { createCoderWorkspace } from "@coder/ai-sdk-sandbox";
216
217
 
217
218
  const agent = new HarnessAgent({
218
- harness: createClaudeCode({ thinking: "adaptive" }),
219
+ harness: createClaudeCode({ thinking: { type: "adaptive" } }),
219
220
  sandbox: createCoderWorkspace({ workspace: "my-dev-ws" }),
220
221
  // or, to create a fresh workspace per session from a template:
221
222
  // sandbox: createCoderWorkspace({ create: { template: 'claude-code-test' } }),
@@ -245,17 +246,181 @@ Esc or Ctrl+C).
245
246
 
246
247
  Because the bridge runs inside the workspace, the workspace image must have:
247
248
 
248
- - **Node.js** (the docs use `node24`). The bridge is `node bridge.mjs`.
249
- - **pnpm** available on PATH (e.g. via `corepack enable`) — the adapter uses it to
250
- bootstrap the bridge.
251
- - **Outbound network access** to the npm registry (the adapter `pnpm install`s
252
- the bridge's dependencies + the Claude Code CLI on first use) and to the model
253
- API (`api.anthropic.com` for Claude Code, `api.openai.com` for Codex). Bake the
254
- dependencies into the image to avoid per-session installs.
249
+ - **Node.js ≥ 22** on the login-shell PATH. The bridge is `node bridge.mjs`, the
250
+ native transport's relay is `node -e`, and the pinned
251
+ `@anthropic-ai/claude-code` bridge dependency requires Node 22+.
252
+ - **pnpm** on the login-shell PATH (e.g. `npm install -g pnpm`) — the adapter's
253
+ bootstrap runs `pnpm install --frozen-lockfile` to install the bridge's
254
+ dependencies.
255
+ - **Outbound network access** to the npm registry (the bootstrap downloads the
256
+ bridge's dependencies — including the Claude Code CLI's ~250 MB platform
257
+ binary, which ships as an npm package — on first use) and to the model API
258
+ (`api.anthropic.com` for Claude Code, `api.openai.com` for Codex). Pre-bake
259
+ the bootstrap into the image to skip the registry entirely — see
260
+ [Template authoring](#template-authoring-zero-install-sessions).
255
261
  - **The model API key** available to the bridge — `ANTHROPIC_API_KEY` /
256
262
  `OPENAI_API_KEY`. Configure it through the adapter's `auth` option or ensure it
257
263
  is present in the workspace environment.
258
- - `bash` and `base64` (standard on any Linux dev image).
264
+ - `bash` and `base64` (remote commands run through `bash -lc`; file I/O is
265
+ base64-encoded), plus `stty` for the native transport's relay bootstrap. All
266
+ standard on any Linux dev image.
267
+
268
+ ## Template authoring: zero-install sessions
269
+
270
+ How to build a Coder template whose workspaces start harness sessions with
271
+ **zero runtime install latency**. Generic template authoring is covered by the
272
+ [Coder template docs](https://coder.com/docs/admin/templates) and
273
+ [Coder's example Docker template](https://github.com/coder/coder/tree/main/examples/templates/docker);
274
+ this section covers only the ai-sdk-specific delta. A validated
275
+ Dockerfile + `main.tf` pair lives in [`examples/template/`](./examples/template/).
276
+
277
+ ### What the first session installs
278
+
279
+ On the first session in a fresh workspace, the Claude Code adapter applies its
280
+ bootstrap recipe relative to the session's default working directory (`$HOME`
281
+ for this provider):
282
+
283
+ 1. It writes `package.json`, `pnpm-lock.yaml`, and `bridge.mjs` (assets shipped
284
+ inside `@ai-sdk/harness-claude-code`) to `~/.harness-bootstrap/claude-code/`.
285
+ 2. It runs `pnpm install --frozen-lockfile --store-dir .pnpm-store` there. This
286
+ is the expensive step: ~300 MB from the npm registry — the bridge
287
+ dependencies include the `@anthropic-ai/claude-code` platform binary — taking
288
+ tens of seconds to minutes depending on the network.
289
+ 3. It runs the CLI's `install.cjs` (links the platform binary npm delivered; no
290
+ extra download) and `./node_modules/.bin/claude --version`.
291
+ 4. It writes a `.bootstrap-<recipe-hash>.ok` marker next to them. Later sessions
292
+ see the marker and skip all of the above — a single file read.
293
+
294
+ Zero-install means baking the results of steps 1–3 into the image **at that
295
+ exact path**. The first session in each workspace then replays the recipe as a
296
+ fast offline no-op (~4 s measured; pnpm prints `Already up to date` and
297
+ downloads nothing) and writes the marker; every session after that is a single
298
+ file read.
299
+
300
+ ### Step 1 — Dockerfile: pre-bake the bootstrap
301
+
302
+ [`examples/template/Dockerfile`](./examples/template/Dockerfile) installs the
303
+ [workspace requirements](#workspace-requirements) (Node 24 + pnpm on PATH,
304
+ bash/coreutils) and replays the recipe at build time — same files, same
305
+ commands, same absolute path:
306
+
307
+ ```dockerfile
308
+ ARG HARNESS_CLAUDE_CODE_VERSION=1.0.76
309
+ RUN mkdir -p /home/${USER}/.harness-bootstrap/claude-code \
310
+ && cd /tmp \
311
+ && npm pack @ai-sdk/harness-claude-code@${HARNESS_CLAUDE_CODE_VERSION} \
312
+ && tar -xzf ai-sdk-harness-claude-code-${HARNESS_CLAUDE_CODE_VERSION}.tgz \
313
+ && cp package/dist/bridge/package.json package/dist/bridge/pnpm-lock.yaml \
314
+ /home/${USER}/.harness-bootstrap/claude-code/ \
315
+ && cp package/dist/bridge/index.mjs \
316
+ /home/${USER}/.harness-bootstrap/claude-code/bridge.mjs \
317
+ && rm -rf package ai-sdk-harness-claude-code-${HARNESS_CLAUDE_CODE_VERSION}.tgz \
318
+ && cd /home/${USER}/.harness-bootstrap/claude-code \
319
+ && pnpm install --frozen-lockfile --store-dir .pnpm-store \
320
+ && node node_modules/@anthropic-ai/claude-code/install.cjs \
321
+ && ./node_modules/.bin/claude --version
322
+ ```
323
+
324
+ Rules that make or break the pre-bake:
325
+
326
+ - **Bake at the final absolute path.** pnpm records the store location as an
327
+ absolute path (in `node_modules/.modules.yaml`), so a cache baked at, say,
328
+ `/opt/...` and copied into `$HOME` later makes the session's `pnpm install`
329
+ abort (`ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY` — pnpm wants to purge and
330
+ reinstall, and a session has no TTY to confirm).
331
+ - **Pin the adapter version your application resolves** (find it with
332
+ `pnpm why @ai-sdk/harness-claude-code`, or in your lockfile) and pass it as
333
+ `--build-arg HARNESS_CLAUDE_CODE_VERSION=...`. The recipe and its marker hash
334
+ derive from the adapter's shipped assets. A mismatch is not fatal — the
335
+ recipe re-runs in the same directory and downloads only the delta — but it
336
+ reintroduces registry traffic on first sessions.
337
+ - **Build for the workspace CPU architecture** — the pre-bake fetches the
338
+ builder platform's `claude` binary.
339
+
340
+ ### Step 2 — main.tf: get the cache into the home volume
341
+
342
+ [`examples/template/main.tf`](./examples/template/main.tf) is Coder's example
343
+ Docker template minus the IDE modules, with three ai-sdk-specific choices:
344
+
345
+ - **The workspace image is the pre-baked one** (`variable "image"`); push it to
346
+ a registry your Coder provisioners can pull from.
347
+ - **`startup_script_behavior = "blocking"`** on `coder_agent`: this provider's
348
+ create mode waits for `lifecycle_state: ready` before the harness runs, and
349
+ blocking makes ready mean "startup script finished" (the attribute defaults to
350
+ `non-blocking`, in which case ready does not wait for the script).
351
+ - **The home volume seeds itself from the image.** Docker populates a fresh
352
+ (empty) named volume with the image's content at the mount path, so
353
+ `/home/coder/.harness-bootstrap` lands in every new workspace's volume with no
354
+ startup-script copying. No port configuration is needed either: the provider
355
+ forwards the bridge port itself (OpenSSH `-L` or the native relay), not
356
+ through template port shares.
357
+
358
+ For volume types that do _not_ copy image content (a Kubernetes PVC mounted at
359
+ `/home/coder` shadows it with an empty filesystem), stash the bake outside the
360
+ mount and restore it — at the same absolute path — on first start:
361
+
362
+ ```dockerfile
363
+ # Dockerfile, after the pre-bake step:
364
+ RUN sudo cp -a /home/${USER}/.harness-bootstrap /opt/harness-bootstrap-stash
365
+ ```
366
+
367
+ ```hcl
368
+ startup_script = <<-EOT
369
+ set -e
370
+ if [ ! -d ~/.harness-bootstrap ]; then
371
+ cp -a /opt/harness-bootstrap-stash ~/.harness-bootstrap
372
+ fi
373
+ EOT
374
+ ```
375
+
376
+ (`cp -a` preserves the hardlinks between the pnpm store and `node_modules`, so
377
+ the stash and the restore each stay ~300 MB rather than doubling.)
378
+
379
+ Build, push, and point the provider at the template
380
+ ([creating templates](https://coder.com/docs/admin/templates/creating-templates)):
381
+
382
+ ```bash
383
+ cd examples/template
384
+ docker build -t <registry>/ai-sdk-sandbox-workspace:v1 .
385
+ docker push <registry>/ai-sdk-sandbox-workspace:v1
386
+ coder templates push ai-sdk-sandbox --variable image=<registry>/ai-sdk-sandbox-workspace:v1
387
+ ```
388
+
389
+ ```ts
390
+ createCoderWorkspace({ create: { template: "ai-sdk-sandbox" } });
391
+ ```
392
+
393
+ ### Sizing
394
+
395
+ Measured on the example image (linux/amd64): ~730 MB total — ~430 MB for
396
+ Ubuntu 24.04 + Node 24 + pnpm, ~300 MB for the bootstrap cache (dominated by
397
+ the pnpm store; `node_modules` hardlinks into it). Each workspace's home volume
398
+ receives the ~300 MB seeded cache; leave headroom for per-session work
399
+ directories (`claude-code-<sessionId>/`) and whatever your agents check out.
400
+
401
+ ### Verifying zero installs
402
+
403
+ **Image-level (no Coder deployment needed).** Replay the bootstrap on a fresh
404
+ volume with networking disabled — it must succeed offline:
405
+
406
+ ```bash
407
+ docker volume rm -f probe-home && docker volume create probe-home
408
+ docker run --rm --network none -v probe-home:/home/coder \
409
+ <image> bash -lc 'cd ~/.harness-bootstrap/claude-code \
410
+ && pnpm install --frozen-lockfile --store-dir .pnpm-store \
411
+ && node node_modules/@anthropic-ai/claude-code/install.cjs \
412
+ && ./node_modules/.bin/claude --version'
413
+ ```
414
+
415
+ Expect `Already up to date` from pnpm and the CLI version banner in a few
416
+ seconds — proof that a session's bootstrap needs no registry access.
417
+
418
+ **Session-level.** After the first session against a workspace from the
419
+ template, `~/.harness-bootstrap/claude-code/` must contain a `.bootstrap-*.ok`
420
+ marker (later sessions skip the recipe entirely). If session creation instead
421
+ stalls for minutes with pnpm download progress in the bootstrap, the image's
422
+ pinned adapter version doesn't match the application's (see the pinning rule
423
+ above).
259
424
 
260
425
  ## Settings
261
426
 
@@ -382,34 +547,37 @@ and a full Claude Code turn with tool use (`scripts/e2e-claude.ts`).
382
547
  native transport refuses to guess.
383
548
  - `@ai-sdk/sandbox-just-bash` cannot expose ports and is rejected by bridge-backed
384
549
  adapters — this provider exists precisely to provide that port.
385
- - To run Claude Code / Codex, the **workspace** image needs Node.js (the adapter
386
- installs the bridge + CLI on first use) and egress to the npm registry and the
387
- model API. Provide the API key via the adapter's `auth` option.
550
+ - To run Claude Code / Codex, the **workspace** image needs Node.js ≥ 22 (the
551
+ adapter installs the bridge + CLI on first use) and egress to the npm registry
552
+ and the model API unless the image pre-bakes the bootstrap (see
553
+ [Template authoring](#template-authoring-zero-install-sessions)). Provide the
554
+ API key via the adapter's `auth` option.
388
555
 
389
556
  ## Development
390
557
 
391
558
  ```bash
392
- npm install
393
- npm run typecheck # tsc against the real harness types
394
- npm test # vitest: unit + local integration (fake `coder` + `ssh`)
395
- npm run build # tsup dist/ (ESM + d.ts)
559
+ # From this package's directory (packages/sandbox):
560
+ pnpm install
561
+ pnpm typecheck # tsc against the real harness types
562
+ pnpm test # vitest: unit + local integration (fake `coder` + `ssh`)
563
+ pnpm build # tsup → dist/ (ESM + d.ts)
396
564
 
397
- # Formatting & linting (Biome):
398
- npm run format # biome format --write . (apply formatting)
399
- npm run lint # biome lint . (report lint issues)
400
- npm run check # biome check . (format + lint, read-only; for CI)
565
+ # Formatting & linting are root-level scripts (`-w` runs them from anywhere in the repo):
566
+ pnpm -w format # oxfmt (apply formatting)
567
+ pnpm -w lint # oxlint (report lint issues)
568
+ pnpm -w check # format check + lint + typecheck (CI gate)
401
569
 
402
570
  # End-to-end against a real workspace (needs the coder CLI + a running workspace):
403
- npm run verify:real -- my-ws
571
+ pnpm verify:real my-ws
404
572
 
405
- # The same contract through Coderd directly. The CLI is used only to retrieve
406
- # the already-authenticated token for this shell; CoderNativeTransport never invokes it:
573
+ # The same contract through Coderd directly. The CLI is used only to mint a
574
+ # token for this shell; CoderNativeTransport never invokes it:
407
575
  CODER_URL=https://coder.example.com \
408
- CODER_SESSION_TOKEN="$(coder login token)" \
409
- npm run verify:native -- my-ws
576
+ CODER_SESSION_TOKEN="$(coder tokens create --name ai-sdk-sandbox)" \
577
+ pnpm verify:native my-ws
410
578
 
411
579
  # End-to-end of create mode (creates a throwaway workspace, then deletes it):
412
- npm run verify:create -- docker
580
+ pnpm verify:create docker
413
581
  ```
414
582
 
415
583
  The local integration tests exercise the real transport (argument building,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coder/ai-sdk-sandbox",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Coder workspace sandbox provider for the Vercel AI SDK v7 HarnessAgent",
5
5
  "keywords": [
6
6
  "ai-sdk",