mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 06:04:43 +00:00
Add SDK health wait gate (#206)
* Add SDK health wait gate * Default connect to waiting for health * Document connect health wait default * Add abort signal to connect health wait * Refactor SDK health probe helper * Update quickstart health wait note * Remove example health polling * Fix docker example codex startup
This commit is contained in:
parent
4335ef6af6
commit
e7343e14bd
14 changed files with 423 additions and 95 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { SimpleBox } from "@boxlite-ai/boxlite";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { detectAgent, buildInspectorUrl, waitForHealth } from "@sandbox-agent/example-shared";
|
||||
import { detectAgent, buildInspectorUrl } from "@sandbox-agent/example-shared";
|
||||
import { setupImage, OCI_DIR } from "./setup-image.ts";
|
||||
|
||||
const env: Record<string, string> = {};
|
||||
|
|
@ -26,9 +26,7 @@ if (result.exitCode !== 0) throw new Error(`Failed to start server: ${result.std
|
|||
|
||||
const baseUrl = "http://localhost:3000";
|
||||
|
||||
console.log("Waiting for server...");
|
||||
await waitForHealth({ baseUrl });
|
||||
|
||||
console.log("Connecting to server...");
|
||||
const client = await SandboxAgent.connect({ baseUrl });
|
||||
const session = await client.createSession({ agent: detectAgent(), sessionInit: { cwd: "/root", mcpServers: [] } });
|
||||
const sessionId = session.id;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
type ProviderName,
|
||||
} from "computesdk";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { detectAgent, buildInspectorUrl, waitForHealth } from "@sandbox-agent/example-shared";
|
||||
import { detectAgent, buildInspectorUrl } from "@sandbox-agent/example-shared";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
|
|
@ -116,9 +116,6 @@ export async function setupComputeSdkSandboxAgent(): Promise<{
|
|||
|
||||
const baseUrl = await sandbox.getUrl({ port: PORT });
|
||||
|
||||
console.log("Waiting for server...");
|
||||
await waitForHealth({ baseUrl });
|
||||
|
||||
const cleanup = async () => {
|
||||
try {
|
||||
await sandbox.destroy();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Daytona, Image } from "@daytonaio/sdk";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { detectAgent, buildInspectorUrl, waitForHealth } from "@sandbox-agent/example-shared";
|
||||
import { detectAgent, buildInspectorUrl } from "@sandbox-agent/example-shared";
|
||||
|
||||
const daytona = new Daytona();
|
||||
|
||||
|
|
@ -25,9 +25,7 @@ await sandbox.process.executeCommand(
|
|||
|
||||
const baseUrl = (await sandbox.getSignedPreviewUrl(3000, 4 * 60 * 60)).url;
|
||||
|
||||
console.log("Waiting for server...");
|
||||
await waitForHealth({ baseUrl });
|
||||
|
||||
console.log("Connecting to server...");
|
||||
const client = await SandboxAgent.connect({ baseUrl });
|
||||
const session = await client.createSession({ agent: detectAgent(), sessionInit: { cwd: "/home/daytona", mcpServers: [] } });
|
||||
const sessionId = session.id;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Daytona } from "@daytonaio/sdk";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { detectAgent, buildInspectorUrl, waitForHealth } from "@sandbox-agent/example-shared";
|
||||
import { detectAgent, buildInspectorUrl } from "@sandbox-agent/example-shared";
|
||||
|
||||
const daytona = new Daytona();
|
||||
|
||||
|
|
@ -30,9 +30,7 @@ await sandbox.process.executeCommand(
|
|||
|
||||
const baseUrl = (await sandbox.getSignedPreviewUrl(3000, 4 * 60 * 60)).url;
|
||||
|
||||
console.log("Waiting for server...");
|
||||
await waitForHealth({ baseUrl });
|
||||
|
||||
console.log("Connecting to server...");
|
||||
const client = await SandboxAgent.connect({ baseUrl });
|
||||
const session = await client.createSession({ agent: detectAgent(), sessionInit: { cwd: "/home/daytona", mcpServers: [] } });
|
||||
const sessionId = session.id;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
import Docker from "dockerode";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { detectAgent, buildInspectorUrl, waitForHealth } from "@sandbox-agent/example-shared";
|
||||
import { detectAgent, buildInspectorUrl } from "@sandbox-agent/example-shared";
|
||||
|
||||
const IMAGE = "alpine:latest";
|
||||
const IMAGE = "node:22-bookworm-slim";
|
||||
const PORT = 3000;
|
||||
const agent = detectAgent();
|
||||
const codexAuthPath = process.env.HOME ? path.join(process.env.HOME, ".codex", "auth.json") : null;
|
||||
const bindMounts = codexAuthPath && fs.existsSync(codexAuthPath)
|
||||
? [`${codexAuthPath}:/root/.codex/auth.json:ro`]
|
||||
: [];
|
||||
|
||||
const docker = new Docker({ socketPath: "/var/run/docker.sock" });
|
||||
|
||||
|
|
@ -24,29 +31,30 @@ console.log("Starting container...");
|
|||
const container = await docker.createContainer({
|
||||
Image: IMAGE,
|
||||
Cmd: ["sh", "-c", [
|
||||
"apk add --no-cache curl ca-certificates libstdc++ libgcc bash",
|
||||
"apt-get update",
|
||||
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl ca-certificates bash libstdc++6",
|
||||
"rm -rf /var/lib/apt/lists/*",
|
||||
"curl -fsSL https://releases.rivet.dev/sandbox-agent/0.2.x/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}` : "",
|
||||
process.env.CODEX_API_KEY ? `CODEX_API_KEY=${process.env.CODEX_API_KEY}` : "",
|
||||
].filter(Boolean),
|
||||
ExposedPorts: { [`${PORT}/tcp`]: {} },
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: { [`${PORT}/tcp`]: [{ HostPort: `${PORT}` }] },
|
||||
Binds: bindMounts,
|
||||
},
|
||||
});
|
||||
await container.start();
|
||||
|
||||
const baseUrl = `http://127.0.0.1:${PORT}`;
|
||||
await waitForHealth({ baseUrl });
|
||||
|
||||
const client = await SandboxAgent.connect({ baseUrl });
|
||||
const session = await client.createSession({ agent: detectAgent(), sessionInit: { cwd: "/root", mcpServers: [] } });
|
||||
const session = await client.createSession({ agent, sessionInit: { cwd: "/root", mcpServers: [] } });
|
||||
const sessionId = session.id;
|
||||
|
||||
console.log(` UI: ${buildInspectorUrl({ baseUrl, sessionId })}`);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Sandbox } from "@e2b/code-interpreter";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { detectAgent, buildInspectorUrl, waitForHealth } from "@sandbox-agent/example-shared";
|
||||
import { detectAgent, buildInspectorUrl } from "@sandbox-agent/example-shared";
|
||||
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
|
|
@ -27,9 +27,7 @@ await sandbox.commands.run("sandbox-agent server --no-token --host 0.0.0.0 --por
|
|||
|
||||
const baseUrl = `https://${sandbox.getHost(3000)}`;
|
||||
|
||||
console.log("Waiting for server...");
|
||||
await waitForHealth({ baseUrl });
|
||||
|
||||
console.log("Connecting to server...");
|
||||
const client = await SandboxAgent.connect({ baseUrl });
|
||||
const session = await client.createSession({ agent: detectAgent(), sessionInit: { cwd: "/home/user", mcpServers: [] } });
|
||||
const sessionId = session.id;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import fs from "node:fs";
|
|||
import path from "node:path";
|
||||
import { PassThrough } from "node:stream";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { waitForHealth } from "./sandbox-agent-client.ts";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const EXAMPLE_IMAGE = "sandbox-agent-examples:latest";
|
||||
|
|
@ -173,7 +172,7 @@ async function ensureExampleImage(_docker: Docker): Promise<string> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Start a Docker container running sandbox-agent and wait for it to be healthy.
|
||||
* Start a Docker container running sandbox-agent.
|
||||
* Registers SIGINT/SIGTERM handlers for cleanup.
|
||||
*/
|
||||
export async function startDockerSandbox(opts: DockerSandboxOptions): Promise<DockerSandbox> {
|
||||
|
|
@ -275,18 +274,8 @@ export async function startDockerSandbox(opts: DockerSandboxOptions): Promise<Do
|
|||
}
|
||||
const baseUrl = `http://127.0.0.1:${mappedHostPort}`;
|
||||
|
||||
try {
|
||||
await waitForHealth({ baseUrl });
|
||||
} catch (err) {
|
||||
stopStartupLogs();
|
||||
console.error(" Container logs:");
|
||||
for (const chunk of logChunks) {
|
||||
process.stderr.write(` ${chunk}`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
stopStartupLogs();
|
||||
console.log(` Ready (${baseUrl})`);
|
||||
console.log(` Started (${baseUrl})`);
|
||||
|
||||
const cleanup = async () => {
|
||||
stopStartupLogs();
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
* Provides minimal helpers for connecting to and interacting with sandbox-agent servers.
|
||||
*/
|
||||
|
||||
import { setTimeout as delay } from "node:timers/promises";
|
||||
|
||||
function normalizeBaseUrl(baseUrl: string): string {
|
||||
return baseUrl.replace(/\/+$/, "");
|
||||
}
|
||||
|
|
@ -74,41 +72,6 @@ export function buildHeaders({
|
|||
return headers;
|
||||
}
|
||||
|
||||
export async function waitForHealth({
|
||||
baseUrl,
|
||||
token,
|
||||
extraHeaders,
|
||||
timeoutMs = 120_000,
|
||||
}: {
|
||||
baseUrl: string;
|
||||
token?: string;
|
||||
extraHeaders?: Record<string, string>;
|
||||
timeoutMs?: number;
|
||||
}): Promise<void> {
|
||||
const normalized = normalizeBaseUrl(baseUrl);
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
let lastError: unknown;
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const headers = buildHeaders({ token, extraHeaders });
|
||||
const response = await fetch(`${normalized}/v1/health`, { headers });
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data?.status === "ok") {
|
||||
return;
|
||||
}
|
||||
lastError = new Error(`Unexpected health response: ${JSON.stringify(data)}`);
|
||||
} else {
|
||||
lastError = new Error(`Health check failed: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
}
|
||||
await delay(500);
|
||||
}
|
||||
throw (lastError ?? new Error("Timed out waiting for /v1/health")) as Error;
|
||||
}
|
||||
|
||||
export function generateSessionId(): string {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let id = "session-";
|
||||
|
|
@ -144,4 +107,3 @@ export function detectAgent(): string {
|
|||
}
|
||||
return "claude";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Sandbox } from "@vercel/sandbox";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { detectAgent, buildInspectorUrl, waitForHealth } from "@sandbox-agent/example-shared";
|
||||
import { detectAgent, buildInspectorUrl } from "@sandbox-agent/example-shared";
|
||||
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
|
|
@ -38,9 +38,7 @@ await sandbox.runCommand({
|
|||
|
||||
const baseUrl = sandbox.domain(3000);
|
||||
|
||||
console.log("Waiting for server...");
|
||||
await waitForHealth({ baseUrl });
|
||||
|
||||
console.log("Connecting to server...");
|
||||
const client = await SandboxAgent.connect({ baseUrl });
|
||||
const session = await client.createSession({ agent: detectAgent(), sessionInit: { cwd: "/home/vercel-sandbox", mcpServers: [] } });
|
||||
const sessionId = session.id;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue