mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 04:03:31 +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 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;
|
||||
|
|
|
|||
|
|
@ -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...");
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"sandbox-agent": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "latest",
|
||||
"typescript": "latest"
|
||||
|
|
|
|||
|
|
@ -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<string, string>;
|
||||
agentId?: string;
|
||||
}): Promise<void> {
|
||||
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<void>((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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue