diff --git a/examples/daytona/src/daytona-with-snapshot.ts b/examples/daytona/src/daytona-with-snapshot.ts index 53bffb1..d0d1ce8 100644 --- a/examples/daytona/src/daytona-with-snapshot.ts +++ b/examples/daytona/src/daytona-with-snapshot.ts @@ -1,5 +1,5 @@ import { Daytona, Image } from "@daytonaio/sdk"; -import { logInspectorUrl, runPrompt } from "@sandbox-agent/example-shared"; +import { runPrompt } from "@sandbox-agent/example-shared"; const daytona = new Daytona(); @@ -23,7 +23,6 @@ await sandbox.process.executeCommand( ); const baseUrl = (await sandbox.getSignedPreviewUrl(3000, 4 * 60 * 60)).url; -logInspectorUrl({ baseUrl }); const cleanup = async () => { await sandbox.delete(60); diff --git a/examples/daytona/src/daytona.ts b/examples/daytona/src/daytona.ts index 24b329a..4fe6a3b 100644 --- a/examples/daytona/src/daytona.ts +++ b/examples/daytona/src/daytona.ts @@ -1,5 +1,5 @@ import { Daytona } from "@daytonaio/sdk"; -import { logInspectorUrl, runPrompt } from "@sandbox-agent/example-shared"; +import { runPrompt } from "@sandbox-agent/example-shared"; const daytona = new Daytona(); @@ -24,7 +24,6 @@ await sandbox.process.executeCommand( ); const baseUrl = (await sandbox.getSignedPreviewUrl(3000, 4 * 60 * 60)).url; -logInspectorUrl({ baseUrl }); const cleanup = async () => { await sandbox.delete(60); diff --git a/examples/docker/src/docker.ts b/examples/docker/src/docker.ts index 392977c..20fafe4 100644 --- a/examples/docker/src/docker.ts +++ b/examples/docker/src/docker.ts @@ -1,84 +1,56 @@ import Docker from "dockerode"; -import { logInspectorUrl, runPrompt, waitForHealth } from "@sandbox-agent/example-shared"; +import { runPrompt, waitForHealth } from "@sandbox-agent/example-shared"; -// Alpine is required because Claude Code binary is built for musl libc const IMAGE = "alpine:latest"; const PORT = 3000; -export async function setupDockerSandboxAgent(): Promise<{ - baseUrl: string; - token?: string; - cleanup: () => Promise; -}> { - const docker = new Docker({ socketPath: "/var/run/docker.sock" }); +const docker = new Docker({ socketPath: "/var/run/docker.sock" }); - // Pull image if needed - try { - await docker.getImage(IMAGE).inspect(); - } catch { - console.log(`Pulling ${IMAGE}...`); - await new Promise((resolve, reject) => { - docker.pull(IMAGE, (err: Error | null, stream: NodeJS.ReadableStream) => { - if (err) return reject(err); - docker.modem.followProgress(stream, (err: Error | null) => err ? reject(err) : resolve()); - }); +// Pull image if needed +try { + await docker.getImage(IMAGE).inspect(); +} catch { + console.log(`Pulling ${IMAGE}...`); + await new Promise((resolve, reject) => { + docker.pull(IMAGE, (err: Error | null, stream: NodeJS.ReadableStream) => { + if (err) return reject(err); + docker.modem.followProgress(stream, (err: Error | null) => err ? reject(err) : resolve()); }); - } - - console.log("Starting container..."); - const container = await docker.createContainer({ - Image: IMAGE, - Cmd: ["sh", "-c", [ - // Install dependencies (Alpine uses apk, not apt-get) - "apk add --no-cache curl ca-certificates libstdc++ libgcc bash", - "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh", - "sandbox-agent install-agent claude", - "sandbox-agent install-agent codex", - `sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`, - ].join(" && ")], - Env: [ - process.env.ANTHROPIC_API_KEY ? `ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}` : "", - process.env.OPENAI_API_KEY ? `OPENAI_API_KEY=${process.env.OPENAI_API_KEY}` : "", - ].filter(Boolean), - ExposedPorts: { [`${PORT}/tcp`]: {} }, - HostConfig: { - AutoRemove: true, - PortBindings: { [`${PORT}/tcp`]: [{ HostPort: `${PORT}` }] }, - }, }); - await container.start(); - - const baseUrl = `http://127.0.0.1:${PORT}`; - await waitForHealth({ baseUrl }); - - const cleanup = async () => { - console.log("Cleaning up..."); - try { await container.stop({ t: 5 }); } catch {} - try { await container.remove({ force: true }); } catch {} - }; - - return { baseUrl, cleanup }; } -// Run interactively if executed directly -const isMainModule = import.meta.url === `file://${process.argv[1]}`; -if (isMainModule) { - if (!process.env.OPENAI_API_KEY && !process.env.ANTHROPIC_API_KEY) { - throw new Error("OPENAI_API_KEY or ANTHROPIC_API_KEY required"); - } +console.log("Starting container..."); +const container = await docker.createContainer({ + Image: IMAGE, + Cmd: ["sh", "-c", [ + "apk add --no-cache curl ca-certificates libstdc++ libgcc bash", + "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh", + "sandbox-agent install-agent claude", + "sandbox-agent install-agent codex", + `sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`, + ].join(" && ")], + Env: [ + process.env.ANTHROPIC_API_KEY ? `ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}` : "", + process.env.OPENAI_API_KEY ? `OPENAI_API_KEY=${process.env.OPENAI_API_KEY}` : "", + ].filter(Boolean), + ExposedPorts: { [`${PORT}/tcp`]: {} }, + HostConfig: { + AutoRemove: true, + PortBindings: { [`${PORT}/tcp`]: [{ HostPort: `${PORT}` }] }, + }, +}); +await container.start(); - const { baseUrl, cleanup } = await setupDockerSandboxAgent(); - logInspectorUrl({ baseUrl }); +const baseUrl = `http://127.0.0.1:${PORT}`; +await waitForHealth({ baseUrl }); - process.once("SIGINT", async () => { - await cleanup(); - process.exit(0); - }); - process.once("SIGTERM", async () => { - await cleanup(); - process.exit(0); - }); +const cleanup = async () => { + try { await container.stop({ t: 5 }); } catch {} + try { await container.remove({ force: true }); } catch {} + process.exit(0); +}; +process.once("SIGINT", cleanup); +process.once("SIGTERM", cleanup); - await runPrompt(baseUrl); - await cleanup(); -} +await runPrompt(baseUrl); +await cleanup(); diff --git a/examples/e2b/src/e2b.ts b/examples/e2b/src/e2b.ts index 43c1c22..8d54c88 100644 --- a/examples/e2b/src/e2b.ts +++ b/examples/e2b/src/e2b.ts @@ -1,65 +1,40 @@ import { Sandbox } from "@e2b/code-interpreter"; -import { logInspectorUrl, runPrompt, waitForHealth } from "@sandbox-agent/example-shared"; +import { runPrompt, waitForHealth } from "@sandbox-agent/example-shared"; -export async function setupE2BSandboxAgent(): Promise<{ - baseUrl: string; - token?: string; - cleanup: () => Promise; -}> { - const envs: Record = {}; - if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; - if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY; +const envs: Record = {}; +if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; +if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY; - const sandbox = await Sandbox.create({ allowInternetAccess: true, envs }); - const run = async (cmd: string) => { - const result = await sandbox.commands.run(cmd); - if (result.exitCode !== 0) throw new Error(`Command failed: ${cmd}\n${result.stderr}`); - return result; - }; +console.log("Creating E2B sandbox..."); +const sandbox = await Sandbox.create({ allowInternetAccess: true, envs }); - console.log("Installing sandbox-agent..."); - await run("curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"); +const run = async (cmd: string) => { + const result = await sandbox.commands.run(cmd); + if (result.exitCode !== 0) throw new Error(`Command failed: ${cmd}\n${result.stderr}`); + return result; +}; - console.log("Installing agents..."); - await run("sandbox-agent install-agent claude"); - await run("sandbox-agent install-agent codex"); +console.log("Installing sandbox-agent..."); +await run("curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"); - console.log("Starting server..."); - await sandbox.commands.run("sandbox-agent server --no-token --host 0.0.0.0 --port 3000", { background: true }); +console.log("Installing agents..."); +await run("sandbox-agent install-agent claude"); +await run("sandbox-agent install-agent codex"); - const baseUrl = `https://${sandbox.getHost(3000)}`; +console.log("Starting server..."); +await sandbox.commands.run("sandbox-agent server --no-token --host 0.0.0.0 --port 3000", { background: true }); - // Wait for server to be ready - console.log("Waiting for server..."); - await waitForHealth({ baseUrl }); +const baseUrl = `https://${sandbox.getHost(3000)}`; - const cleanup = async () => { - console.log("Cleaning up..."); - await sandbox.kill(); - }; +console.log("Waiting for server..."); +await waitForHealth({ baseUrl }); - return { baseUrl, cleanup }; -} +const cleanup = async () => { + await sandbox.kill(); + process.exit(0); +}; +process.once("SIGINT", cleanup); +process.once("SIGTERM", cleanup); -// Run interactively if executed directly -const isMainModule = import.meta.url === `file://${process.argv[1]}`; -if (isMainModule) { - if (!process.env.OPENAI_API_KEY && !process.env.ANTHROPIC_API_KEY) { - throw new Error("E2B_API_KEY and (OPENAI_API_KEY or ANTHROPIC_API_KEY) required"); - } - - const { baseUrl, cleanup } = await setupE2BSandboxAgent(); - logInspectorUrl({ baseUrl }); - - process.once("SIGINT", async () => { - await cleanup(); - process.exit(0); - }); - process.once("SIGTERM", async () => { - await cleanup(); - process.exit(0); - }); - - await runPrompt(baseUrl); - await cleanup(); -} +await runPrompt(baseUrl); +await cleanup(); diff --git a/examples/shared/src/sandbox-agent-client.ts b/examples/shared/src/sandbox-agent-client.ts index 5a4e598..8258ee8 100644 --- a/examples/shared/src/sandbox-agent-client.ts +++ b/examples/shared/src/sandbox-agent-client.ts @@ -118,6 +118,8 @@ function detectAgent(): string { } export async function runPrompt(baseUrl: string): Promise { + console.log(`UI: ${buildInspectorUrl({ baseUrl })}`); + const client = await SandboxAgent.connect({ baseUrl }); const agent = detectAgent(); diff --git a/examples/vercel/.gitignore b/examples/vercel/.gitignore new file mode 100644 index 0000000..c8a7336 --- /dev/null +++ b/examples/vercel/.gitignore @@ -0,0 +1,2 @@ +.vercel +.env*.local diff --git a/examples/vercel/src/vercel.ts b/examples/vercel/src/vercel.ts index 4135ba8..ed2d836 100644 --- a/examples/vercel/src/vercel.ts +++ b/examples/vercel/src/vercel.ts @@ -1,87 +1,51 @@ import { Sandbox } from "@vercel/sandbox"; -import { logInspectorUrl, runPrompt, waitForHealth } from "@sandbox-agent/example-shared"; +import { runPrompt, waitForHealth } from "@sandbox-agent/example-shared"; -export async function setupVercelSandboxAgent(): Promise<{ - baseUrl: string; - token?: string; - cleanup: () => Promise; -}> { - // Build env vars for agent API keys - const envs: Record = {}; - if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; - if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY; +const envs: Record = {}; +if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; +if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY; - // Create sandbox with port 3000 exposed - const sandbox = await Sandbox.create({ - runtime: "node24", - ports: [3000], - }); +console.log("Creating Vercel sandbox..."); +const sandbox = await Sandbox.create({ + runtime: "node24", + ports: [3000], +}); - // Helper to run commands and check exit code - const run = async (cmd: string, args: string[] = []) => { - const result = await sandbox.runCommand({ cmd, args, env: envs }); - if (result.exitCode !== 0) { - const stderr = await result.stderr(); - throw new Error(`Command failed: ${cmd} ${args.join(" ")}\n${stderr}`); - } - return result; - }; - - console.log("Installing sandbox-agent..."); - await run("sh", ["-c", "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"]); - - console.log("Installing agents..."); - await run("sandbox-agent", ["install-agent", "claude"]); - await run("sandbox-agent", ["install-agent", "codex"]); - - console.log("Starting server..."); - await sandbox.runCommand({ - cmd: "sandbox-agent", - args: ["server", "--no-token", "--host", "0.0.0.0", "--port", "3000"], - env: envs, - detached: true, - }); - - const baseUrl = sandbox.domain(3000); - - console.log("Waiting for server..."); - await waitForHealth({ baseUrl }); - - const cleanup = async () => { - console.log("Cleaning up..."); - await sandbox.stop(); - }; - - return { baseUrl, cleanup }; -} - -// Run interactively if executed directly -const isMainModule = import.meta.url === `file://${process.argv[1]}`; -if (isMainModule) { - // Check for Vercel auth - if (!process.env.VERCEL_OIDC_TOKEN && !process.env.VERCEL_ACCESS_TOKEN) { - throw new Error("Vercel authentication required. Run 'vercel env pull' or set VERCEL_ACCESS_TOKEN"); +const run = async (cmd: string, args: string[] = []) => { + const result = await sandbox.runCommand({ cmd, args, env: envs }); + if (result.exitCode !== 0) { + const stderr = await result.stderr(); + throw new Error(`Command failed: ${cmd} ${args.join(" ")}\n${stderr}`); } + return result; +}; - if (!process.env.OPENAI_API_KEY && !process.env.ANTHROPIC_API_KEY) { - throw new Error("OPENAI_API_KEY or ANTHROPIC_API_KEY required"); - } +console.log("Installing sandbox-agent..."); +await run("sh", ["-c", "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"]); - const { baseUrl, cleanup } = await setupVercelSandboxAgent(); - logInspectorUrl({ baseUrl }); +console.log("Installing agents..."); +await run("sandbox-agent", ["install-agent", "claude"]); +await run("sandbox-agent", ["install-agent", "codex"]); - process.once("SIGINT", async () => { - await cleanup(); - process.exit(0); - }); - process.once("SIGTERM", async () => { - await cleanup(); - process.exit(0); - }); +console.log("Starting server..."); +await sandbox.runCommand({ + cmd: "sandbox-agent", + args: ["server", "--no-token", "--host", "0.0.0.0", "--port", "3000"], + env: envs, + detached: true, +}); - await runPrompt({ - baseUrl, - autoApprovePermissions: process.env.AUTO_APPROVE_PERMISSIONS === "true", - }); - await cleanup(); -} +const baseUrl = sandbox.domain(3000); + +console.log("Waiting for server..."); +await waitForHealth({ baseUrl }); + +const cleanup = async () => { + await sandbox.stop(); + process.exit(0); +}; +process.once("SIGINT", cleanup); +process.once("SIGTERM", cleanup); + +await runPrompt(baseUrl); +await cleanup();