mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 06:04:43 +00:00
feat: add opencode container example connecting to sandbox agent
This commit is contained in:
parent
a7b3881099
commit
2b3e8914fc
6 changed files with 174 additions and 0 deletions
1
.turbo
Symbolic link
1
.turbo
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/home/nathan/sandbox-agent/.turbo
|
||||
21
examples/opencode/package.json
Normal file
21
examples/opencode/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "@sandbox-agent/example-opencode",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "tsx src/opencode.ts",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "latest",
|
||||
"@sandbox-agent/example-shared": "workspace:*",
|
||||
"dockerode": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dockerode": "latest",
|
||||
"@types/node": "latest",
|
||||
"tsx": "latest",
|
||||
"typescript": "latest",
|
||||
"vitest": "^3.0.0"
|
||||
}
|
||||
}
|
||||
134
examples/opencode/src/opencode.ts
Normal file
134
examples/opencode/src/opencode.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import Docker from "dockerode";
|
||||
import { waitForHealth } from "@sandbox-agent/example-shared";
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk";
|
||||
|
||||
const IMAGE = "alpine:latest";
|
||||
const PORT = 2468;
|
||||
|
||||
const docker = new Docker({ socketPath: "/var/run/docker.sock" });
|
||||
|
||||
async function ensureImage(image: string): Promise<void> {
|
||||
try {
|
||||
await docker.getImage(image).inspect();
|
||||
} catch {
|
||||
console.log(`Pulling ${image}...`);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
docker.pull(image, (err: Error | null, stream: NodeJS.ReadableStream) => {
|
||||
if (err) return reject(err);
|
||||
docker.modem.followProgress(stream, (followError: Error | null) => {
|
||||
if (followError) return reject(followError);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function setupDockerSandboxAgent(): Promise<{
|
||||
baseUrl: string;
|
||||
cleanup: () => Promise<void>;
|
||||
}> {
|
||||
await ensureImage(IMAGE);
|
||||
|
||||
console.log("Starting container...");
|
||||
const container = await docker.createContainer({
|
||||
Image: IMAGE,
|
||||
Cmd: [
|
||||
"sh",
|
||||
"-c",
|
||||
[
|
||||
"apk add --no-cache curl ca-certificates libstdc++ libgcc bash",
|
||||
"curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh",
|
||||
"sandbox-agent install-agent claude",
|
||||
"sandbox-agent install-agent codex",
|
||||
`sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`,
|
||||
].join(" && "),
|
||||
],
|
||||
Env: [
|
||||
process.env.ANTHROPIC_API_KEY ? `ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}` : "",
|
||||
process.env.OPENAI_API_KEY ? `OPENAI_API_KEY=${process.env.OPENAI_API_KEY}` : "",
|
||||
].filter(Boolean),
|
||||
ExposedPorts: { [`${PORT}/tcp`]: {} },
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: { [`${PORT}/tcp`]: [{ HostPort: `${PORT}` }] },
|
||||
},
|
||||
});
|
||||
|
||||
await container.start();
|
||||
|
||||
const baseUrl = `http://127.0.0.1:${PORT}`;
|
||||
await waitForHealth({ baseUrl });
|
||||
|
||||
const cleanup = async () => {
|
||||
try {
|
||||
await container.stop({ t: 5 });
|
||||
} catch {}
|
||||
try {
|
||||
await container.remove({ force: true });
|
||||
} catch {}
|
||||
};
|
||||
|
||||
return { baseUrl, cleanup };
|
||||
}
|
||||
|
||||
const { baseUrl, cleanup } = await setupDockerSandboxAgent();
|
||||
process.once("SIGINT", async () => {
|
||||
await cleanup();
|
||||
process.exit(0);
|
||||
});
|
||||
process.once("SIGTERM", async () => {
|
||||
await cleanup();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
const opencodeBaseUrl = `${baseUrl}/opencode`;
|
||||
console.log(`OpenCode API: ${opencodeBaseUrl}`);
|
||||
|
||||
const client = createOpencodeClient({ baseUrl: opencodeBaseUrl });
|
||||
|
||||
const health = await client.global.health();
|
||||
const healthError = (health as any)?.error;
|
||||
if (healthError) {
|
||||
console.warn(`OpenCode health error: ${healthError}`);
|
||||
} else {
|
||||
console.log("OpenCode health: ok");
|
||||
}
|
||||
|
||||
const session = await client.session.create();
|
||||
const sessionId = session.data?.id;
|
||||
if (!sessionId) {
|
||||
await cleanup();
|
||||
throw new Error("OpenCode session ID missing");
|
||||
}
|
||||
|
||||
const eventStream = await client.event.subscribe();
|
||||
const stream = (eventStream as any).stream as AsyncIterable<any> & { return?: () => Promise<void> };
|
||||
|
||||
await client.session.promptAsync({
|
||||
path: { id: sessionId },
|
||||
body: {
|
||||
parts: [{ type: "text", text: "Say hello from OpenCode." }],
|
||||
},
|
||||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
void stream.return?.();
|
||||
}, 60_000);
|
||||
|
||||
try {
|
||||
for await (const event of stream) {
|
||||
const eventSessionId = event?.properties?.session?.id ?? event?.session?.id;
|
||||
if (eventSessionId && eventSessionId !== sessionId) continue;
|
||||
const type = event?.type ?? "unknown";
|
||||
console.log(`event: ${type}`);
|
||||
if (type === "session.idle" || type === "session.error") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
await stream.return?.();
|
||||
}
|
||||
|
||||
await cleanup();
|
||||
16
examples/opencode/tsconfig.json
Normal file
16
examples/opencode/tsconfig.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
}
|
||||
1
node_modules
Symbolic link
1
node_modules
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/home/nathan/sandbox-agent/node_modules
|
||||
1
target
Symbolic link
1
target
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/home/nathan/sandbox-agent/target
|
||||
Loading…
Add table
Add a link
Reference in a new issue