Add Modal and ComputeSDK built-in providers, update examples and docs

- Add `sandbox-agent/modal` provider using Modal SDK with node:22-slim image
- Add `sandbox-agent/computesdk` provider using ComputeSDK's unified sandbox API
- Update Modal and ComputeSDK examples to use new SDK providers
- Update Modal and ComputeSDK deploy docs with provider-based examples
- Add Modal to quickstart CodeGroup and docs.json navigation
- Add provider test entries for Modal and ComputeSDK
- Remove old standalone example files (modal.ts, computesdk.ts)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nathan Flurry 2026-03-15 14:45:52 -07:00
parent 441083ea2a
commit 20202c45ee
18 changed files with 377 additions and 646 deletions

View file

@ -0,0 +1,53 @@
import { compute } from "computesdk";
import type { SandboxProvider } from "./types.ts";
import { DEFAULT_AGENTS, SANDBOX_AGENT_INSTALL_SCRIPT } from "./shared.ts";
const DEFAULT_AGENT_PORT = 3000;
export interface ComputeSdkProviderOptions {
create?: {
envs?: Record<string, string>;
};
agentPort?: number;
}
export function computesdk(options: ComputeSdkProviderOptions = {}): SandboxProvider {
const agentPort = options.agentPort ?? DEFAULT_AGENT_PORT;
return {
name: "computesdk",
async create(): Promise<string> {
const envs = options.create?.envs;
const sandbox = await compute.sandbox.create({
envs: envs && Object.keys(envs).length > 0 ? envs : undefined,
});
const run = async (cmd: string, runOptions?: { background?: boolean }) => {
const result = await sandbox.runCommand(cmd, runOptions);
if (typeof result?.exitCode === "number" && result.exitCode !== 0) {
throw new Error(`computesdk command failed: ${cmd} (exit ${result.exitCode})\n${result.stderr || ""}`);
}
return result;
};
await run(`curl -fsSL ${SANDBOX_AGENT_INSTALL_SCRIPT} | sh`);
for (const agent of DEFAULT_AGENTS) {
await run(`sandbox-agent install-agent ${agent}`);
}
await run(`sandbox-agent server --no-token --host 0.0.0.0 --port ${agentPort}`, {
background: true,
});
return sandbox.sandboxId;
},
async destroy(sandboxId: string): Promise<void> {
const sandbox = await compute.sandbox.getById(sandboxId);
if (sandbox) await sandbox.destroy();
},
async getUrl(sandboxId: string): Promise<string> {
const sandbox = await compute.sandbox.getById(sandboxId);
if (!sandbox) throw new Error(`computesdk sandbox not found: ${sandboxId}`);
return sandbox.getUrl({ port: agentPort });
},
};
}

View file

@ -0,0 +1,75 @@
import { ModalClient } from "modal";
import type { SandboxProvider } from "./types.ts";
import { DEFAULT_AGENTS, SANDBOX_AGENT_INSTALL_SCRIPT, buildServerStartCommand } from "./shared.ts";
const DEFAULT_AGENT_PORT = 3000;
const DEFAULT_APP_NAME = "sandbox-agent";
export interface ModalProviderOptions {
create?: {
secrets?: Record<string, string>;
appName?: string;
};
agentPort?: number;
}
export function modal(options: ModalProviderOptions = {}): SandboxProvider {
const agentPort = options.agentPort ?? DEFAULT_AGENT_PORT;
const appName = options.create?.appName ?? DEFAULT_APP_NAME;
const client = new ModalClient();
return {
name: "modal",
async create(): Promise<string> {
const app = await client.apps.fromName(appName, { createIfMissing: true });
const image = client.images
.fromRegistry("node:22-slim")
.dockerfileCommands([
"RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*",
`RUN curl -fsSL ${SANDBOX_AGENT_INSTALL_SCRIPT} | sh`,
]);
const envVars = options.create?.secrets ?? {};
const secrets = Object.keys(envVars).length > 0 ? [await client.secrets.fromObject(envVars)] : [];
const sb = await client.sandboxes.create(app, image, {
encryptedPorts: [agentPort],
secrets,
});
const exec = async (cmd: string) => {
const p = await sb.exec(["bash", "-c", cmd], {
stdout: "pipe",
stderr: "pipe",
});
const exitCode = await p.wait();
if (exitCode !== 0) {
const stderr = await p.stderr.readText();
throw new Error(`modal command failed (exit ${exitCode}): ${cmd}\n${stderr}`);
}
};
for (const agent of DEFAULT_AGENTS) {
await exec(`sandbox-agent install-agent ${agent}`);
}
await sb.exec(["bash", "-c", buildServerStartCommand(agentPort)]);
return sb.sandboxId;
},
async destroy(sandboxId: string): Promise<void> {
const sb = await client.sandboxes.fromId(sandboxId);
await sb.terminate();
},
async getUrl(sandboxId: string): Promise<string> {
const sb = await client.sandboxes.fromId(sandboxId);
const tunnels = await sb.tunnels();
const tunnel = tunnels[agentPort];
if (!tunnel) {
throw new Error(`modal: no tunnel found for port ${agentPort}`);
}
return tunnel.url;
},
};
}