@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.
- package/README.md +199 -31
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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**
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
- **
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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` (
|
|
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
|
|
386
|
-
installs the bridge + CLI on first use) and egress to the npm registry
|
|
387
|
-
model API
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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 (
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
571
|
+
pnpm verify:real my-ws
|
|
404
572
|
|
|
405
|
-
# The same contract through Coderd directly. The CLI is used only to
|
|
406
|
-
#
|
|
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
|
|
409
|
-
|
|
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
|
-
|
|
580
|
+
pnpm verify:create docker
|
|
413
581
|
```
|
|
414
582
|
|
|
415
583
|
The local integration tests exercise the real transport (argument building,
|