fix(examples): use SDK to install agents instead of CLI command

This commit is contained in:
Nathan Flurry 2026-01-28 02:31:43 -08:00
parent b98c848965
commit 8af152f0b3
7 changed files with 94 additions and 57 deletions

View file

@ -11,7 +11,7 @@ if (
} }
const SNAPSHOT = "sandbox-agent-ready"; 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(); const daytona = new Daytona();
@ -27,11 +27,18 @@ if (!hasSnapshot) {
image: Image.base("ubuntu:22.04").runCommands( image: Image.base("ubuntu:22.04").runCommands(
// Install dependencies // Install dependencies
"apt-get update && apt-get install -y curl ca-certificates", "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", "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh",
// Pre-install agents using sandbox-agent CLI // Create agent bin directory
"sandbox-agent install-agent claude", `mkdir -p ${AGENT_BIN_DIR}`,
"sandbox-agent install-agent codex", // 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}`) }, { 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({ const sandbox = await daytona.create({
snapshot: SNAPSHOT, snapshot: SNAPSHOT,
envVars, envVars,
autoStopInterval: 0,
}); });
console.log("Starting server..."); console.log("Starting server...");
await sandbox.process.executeCommand( 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; const baseUrl = (await sandbox.getSignedPreviewUrl(3000, 4 * 60 * 60)).url;

View file

@ -47,7 +47,6 @@ if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_
const sandbox = await daytona.create({ const sandbox = await daytona.create({
snapshot: SNAPSHOT, snapshot: SNAPSHOT,
envVars, envVars,
autoStopInterval: 0,
}); });
console.log("Starting server..."); console.log("Starting server...");

View file

@ -8,7 +8,8 @@
}, },
"dependencies": { "dependencies": {
"@e2b/code-interpreter": "latest", "@e2b/code-interpreter": "latest",
"@sandbox-agent/example-shared": "workspace:*" "@sandbox-agent/example-shared": "workspace:*",
"sandbox-agent": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "latest", "@types/node": "latest",

View file

@ -1,4 +1,5 @@
import { Sandbox } from "@e2b/code-interpreter"; import { Sandbox } from "@e2b/code-interpreter";
import { SandboxAgent } from "sandbox-agent";
import { logInspectorUrl, runPrompt } from "@sandbox-agent/example-shared"; import { logInspectorUrl, runPrompt } from "@sandbox-agent/example-shared";
if (!process.env.E2B_API_KEY || (!process.env.OPENAI_API_KEY && !process.env.ANTHROPIC_API_KEY)) { 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..."); console.log("Installing sandbox-agent...");
await run("curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"); 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..."); console.log("Starting server...");
await sandbox.commands.run("sandbox-agent server --no-token --host 0.0.0.0 --port 3000", { background: true }); 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)}`; const baseUrl = `https://${sandbox.getHost(3000)}`;
logInspectorUrl({ baseUrl }); 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 () => { const cleanup = async () => {
console.log("Cleaning up..."); console.log("Cleaning up...");
await sandbox.kill(); await sandbox.kill();

View file

@ -8,6 +8,9 @@
"scripts": { "scripts": {
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": {
"sandbox-agent": "workspace:*"
},
"devDependencies": { "devDependencies": {
"@types/node": "latest", "@types/node": "latest",
"typescript": "latest" "typescript": "latest"

View file

@ -1,6 +1,7 @@
import { createInterface } from "node:readline/promises"; import { createInterface } from "node:readline/promises";
import { randomUUID } from "node:crypto"; import { randomUUID } from "node:crypto";
import { setTimeout as delay } from "node:timers/promises"; import { setTimeout as delay } from "node:timers/promises";
import { SandboxAgent } from "sandbox-agent";
export function normalizeBaseUrl(baseUrl: string): string { export function normalizeBaseUrl(baseUrl: string): string {
return baseUrl.replace(/\/+$/, ""); return baseUrl.replace(/\/+$/, "");
@ -279,54 +280,57 @@ export async function runPrompt({
extraHeaders?: Record<string, string>; extraHeaders?: Record<string, string>;
agentId?: string; agentId?: string;
}): Promise<void> { }): Promise<void> {
const normalized = normalizeBaseUrl(baseUrl); const client = await SandboxAgent.connect({
const sessionId = await createSession({ baseUrl, token, extraHeaders, agentId }); 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.`); console.log(`Session ${sessionId} ready. Press Ctrl+C to quit.`);
// Connect to SSE event stream let isThinking = false;
const headers = buildHeaders({ token, extraHeaders }); let hasStartedOutput = false;
const sseResponse = await fetch(`${normalized}/v1/sessions/${sessionId}/events/sse`, { headers }); let turnResolve: (() => void) | null = null;
if (!sseResponse.ok || !sseResponse.body) {
throw new Error(`Failed to connect to SSE: ${sseResponse.status}`);
}
const reader = sseResponse.body.getReader(); // Stream events in background using SDK
const decoder = new TextDecoder();
let buffer = "";
let lastSeq = 0;
// Process SSE events in background
const processEvents = async () => { const processEvents = async () => {
while (true) { for await (const event of client.streamEvents(sessionId)) {
const { done, value } = await reader.read(); // Show thinking indicator when assistant starts
if (done) break; 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 }); // Print text deltas
const lines = buffer.split("\n"); if (event.type === "item.delta") {
buffer = lines.pop() || ""; const delta = (event.data as any)?.delta;
if (delta) {
for (const line of lines) { if (isThinking && !hasStartedOutput) {
if (!line.startsWith("data: ")) continue; process.stdout.write("\r\x1b[K"); // Clear line
const data = line.slice(6); hasStartedOutput = true;
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);
} }
const text = typeof delta === "string" ? delta : delta.type === "text" ? delta.text || "" : "";
if (text) process.stdout.write(text);
}
}
// Print newline after completed assistant message // Signal turn complete
if (event.type === "item.completed" && event.data?.item?.role === "assistant") { if (event.type === "item.completed") {
process.stdout.write("\n> "); const item = (event.data as any)?.item;
} if (item?.role === "assistant") {
} catch {} isThinking = false;
process.stdout.write("\n");
turnResolve?.();
turnResolve = null;
}
} }
} }
}; };
@ -338,14 +342,16 @@ export async function runPrompt({
const line = await rl.question("> "); const line = await rl.question("> ");
if (!line.trim()) continue; if (!line.trim()) continue;
const turnComplete = new Promise<void>((resolve) => {
turnResolve = resolve;
});
try { try {
await fetch(`${normalized}/v1/sessions/${sessionId}/messages`, { await client.postMessage(sessionId, { message: line.trim() });
method: "POST", await turnComplete;
headers: buildHeaders({ token, extraHeaders, contentType: true }),
body: JSON.stringify({ message: line.trim() }),
});
} catch (error) { } catch (error) {
console.error(error instanceof Error ? error.message : error); console.error(error instanceof Error ? error.message : error);
turnResolve = null;
} }
} }
} }

7
pnpm-lock.yaml generated
View file

@ -61,6 +61,9 @@ importers:
'@sandbox-agent/example-shared': '@sandbox-agent/example-shared':
specifier: workspace:* specifier: workspace:*
version: link:../shared version: link:../shared
sandbox-agent:
specifier: workspace:*
version: link:../../sdks/typescript
devDependencies: devDependencies:
'@types/node': '@types/node':
specifier: latest specifier: latest
@ -73,6 +76,10 @@ importers:
version: 5.9.3 version: 5.9.3
examples/shared: examples/shared:
dependencies:
sandbox-agent:
specifier: workspace:*
version: link:../../sdks/typescript
devDependencies: devDependencies:
'@types/node': '@types/node':
specifier: latest specifier: latest