Created
November 8, 2025 06:22
-
-
Save RhysSullivan/5e6c4588f3b04cb3d63065bea50ff17e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // opencode-sandbox.ts | |
| import ms from "ms"; | |
| import { Sandbox } from "@vercel/sandbox"; | |
| import { setTimeout as sleep } from "timers/promises"; | |
| import { createOpencodeClient } from "@opencode-ai/sdk"; | |
| type CreateOpenCodeSandboxParams = { | |
| /** Public or private GitHub repo URL */ | |
| repoUrl: string; | |
| /** Optional Git revision/branch */ | |
| revision?: string; | |
| /** vCPUs to allocate to the sandbox (default 4) */ | |
| vcpus?: number; | |
| /** How long the sandbox may live (default 30m) */ | |
| timeoutMs?: number; | |
| /** Model ID to use when you send prompts (optional) */ | |
| defaultModelId?: string; // e.g. "claude-3-5-sonnet-20241022" | |
| }; | |
| export async function createOpenCodeClientForRepo( | |
| params: CreateOpenCodeSandboxParams | |
| ) { | |
| const { | |
| repoUrl, | |
| revision, | |
| vcpus = 4, | |
| timeoutMs = ms("30m"), | |
| defaultModelId = "openai/gpt-4o-mini", | |
| } = params; | |
| // 1. Create sandbox with your GitHub repo | |
| const sandbox = await Sandbox.create({ | |
| source: { | |
| type: "git", | |
| url: repoUrl, | |
| ...(revision ? { revision } : {}), | |
| }, | |
| resources: { vcpus }, | |
| timeout: timeoutMs, | |
| ports: [4096], // OpenCode HTTP server port | |
| runtime: "node22", | |
| }); | |
| console.log("[sandbox] Created:", sandbox.sandboxId); | |
| console.log("[sandbox] Installing OpenCode CLI globally..."); | |
| // 2. Install OpenCode in the VM (requires sudo for global install) | |
| const installOpenCode = await sandbox.runCommand({ | |
| cmd: "npm", | |
| args: ["install", "-g", "opencode-ai"], | |
| stdout: process.stdout, | |
| stderr: process.stderr, | |
| env: { | |
| VERCEL_OIDC_TOKEN: process.env.VERCEL_OIDC_TOKEN!, | |
| }, | |
| sudo: true, | |
| }); | |
| if (installOpenCode.exitCode !== 0) { | |
| throw new Error( | |
| `[sandbox] OpenCode install failed with code ${installOpenCode.exitCode}` | |
| ); | |
| } | |
| console.log("[sandbox] Starting OpenCode server (opencode serve)..."); | |
| // 3. Start OpenCode HTTP server in detached mode so it keeps running | |
| await sandbox.runCommand({ | |
| cmd: "opencode", | |
| args: ["serve", "--hostname", "0.0.0.0", "--port", "4096"], | |
| stdout: process.stdout, | |
| stderr: process.stderr, | |
| detached: true, | |
| env: { | |
| VERCEL_OIDC_TOKEN: process.env.VERCEL_OIDC_TOKEN!, | |
| }, | |
| }); | |
| // Give the server a moment to boot (you can replace this with active health checks if you want) | |
| await sleep(3000); | |
| // 4. Get exposed public URL for the sandbox port | |
| const baseUrl = sandbox.domain(4096); | |
| console.log("[sandbox] OpenCode server URL:", baseUrl); | |
| // 5. Connect with OpenCode SDK | |
| const client = createOpencodeClient({ baseUrl }); | |
| // 6. Configure Vercel provider on the server | |
| console.log("[opencode] Configuring Vercel API key via auth.set..."); | |
| await client.auth.set({ | |
| path: { id: "vercel" }, | |
| body: { type: "api", key: process.env.VERCEL_OIDC_TOKEN! }, | |
| }); | |
| // 7. Create a starter session so you can prompt immediately | |
| console.log("[opencode] Creating initial session..."); | |
| const session = await client.session.create({ | |
| body: { title: "Sandbox session" }, | |
| }); | |
| if (session.error) { | |
| throw new Error(`[opencode] Failed to create session: ${session.error}`); | |
| } | |
| console.log("[opencode] Session ID:", session.data.id); | |
| // 8. Optionally send an initial prompt that references the repo | |
| if (defaultModelId) { | |
| console.log("[opencode] Sending initial prompt against the repo..."); | |
| const promptResult = await client.session.prompt({ | |
| path: { id: session.data.id }, | |
| body: { | |
| model: { | |
| providerID: "vercel", | |
| modelID: defaultModelId, | |
| }, | |
| parts: [ | |
| { | |
| type: "text", | |
| text: | |
| "You are connected to a Vercel Sandbox VM that has this GitHub repo checked out. " + | |
| "You can read and modify files via your normal tools. Reply with a short summary " + | |
| "of the project structure before making any edits.", | |
| }, | |
| { | |
| type: "text", | |
| text: "Please tell me how Answer Overflow does data fetching between the client and the server and the relevant files to look at.", | |
| }, | |
| ], | |
| }, | |
| }); | |
| if (promptResult.error) { | |
| throw new Error( | |
| `[opencode] Failed to send initial prompt: ${promptResult.error}` | |
| ); | |
| } | |
| console.log( | |
| "[opencode] Initial prompt response:", | |
| JSON.stringify(promptResult.data, null, 2) | |
| ); | |
| } | |
| // Return both the sandbox (so you can stop it later) and the OpenCode client | |
| return { | |
| sandbox, | |
| client, | |
| sessionId: session.data.id, | |
| baseUrl, | |
| }; | |
| } | |
| /** | |
| * Example usage as a standalone script: | |
| * | |
| * node --env-file .env.local --experimental-strip-types ./opencode-sandbox.ts | |
| */ | |
| async function main() { | |
| const repoUrl = "https://github.com/AnswerOverflow/AnswerOverflow.git"; | |
| const { sandbox, client, sessionId, baseUrl } = | |
| await createOpenCodeClientForRepo({ | |
| repoUrl, | |
| defaultModelId: "openai/gpt-4o-mini", | |
| }); | |
| console.log("[ready] OpenCode client is connected to:", baseUrl); | |
| console.log("[ready] Session ID:", sessionId); | |
| // When you're done, you can stop the sandbox: | |
| await sandbox.stop(); | |
| } | |
| main().catch((err) => { | |
| console.error(err); | |
| process.exit(1); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment