import {
compute,
detectProvider,
getMissingEnvVars,
getProviderConfigFromEnv,
isProviderAuthComplete,
isValidProvider,
PROVIDER_NAMES,
type ExplicitComputeConfig,
type ProviderName,
} from "computesdk";
import { SandboxAgent } from "sandbox-agent";
const PORT = 3000;
const REQUEST_TIMEOUT_MS =
Number.parseInt(process.env.COMPUTESDK_TIMEOUT_MS || "", 10) || 120_000;
/**
* Detects and validates the provider to use.
* Priority: COMPUTESDK_PROVIDER env var > auto-detection from API keys
*/
function resolveProvider(): ProviderName {
const providerOverride = process.env.COMPUTESDK_PROVIDER;
if (providerOverride) {
if (!isValidProvider(providerOverride)) {
throw new Error(
`Unsupported provider "${providerOverride}". Supported: ${PROVIDER_NAMES.join(", ")}`
);
}
if (!isProviderAuthComplete(providerOverride)) {
const missing = getMissingEnvVars(providerOverride);
throw new Error(
`Missing credentials for "${providerOverride}". Set: ${missing.join(", ")}`
);
}
return providerOverride as ProviderName;
}
const detected = detectProvider();
if (!detected) {
throw new Error(
`No provider credentials found. Set one of: ${PROVIDER_NAMES.map((p) => getMissingEnvVars(p).join(", ")).join(" | ")}`
);
}
return detected as ProviderName;
}
function configureComputeSDK(): void {
const provider = resolveProvider();
const config: ExplicitComputeConfig = {
provider,
computesdkApiKey: process.env.COMPUTESDK_API_KEY,
requestTimeoutMs: REQUEST_TIMEOUT_MS,
};
// Add provider-specific config from environment
const providerConfig = getProviderConfigFromEnv(provider);
if (Object.keys(providerConfig).length > 0) {
(config as any)[provider] = providerConfig;
}
compute.setConfig(config);
}
configureComputeSDK();
// Build environment variables to pass to sandbox
const envs: Record<string, string> = {};
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
// Create sandbox
const sandbox = await compute.sandbox.create({
envs: Object.keys(envs).length > 0 ? envs : undefined,
});
// Helper to run commands with error handling
const run = async (cmd: string, options?: { background?: boolean }) => {
const result = await sandbox.runCommand(cmd, options);
if (typeof result?.exitCode === "number" && result.exitCode !== 0) {
throw new Error(`Command failed: ${cmd} (exit ${result.exitCode})\n${result.stderr || ""}`);
}
return result;
};
// Install sandbox-agent
await run("curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh");
// Install agents conditionally based on available API keys
if (envs.ANTHROPIC_API_KEY) {
await run("sandbox-agent install-agent claude");
}
if (envs.OPENAI_API_KEY) {
await run("sandbox-agent install-agent codex");
}
// Start the server in the background
await run(`sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`, { background: true });
// Get the public URL for the sandbox
const baseUrl = await sandbox.getUrl({ port: PORT });
// Wait for server to be ready
const deadline = Date.now() + REQUEST_TIMEOUT_MS;
while (Date.now() < deadline) {
try {
const response = await fetch(`${baseUrl}/v1/health`);
if (response.ok) {
const data = await response.json();
if (data?.status === "ok") break;
}
} catch {
// Server not ready yet
}
await new Promise((r) => setTimeout(r, 500));
}
// Connect to the server
const client = await SandboxAgent.connect({ baseUrl });
// Detect which agent to use based on available API keys
const agent = envs.ANTHROPIC_API_KEY ? "claude" : "codex";
// Create a session and start coding
await client.createSession("my-session", { agent });
await client.postMessage("my-session", {
message: "Summarize this repository",
});
for await (const event of client.streamEvents("my-session")) {
console.log(event.type, event.data);
}
// Cleanup
await sandbox.destroy();