mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-21 01:01:46 +00:00
SDK: Add ensureServer() for automatic server recovery
Add ensureServer() to SandboxProvider interface to handle cases where the sandbox-agent server stops or goes to sleep. The SDK now calls this method after 3 consecutive health-check failures, allowing providers to restart the server if needed. Most built-in providers (E2B, Daytona, Vercel, Modal, ComputeSDK) implement this. Docker and Cloudflare manage server lifecycle differently, and Local uses managed child processes. Also update docs for quickstart, architecture, multiplayer, and session persistence; mark persist-* packages as deprecated; and add ensureServer implementations to all applicable providers. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d008283c17
commit
35840facdd
38 changed files with 620 additions and 205 deletions
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
> **Deprecated:** This package has been deprecated and removed.
|
||||
|
||||
Copy the driver source directly into your project. See the [session persistence docs](https://sandboxagent.dev/session-persistence) for guidance.
|
||||
Copy the driver source into your project. See the [reference implementation](https://github.com/rivet-dev/sandbox-agent/tree/main/frontend/packages/inspector/src/persist-indexeddb.ts) and the [session persistence docs](https://sandboxagent.dev/session-persistence) for guidance.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@sandbox-agent/persist-indexeddb",
|
||||
"version": "0.3.2",
|
||||
"description": "IndexedDB persistence driver for the Sandbox Agent TypeScript SDK",
|
||||
"description": "IndexedDB persistence driver for the Sandbox Agent TypeScript SDK (DEPRECATED)",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
throw new Error(
|
||||
"@sandbox-agent/persist-indexeddb has been deprecated and removed. " +
|
||||
"Copy the reference implementation into your project instead. " +
|
||||
"See https://github.com/nichochar/sandbox-agent/tree/main/examples/persist-indexeddb",
|
||||
"Copy the reference implementation from frontend/packages/inspector/src/persist-indexeddb.ts into your project instead. " +
|
||||
"See https://github.com/rivet-dev/sandbox-agent/tree/main/frontend/packages/inspector/src/persist-indexeddb.ts",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# @sandbox-agent/persist-postgres
|
||||
|
||||
> **Deprecated:** This package has been deprecated and removed. The implementation now lives as a copy-paste reference in [`examples/persist-postgres`](../../examples/persist-postgres).
|
||||
> **Deprecated:** This package has been deprecated and removed.
|
||||
|
||||
Install `pg` directly and copy the driver source into your project. See the [full example](https://github.com/nichochar/sandbox-agent/tree/main/examples/persist-postgres).
|
||||
Install `pg` directly and copy the driver source into your project. See the [full example](https://github.com/rivet-dev/sandbox-agent/tree/main/examples/persist-postgres) and the [session persistence docs](https://sandboxagent.dev/session-persistence) for guidance.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
throw new Error(
|
||||
"@sandbox-agent/persist-postgres has been deprecated and removed. " +
|
||||
"Copy the reference implementation from examples/persist-postgres into your project instead. " +
|
||||
"See https://github.com/nichochar/sandbox-agent/tree/main/examples/persist-postgres",
|
||||
"See https://github.com/rivet-dev/sandbox-agent/tree/main/examples/persist-postgres",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
> **Deprecated:** This package has been deprecated and removed.
|
||||
|
||||
Copy the driver source directly into your project. See the [session persistence docs](https://sandboxagent.dev/session-persistence) for guidance.
|
||||
Copy the driver source into your project. See the [multiplayer docs](https://github.com/rivet-dev/sandbox-agent/tree/main/docs/multiplayer.mdx) and the [session persistence docs](https://sandboxagent.dev/session-persistence) for guidance.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@sandbox-agent/persist-rivet",
|
||||
"version": "0.3.2",
|
||||
"description": "Rivet Actor persistence driver for the Sandbox Agent TypeScript SDK",
|
||||
"description": "Rivet Actor persistence driver for the Sandbox Agent TypeScript SDK (DEPRECATED)",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
throw new Error(
|
||||
"@sandbox-agent/persist-rivet has been deprecated and removed. " +
|
||||
"Copy the reference implementation into your project instead. " +
|
||||
"See https://github.com/nichochar/sandbox-agent/tree/main/examples/persist-rivet",
|
||||
"Copy the reference implementation from docs/multiplayer.mdx into your project instead. " +
|
||||
"See https://github.com/rivet-dev/sandbox-agent/tree/main/docs/multiplayer.mdx",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# @sandbox-agent/persist-sqlite
|
||||
|
||||
> **Deprecated:** This package has been deprecated and removed. The implementation now lives as a copy-paste reference in [`examples/persist-sqlite`](../../examples/persist-sqlite).
|
||||
> **Deprecated:** This package has been deprecated and removed.
|
||||
|
||||
Install `better-sqlite3` directly and copy the driver source into your project. See the [full example](https://github.com/nichochar/sandbox-agent/tree/main/examples/persist-sqlite).
|
||||
Install `better-sqlite3` directly and copy the driver source into your project. See the [full example](https://github.com/rivet-dev/sandbox-agent/tree/main/examples/persist-sqlite) and the [session persistence docs](https://sandboxagent.dev/session-persistence) for guidance.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@sandbox-agent/persist-sqlite",
|
||||
"version": "0.3.2",
|
||||
"description": "SQLite persistence driver for the Sandbox Agent TypeScript SDK",
|
||||
"description": "SQLite persistence driver for the Sandbox Agent TypeScript SDK (DEPRECATED)",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
throw new Error(
|
||||
"@sandbox-agent/persist-sqlite has been deprecated and removed. " +
|
||||
"Copy the reference implementation from examples/persist-sqlite into your project instead. " +
|
||||
"See https://github.com/nichochar/sandbox-agent/tree/main/examples/persist-sqlite",
|
||||
"See https://github.com/rivet-dev/sandbox-agent/tree/main/examples/persist-sqlite",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ const HEALTH_WAIT_MIN_DELAY_MS = 500;
|
|||
const HEALTH_WAIT_MAX_DELAY_MS = 15_000;
|
||||
const HEALTH_WAIT_LOG_AFTER_MS = 5_000;
|
||||
const HEALTH_WAIT_LOG_EVERY_MS = 10_000;
|
||||
const HEALTH_WAIT_ENSURE_SERVER_AFTER_FAILURES = 3;
|
||||
|
||||
export interface SandboxAgentHealthWaitOptions {
|
||||
timeoutMs?: number;
|
||||
|
|
@ -903,7 +904,7 @@ export class SandboxAgent {
|
|||
const createdSandbox = !existingSandbox;
|
||||
|
||||
if (existingSandbox) {
|
||||
await provider.wake?.(rawSandboxId);
|
||||
await provider.ensureServer?.(rawSandboxId);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -2118,6 +2119,7 @@ export class SandboxAgent {
|
|||
let delayMs = HEALTH_WAIT_MIN_DELAY_MS;
|
||||
let nextLogAt = startedAt + HEALTH_WAIT_LOG_AFTER_MS;
|
||||
let lastError: unknown;
|
||||
let consecutiveFailures = 0;
|
||||
|
||||
while (!this.disposed && (deadline === undefined || Date.now() < deadline)) {
|
||||
throwIfAborted(signal);
|
||||
|
|
@ -2128,11 +2130,22 @@ export class SandboxAgent {
|
|||
return;
|
||||
}
|
||||
lastError = new Error(`Unexpected health response: ${JSON.stringify(health)}`);
|
||||
consecutiveFailures++;
|
||||
} catch (error) {
|
||||
if (isAbortError(error)) {
|
||||
throw error;
|
||||
}
|
||||
lastError = error;
|
||||
consecutiveFailures++;
|
||||
}
|
||||
|
||||
if (consecutiveFailures >= HEALTH_WAIT_ENSURE_SERVER_AFTER_FAILURES && this.sandboxProvider?.ensureServer && this.sandboxProviderRawId) {
|
||||
try {
|
||||
await this.sandboxProvider.ensureServer(this.sandboxProviderRawId);
|
||||
} catch {
|
||||
// Best-effort; the next health check will determine if it worked.
|
||||
}
|
||||
consecutiveFailures = 0;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
|
|
|||
|
|
@ -49,5 +49,12 @@ export function computesdk(options: ComputeSdkProviderOptions = {}): SandboxProv
|
|||
if (!sandbox) throw new Error(`computesdk sandbox not found: ${sandboxId}`);
|
||||
return sandbox.getUrl({ port: agentPort });
|
||||
},
|
||||
async ensureServer(sandboxId: string): Promise<void> {
|
||||
const sandbox = await compute.sandbox.getById(sandboxId);
|
||||
if (!sandbox) throw new Error(`computesdk sandbox not found: ${sandboxId}`);
|
||||
await sandbox.runCommand(`sandbox-agent server --no-token --host 0.0.0.0 --port ${agentPort}`, {
|
||||
background: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export function daytona(options: DaytonaProviderOptions = {}): SandboxProvider {
|
|||
const preview = await sandbox.getSignedPreviewUrl(agentPort, previewTtlSeconds);
|
||||
return typeof preview === "string" ? preview : preview.url;
|
||||
},
|
||||
async wake(sandboxId: string): Promise<void> {
|
||||
async ensureServer(sandboxId: string): Promise<void> {
|
||||
const sandbox = await client.get(sandboxId);
|
||||
if (!sandbox) {
|
||||
throw new Error(`daytona sandbox not found: ${sandboxId}`);
|
||||
|
|
|
|||
|
|
@ -53,5 +53,10 @@ export function e2b(options: E2BProviderOptions = {}): SandboxProvider {
|
|||
const sandbox = await Sandbox.connect(sandboxId, connectOpts as any);
|
||||
return `https://${sandbox.getHost(agentPort)}`;
|
||||
},
|
||||
async ensureServer(sandboxId: string): Promise<void> {
|
||||
const connectOpts = await resolveOptions(options.connect, sandboxId);
|
||||
const sandbox = await Sandbox.connect(sandboxId, connectOpts as any);
|
||||
await sandbox.commands.run(`sandbox-agent server --no-token --host 0.0.0.0 --port ${agentPort}`, { background: true, timeoutMs: 0 });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,5 +66,9 @@ export function modal(options: ModalProviderOptions = {}): SandboxProvider {
|
|||
}
|
||||
return tunnel.url;
|
||||
},
|
||||
async ensureServer(sandboxId: string): Promise<void> {
|
||||
const sb = await client.sandboxes.fromId(sandboxId);
|
||||
sb.exec(["sandbox-agent", "server", "--no-token", "--host", "0.0.0.0", "--port", String(agentPort)]);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,11 @@ export interface SandboxProvider {
|
|||
getFetch?(sandboxId: string): Promise<typeof globalThis.fetch>;
|
||||
|
||||
/**
|
||||
* Optional hook invoked before reconnecting to an existing sandbox.
|
||||
* Useful for providers where the sandbox-agent process may need to be restarted.
|
||||
* Ensure the sandbox-agent server process is running inside the sandbox.
|
||||
* Called during health-wait after consecutive failures, and before
|
||||
* reconnecting to an existing sandbox. Implementations should be
|
||||
* idempotent — if the server is already running, this should be a no-op
|
||||
* (e.g. the duplicate process exits on port conflict).
|
||||
*/
|
||||
wake?(sandboxId: string): Promise<void>;
|
||||
ensureServer?(sandboxId: string): Promise<void>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,5 +53,13 @@ export function vercel(options: VercelProviderOptions = {}): SandboxProvider {
|
|||
const sandbox = await Sandbox.get({ sandboxId });
|
||||
return sandbox.domain(agentPort);
|
||||
},
|
||||
async ensureServer(sandboxId: string): Promise<void> {
|
||||
const sandbox = await Sandbox.get({ sandboxId });
|
||||
await sandbox.runCommand({
|
||||
cmd: "sandbox-agent",
|
||||
args: ["server", "--no-token", "--host", "0.0.0.0", "--port", String(agentPort)],
|
||||
detached: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue