mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 12:04:15 +00:00
Fix Modal provider: pre-install agents in image, fire-and-forget exec for server
- Pre-install agents in Dockerfile commands so they are cached across creates - Use fire-and-forget exec (no wait) to keep server alive in Modal sandbox - Add memoryMiB option (default 2GB) to avoid OOM during agent install Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
20202c45ee
commit
f5d64ca0fd
1 changed files with 13 additions and 18 deletions
|
|
@ -1,14 +1,16 @@
|
||||||
import { ModalClient } from "modal";
|
import { ModalClient } from "modal";
|
||||||
import type { SandboxProvider } from "./types.ts";
|
import type { SandboxProvider } from "./types.ts";
|
||||||
import { DEFAULT_AGENTS, SANDBOX_AGENT_INSTALL_SCRIPT, buildServerStartCommand } from "./shared.ts";
|
import { DEFAULT_AGENTS, SANDBOX_AGENT_INSTALL_SCRIPT } from "./shared.ts";
|
||||||
|
|
||||||
const DEFAULT_AGENT_PORT = 3000;
|
const DEFAULT_AGENT_PORT = 3000;
|
||||||
const DEFAULT_APP_NAME = "sandbox-agent";
|
const DEFAULT_APP_NAME = "sandbox-agent";
|
||||||
|
const DEFAULT_MEMORY_MIB = 2048;
|
||||||
|
|
||||||
export interface ModalProviderOptions {
|
export interface ModalProviderOptions {
|
||||||
create?: {
|
create?: {
|
||||||
secrets?: Record<string, string>;
|
secrets?: Record<string, string>;
|
||||||
appName?: string;
|
appName?: string;
|
||||||
|
memoryMiB?: number;
|
||||||
};
|
};
|
||||||
agentPort?: number;
|
agentPort?: number;
|
||||||
}
|
}
|
||||||
|
|
@ -16,6 +18,7 @@ export interface ModalProviderOptions {
|
||||||
export function modal(options: ModalProviderOptions = {}): SandboxProvider {
|
export function modal(options: ModalProviderOptions = {}): SandboxProvider {
|
||||||
const agentPort = options.agentPort ?? DEFAULT_AGENT_PORT;
|
const agentPort = options.agentPort ?? DEFAULT_AGENT_PORT;
|
||||||
const appName = options.create?.appName ?? DEFAULT_APP_NAME;
|
const appName = options.create?.appName ?? DEFAULT_APP_NAME;
|
||||||
|
const memoryMiB = options.create?.memoryMiB ?? DEFAULT_MEMORY_MIB;
|
||||||
const client = new ModalClient();
|
const client = new ModalClient();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -23,11 +26,15 @@ export function modal(options: ModalProviderOptions = {}): SandboxProvider {
|
||||||
async create(): Promise<string> {
|
async create(): Promise<string> {
|
||||||
const app = await client.apps.fromName(appName, { createIfMissing: true });
|
const app = await client.apps.fromName(appName, { createIfMissing: true });
|
||||||
|
|
||||||
|
// Pre-install sandbox-agent and agents in the image so they are cached
|
||||||
|
// across sandbox creates and don't need to be installed at runtime.
|
||||||
|
const installAgentCmds = DEFAULT_AGENTS.map((agent) => `RUN sandbox-agent install-agent ${agent}`);
|
||||||
const image = client.images
|
const image = client.images
|
||||||
.fromRegistry("node:22-slim")
|
.fromRegistry("node:22-slim")
|
||||||
.dockerfileCommands([
|
.dockerfileCommands([
|
||||||
"RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*",
|
"RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*",
|
||||||
`RUN curl -fsSL ${SANDBOX_AGENT_INSTALL_SCRIPT} | sh`,
|
`RUN curl -fsSL ${SANDBOX_AGENT_INSTALL_SCRIPT} | sh`,
|
||||||
|
...installAgentCmds,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const envVars = options.create?.secrets ?? {};
|
const envVars = options.create?.secrets ?? {};
|
||||||
|
|
@ -36,25 +43,13 @@ export function modal(options: ModalProviderOptions = {}): SandboxProvider {
|
||||||
const sb = await client.sandboxes.create(app, image, {
|
const sb = await client.sandboxes.create(app, image, {
|
||||||
encryptedPorts: [agentPort],
|
encryptedPorts: [agentPort],
|
||||||
secrets,
|
secrets,
|
||||||
|
memoryMiB,
|
||||||
});
|
});
|
||||||
|
|
||||||
const exec = async (cmd: string) => {
|
// Start the server as a long-running exec process. We intentionally
|
||||||
const p = await sb.exec(["bash", "-c", cmd], {
|
// do NOT await p.wait() — the process stays alive for the sandbox
|
||||||
stdout: "pipe",
|
// lifetime and keeps the port open for the tunnel.
|
||||||
stderr: "pipe",
|
sb.exec(["sandbox-agent", "server", "--no-token", "--host", "0.0.0.0", "--port", String(agentPort)]);
|
||||||
});
|
|
||||||
const exitCode = await p.wait();
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
const stderr = await p.stderr.readText();
|
|
||||||
throw new Error(`modal command failed (exit ${exitCode}): ${cmd}\n${stderr}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const agent of DEFAULT_AGENTS) {
|
|
||||||
await exec(`sandbox-agent install-agent ${agent}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await sb.exec(["bash", "-c", buildServerStartCommand(agentPort)]);
|
|
||||||
|
|
||||||
return sb.sandboxId;
|
return sb.sandboxId;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue