mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 07:04:48 +00:00
fix(examples): use SDK to install agents instead of CLI command
This commit is contained in:
parent
b98c848965
commit
8af152f0b3
7 changed files with 94 additions and 57 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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...");
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
7
pnpm-lock.yaml
generated
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue