mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-19 20:00:48 +00:00
Add Modal and ComputeSDK built-in providers, update examples and docs
- Add `sandbox-agent/modal` provider using Modal SDK with node:22-slim image - Add `sandbox-agent/computesdk` provider using ComputeSDK's unified sandbox API - Update Modal and ComputeSDK examples to use new SDK providers - Update Modal and ComputeSDK deploy docs with provider-based examples - Add Modal to quickstart CodeGroup and docs.json navigation - Add provider test entries for Modal and ComputeSDK - Remove old standalone example files (modal.ts, computesdk.ts) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
441083ea2a
commit
20202c45ee
18 changed files with 377 additions and 646 deletions
|
|
@ -38,6 +38,14 @@
|
|||
"./cloudflare": {
|
||||
"types": "./dist/providers/cloudflare.d.ts",
|
||||
"import": "./dist/providers/cloudflare.js"
|
||||
},
|
||||
"./modal": {
|
||||
"types": "./dist/providers/modal.d.ts",
|
||||
"import": "./dist/providers/modal.js"
|
||||
},
|
||||
"./computesdk": {
|
||||
"types": "./dist/providers/computesdk.d.ts",
|
||||
"import": "./dist/providers/computesdk.js"
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
@ -46,7 +54,9 @@
|
|||
"@e2b/code-interpreter": ">=1.0.0",
|
||||
"@vercel/sandbox": ">=0.1.0",
|
||||
"dockerode": ">=4.0.0",
|
||||
"get-port": ">=7.0.0"
|
||||
"get-port": ">=7.0.0",
|
||||
"modal": ">=0.1.0",
|
||||
"computesdk": ">=0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@cloudflare/sandbox": {
|
||||
|
|
@ -66,6 +76,12 @@
|
|||
},
|
||||
"get-port": {
|
||||
"optional": true
|
||||
},
|
||||
"modal": {
|
||||
"optional": true
|
||||
},
|
||||
"computesdk": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -94,6 +110,8 @@
|
|||
"@vercel/sandbox": ">=0.1.0",
|
||||
"dockerode": ">=4.0.0",
|
||||
"get-port": ">=7.0.0",
|
||||
"modal": ">=0.1.0",
|
||||
"computesdk": ">=0.1.0",
|
||||
"openapi-typescript": "^6.7.0",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
|
|
|
|||
53
sdks/typescript/src/providers/computesdk.ts
Normal file
53
sdks/typescript/src/providers/computesdk.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { compute } from "computesdk";
|
||||
import type { SandboxProvider } from "./types.ts";
|
||||
import { DEFAULT_AGENTS, SANDBOX_AGENT_INSTALL_SCRIPT } from "./shared.ts";
|
||||
|
||||
const DEFAULT_AGENT_PORT = 3000;
|
||||
|
||||
export interface ComputeSdkProviderOptions {
|
||||
create?: {
|
||||
envs?: Record<string, string>;
|
||||
};
|
||||
agentPort?: number;
|
||||
}
|
||||
|
||||
export function computesdk(options: ComputeSdkProviderOptions = {}): SandboxProvider {
|
||||
const agentPort = options.agentPort ?? DEFAULT_AGENT_PORT;
|
||||
|
||||
return {
|
||||
name: "computesdk",
|
||||
async create(): Promise<string> {
|
||||
const envs = options.create?.envs;
|
||||
const sandbox = await compute.sandbox.create({
|
||||
envs: envs && Object.keys(envs).length > 0 ? envs : undefined,
|
||||
});
|
||||
|
||||
const run = async (cmd: string, runOptions?: { background?: boolean }) => {
|
||||
const result = await sandbox.runCommand(cmd, runOptions);
|
||||
if (typeof result?.exitCode === "number" && result.exitCode !== 0) {
|
||||
throw new Error(`computesdk command failed: ${cmd} (exit ${result.exitCode})\n${result.stderr || ""}`);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
await run(`curl -fsSL ${SANDBOX_AGENT_INSTALL_SCRIPT} | sh`);
|
||||
for (const agent of DEFAULT_AGENTS) {
|
||||
await run(`sandbox-agent install-agent ${agent}`);
|
||||
}
|
||||
await run(`sandbox-agent server --no-token --host 0.0.0.0 --port ${agentPort}`, {
|
||||
background: true,
|
||||
});
|
||||
|
||||
return sandbox.sandboxId;
|
||||
},
|
||||
async destroy(sandboxId: string): Promise<void> {
|
||||
const sandbox = await compute.sandbox.getById(sandboxId);
|
||||
if (sandbox) await sandbox.destroy();
|
||||
},
|
||||
async getUrl(sandboxId: string): Promise<string> {
|
||||
const sandbox = await compute.sandbox.getById(sandboxId);
|
||||
if (!sandbox) throw new Error(`computesdk sandbox not found: ${sandboxId}`);
|
||||
return sandbox.getUrl({ port: agentPort });
|
||||
},
|
||||
};
|
||||
}
|
||||
75
sdks/typescript/src/providers/modal.ts
Normal file
75
sdks/typescript/src/providers/modal.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { ModalClient } from "modal";
|
||||
import type { SandboxProvider } from "./types.ts";
|
||||
import { DEFAULT_AGENTS, SANDBOX_AGENT_INSTALL_SCRIPT, buildServerStartCommand } from "./shared.ts";
|
||||
|
||||
const DEFAULT_AGENT_PORT = 3000;
|
||||
const DEFAULT_APP_NAME = "sandbox-agent";
|
||||
|
||||
export interface ModalProviderOptions {
|
||||
create?: {
|
||||
secrets?: Record<string, string>;
|
||||
appName?: string;
|
||||
};
|
||||
agentPort?: number;
|
||||
}
|
||||
|
||||
export function modal(options: ModalProviderOptions = {}): SandboxProvider {
|
||||
const agentPort = options.agentPort ?? DEFAULT_AGENT_PORT;
|
||||
const appName = options.create?.appName ?? DEFAULT_APP_NAME;
|
||||
const client = new ModalClient();
|
||||
|
||||
return {
|
||||
name: "modal",
|
||||
async create(): Promise<string> {
|
||||
const app = await client.apps.fromName(appName, { createIfMissing: true });
|
||||
|
||||
const image = client.images
|
||||
.fromRegistry("node:22-slim")
|
||||
.dockerfileCommands([
|
||||
"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`,
|
||||
]);
|
||||
|
||||
const envVars = options.create?.secrets ?? {};
|
||||
const secrets = Object.keys(envVars).length > 0 ? [await client.secrets.fromObject(envVars)] : [];
|
||||
|
||||
const sb = await client.sandboxes.create(app, image, {
|
||||
encryptedPorts: [agentPort],
|
||||
secrets,
|
||||
});
|
||||
|
||||
const exec = async (cmd: string) => {
|
||||
const p = await sb.exec(["bash", "-c", cmd], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
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;
|
||||
},
|
||||
async destroy(sandboxId: string): Promise<void> {
|
||||
const sb = await client.sandboxes.fromId(sandboxId);
|
||||
await sb.terminate();
|
||||
},
|
||||
async getUrl(sandboxId: string): Promise<string> {
|
||||
const sb = await client.sandboxes.fromId(sandboxId);
|
||||
const tunnels = await sb.tunnels();
|
||||
const tunnel = tunnels[agentPort];
|
||||
if (!tunnel) {
|
||||
throw new Error(`modal: no tunnel found for port ${agentPort}`);
|
||||
}
|
||||
return tunnel.url;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ import { docker } from "../src/providers/docker.ts";
|
|||
import { e2b } from "../src/providers/e2b.ts";
|
||||
import { daytona } from "../src/providers/daytona.ts";
|
||||
import { vercel } from "../src/providers/vercel.ts";
|
||||
import { modal } from "../src/providers/modal.ts";
|
||||
import { computesdk } from "../src/providers/computesdk.ts";
|
||||
import { prepareMockAgentDataHome } from "./helpers/mock-agent.ts";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
|
@ -216,6 +218,42 @@ function buildProviders(): ProviderEntry[] {
|
|||
});
|
||||
}
|
||||
|
||||
// --- modal ---
|
||||
// Session tests disabled: see docker comment above (ACP protocol mismatch).
|
||||
{
|
||||
entries.push({
|
||||
name: "modal",
|
||||
skipReasons: [...missingEnvVars("MODAL_TOKEN_ID", "MODAL_TOKEN_SECRET"), ...missingModules("modal")],
|
||||
agent: "claude",
|
||||
startTimeoutMs: 300_000,
|
||||
canVerifyDestroyedSandbox: false,
|
||||
sessionTestsEnabled: false,
|
||||
createProvider() {
|
||||
return modal({
|
||||
create: { secrets: collectApiKeys() },
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// --- computesdk ---
|
||||
// Session tests disabled: see docker comment above (ACP protocol mismatch).
|
||||
{
|
||||
entries.push({
|
||||
name: "computesdk",
|
||||
skipReasons: [...missingEnvVars("COMPUTESDK_API_KEY"), ...missingModules("computesdk")],
|
||||
agent: "claude",
|
||||
startTimeoutMs: 300_000,
|
||||
canVerifyDestroyedSandbox: false,
|
||||
sessionTestsEnabled: false,
|
||||
createProvider() {
|
||||
return computesdk({
|
||||
create: { envs: collectApiKeys() },
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ export default defineConfig({
|
|||
"src/providers/docker.ts",
|
||||
"src/providers/vercel.ts",
|
||||
"src/providers/cloudflare.ts",
|
||||
"src/providers/modal.ts",
|
||||
"src/providers/computesdk.ts",
|
||||
],
|
||||
format: ["esm"],
|
||||
dts: true,
|
||||
clean: true,
|
||||
sourcemap: true,
|
||||
external: ["@cloudflare/sandbox", "@daytonaio/sdk", "@e2b/code-interpreter", "@vercel/sandbox", "dockerode", "get-port"],
|
||||
external: ["@cloudflare/sandbox", "@daytonaio/sdk", "@e2b/code-interpreter", "@vercel/sandbox", "dockerode", "get-port", "modal", "computesdk"],
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue