From 8af152f0b350f1c71c519d17c144bdd2f097fcaf Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Wed, 28 Jan 2026 02:31:43 -0800 Subject: [PATCH] fix(examples): use SDK to install agents instead of CLI command --- examples/daytona/src/daytona-fallback.ts | 20 +++-- examples/daytona/src/daytona.ts | 1 - examples/e2b/package.json | 3 +- examples/e2b/src/e2b.ts | 19 +++- examples/shared/package.json | 3 + examples/shared/src/sandbox-agent-client.ts | 98 +++++++++++---------- pnpm-lock.yaml | 7 ++ 7 files changed, 94 insertions(+), 57 deletions(-) diff --git a/examples/daytona/src/daytona-fallback.ts b/examples/daytona/src/daytona-fallback.ts index 7005981..eeb21c0 100644 --- a/examples/daytona/src/daytona-fallback.ts +++ b/examples/daytona/src/daytona-fallback.ts @@ -11,7 +11,7 @@ if ( } const SNAPSHOT = "sandbox-agent-ready"; -const BINARY = "/usr/local/bin/sandbox-agent"; +const AGENT_BIN_DIR = "/root/.local/share/sandbox-agent/bin"; const daytona = new Daytona(); @@ -27,11 +27,18 @@ if (!hasSnapshot) { image: Image.base("ubuntu:22.04").runCommands( // Install dependencies "apt-get update && apt-get install -y curl ca-certificates", - // Install sandbox-agent via install script + // Install sandbox-agent "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh", - // Pre-install agents using sandbox-agent CLI - "sandbox-agent install-agent claude", - "sandbox-agent install-agent codex", + // Create agent bin directory + `mkdir -p ${AGENT_BIN_DIR}`, + // Install Claude: get latest version, download binary + `CLAUDE_VERSION=$(curl -fsSL https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/latest) && ` + + `curl -fsSL -o ${AGENT_BIN_DIR}/claude "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/$CLAUDE_VERSION/linux-x64/claude" && ` + + `chmod +x ${AGENT_BIN_DIR}/claude`, + // Install Codex: download tarball, extract binary + `curl -fsSL -L https://github.com/openai/codex/releases/latest/download/codex-x86_64-unknown-linux-musl.tar.gz | tar -xzf - -C /tmp && ` + + `find /tmp -name 'codex-x86_64-unknown-linux-musl' -exec mv {} ${AGENT_BIN_DIR}/codex \\; && ` + + `chmod +x ${AGENT_BIN_DIR}/codex`, ), }, { onLogs: (log) => console.log(` ${log}`) }, @@ -47,12 +54,11 @@ if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_ const sandbox = await daytona.create({ snapshot: SNAPSHOT, envVars, - autoStopInterval: 0, }); console.log("Starting server..."); await sandbox.process.executeCommand( - `nohup ${BINARY} server --no-token --host 0.0.0.0 --port 3000 >/tmp/sandbox-agent.log 2>&1 &`, + "nohup sandbox-agent server --no-token --host 0.0.0.0 --port 3000 >/tmp/sandbox-agent.log 2>&1 &", ); const baseUrl = (await sandbox.getSignedPreviewUrl(3000, 4 * 60 * 60)).url; diff --git a/examples/daytona/src/daytona.ts b/examples/daytona/src/daytona.ts index 7005981..3eac968 100644 --- a/examples/daytona/src/daytona.ts +++ b/examples/daytona/src/daytona.ts @@ -47,7 +47,6 @@ if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_ const sandbox = await daytona.create({ snapshot: SNAPSHOT, envVars, - autoStopInterval: 0, }); console.log("Starting server..."); diff --git a/examples/e2b/package.json b/examples/e2b/package.json index 1d9f84e..be413c6 100644 --- a/examples/e2b/package.json +++ b/examples/e2b/package.json @@ -8,7 +8,8 @@ }, "dependencies": { "@e2b/code-interpreter": "latest", - "@sandbox-agent/example-shared": "workspace:*" + "@sandbox-agent/example-shared": "workspace:*", + "sandbox-agent": "workspace:*" }, "devDependencies": { "@types/node": "latest", diff --git a/examples/e2b/src/e2b.ts b/examples/e2b/src/e2b.ts index 4e54767..fa8bb1e 100644 --- a/examples/e2b/src/e2b.ts +++ b/examples/e2b/src/e2b.ts @@ -1,4 +1,5 @@ import { Sandbox } from "@e2b/code-interpreter"; +import { SandboxAgent } from "sandbox-agent"; import { logInspectorUrl, runPrompt } from "@sandbox-agent/example-shared"; if (!process.env.E2B_API_KEY || (!process.env.OPENAI_API_KEY && !process.env.ANTHROPIC_API_KEY)) { @@ -11,8 +12,6 @@ const run = (cmd: string) => sandbox.commands.run(cmd); console.log("Installing sandbox-agent..."); await run("curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"); -await run("sandbox-agent install-agent claude"); -await run("sandbox-agent install-agent codex"); console.log("Starting server..."); await sandbox.commands.run("sandbox-agent server --no-token --host 0.0.0.0 --port 3000", { background: true }); @@ -20,6 +19,22 @@ await sandbox.commands.run("sandbox-agent server --no-token --host 0.0.0.0 --por const baseUrl = `https://${sandbox.getHost(3000)}`; logInspectorUrl({ baseUrl }); +// Wait for server to be ready +console.log("Waiting for server..."); +const client = await SandboxAgent.connect({ baseUrl }); +for (let i = 0; i < 30; i++) { + try { + await client.getHealth(); + break; + } catch { + await new Promise((r) => setTimeout(r, 1000)); + } +} + +console.log("Installing agents..."); +await client.installAgent("claude"); +await client.installAgent("codex"); + const cleanup = async () => { console.log("Cleaning up..."); await sandbox.kill(); diff --git a/examples/shared/package.json b/examples/shared/package.json index c9870a6..18906ef 100644 --- a/examples/shared/package.json +++ b/examples/shared/package.json @@ -8,6 +8,9 @@ "scripts": { "typecheck": "tsc --noEmit" }, + "dependencies": { + "sandbox-agent": "workspace:*" + }, "devDependencies": { "@types/node": "latest", "typescript": "latest" diff --git a/examples/shared/src/sandbox-agent-client.ts b/examples/shared/src/sandbox-agent-client.ts index 55c168d..817d910 100644 --- a/examples/shared/src/sandbox-agent-client.ts +++ b/examples/shared/src/sandbox-agent-client.ts @@ -1,6 +1,7 @@ import { createInterface } from "node:readline/promises"; import { randomUUID } from "node:crypto"; import { setTimeout as delay } from "node:timers/promises"; +import { SandboxAgent } from "sandbox-agent"; export function normalizeBaseUrl(baseUrl: string): string { return baseUrl.replace(/\/+$/, ""); @@ -279,54 +280,57 @@ export async function runPrompt({ extraHeaders?: Record; agentId?: string; }): Promise { - const normalized = normalizeBaseUrl(baseUrl); - const sessionId = await createSession({ baseUrl, token, extraHeaders, agentId }); + const client = await SandboxAgent.connect({ + baseUrl, + token, + headers: extraHeaders, + }); + + const sessionId = randomUUID(); + await client.createSession(sessionId, { + agent: agentId || process.env.SANDBOX_AGENT || "claude", + }); console.log(`Session ${sessionId} ready. Press Ctrl+C to quit.`); - // Connect to SSE event stream - const headers = buildHeaders({ token, extraHeaders }); - const sseResponse = await fetch(`${normalized}/v1/sessions/${sessionId}/events/sse`, { headers }); - if (!sseResponse.ok || !sseResponse.body) { - throw new Error(`Failed to connect to SSE: ${sseResponse.status}`); - } + let isThinking = false; + let hasStartedOutput = false; + let turnResolve: (() => void) | null = null; - const reader = sseResponse.body.getReader(); - const decoder = new TextDecoder(); - let buffer = ""; - let lastSeq = 0; - - // Process SSE events in background + // Stream events in background using SDK const processEvents = async () => { - while (true) { - const { done, value } = await reader.read(); - if (done) break; + for await (const event of client.streamEvents(sessionId)) { + // Show thinking indicator when assistant starts + if (event.type === "item.started") { + const item = (event.data as any)?.item; + if (item?.role === "assistant") { + isThinking = true; + hasStartedOutput = false; + process.stdout.write("Thinking..."); + } + } - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split("\n"); - buffer = lines.pop() || ""; - - for (const line of lines) { - if (!line.startsWith("data: ")) continue; - const data = line.slice(6); - if (data === "[DONE]") continue; - - try { - const event = JSON.parse(data); - if (event.sequence <= lastSeq) continue; - lastSeq = event.sequence; - - // Print text deltas - if (event.type === "item.delta" && event.data?.delta) { - const delta = event.data.delta; - const text = typeof delta === "string" ? delta : delta.type === "text" ? delta.text || "" : ""; - if (text) process.stdout.write(text); + // Print text deltas + if (event.type === "item.delta") { + const delta = (event.data as any)?.delta; + if (delta) { + if (isThinking && !hasStartedOutput) { + process.stdout.write("\r\x1b[K"); // Clear line + hasStartedOutput = true; } + const text = typeof delta === "string" ? delta : delta.type === "text" ? delta.text || "" : ""; + if (text) process.stdout.write(text); + } + } - // Print newline after completed assistant message - if (event.type === "item.completed" && event.data?.item?.role === "assistant") { - process.stdout.write("\n> "); - } - } catch {} + // Signal turn complete + if (event.type === "item.completed") { + const item = (event.data as any)?.item; + if (item?.role === "assistant") { + isThinking = false; + process.stdout.write("\n"); + turnResolve?.(); + turnResolve = null; + } } } }; @@ -338,14 +342,16 @@ export async function runPrompt({ const line = await rl.question("> "); if (!line.trim()) continue; + const turnComplete = new Promise((resolve) => { + turnResolve = resolve; + }); + try { - await fetch(`${normalized}/v1/sessions/${sessionId}/messages`, { - method: "POST", - headers: buildHeaders({ token, extraHeaders, contentType: true }), - body: JSON.stringify({ message: line.trim() }), - }); + await client.postMessage(sessionId, { message: line.trim() }); + await turnComplete; } catch (error) { console.error(error instanceof Error ? error.message : error); + turnResolve = null; } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1840e43..ede8900 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,6 +61,9 @@ importers: '@sandbox-agent/example-shared': specifier: workspace:* version: link:../shared + sandbox-agent: + specifier: workspace:* + version: link:../../sdks/typescript devDependencies: '@types/node': specifier: latest @@ -73,6 +76,10 @@ importers: version: 5.9.3 examples/shared: + dependencies: + sandbox-agent: + specifier: workspace:* + version: link:../../sdks/typescript devDependencies: '@types/node': specifier: latest