OpenCode
Add OpenCode AI coding assistant to your VMs.
The OpenCode integration installs OpenCode, an AI-powered coding assistant, and provides helper methods for exposing the web UI and creating API clients.
Installation
npm install @freestyle-sh/with-opencode freestyle-sandboxesUsage
import { freestyle, VmSpec } from "freestyle-sandboxes";
import { VmOpenCode } from "@freestyle-sh/with-opencode";
const { vm } = await freestyle.vms.create({
spec: new VmSpec({
with: {
opencode: new VmOpenCode(),
},
}),
});
// Expose the web UI
const { url } = await vm.opencode.routeWeb();
console.log(`OpenCode available at: ${url}`);Options
new VmOpenCode({
server: {
port: 4096, // Optional: API server port (default: 4096)
username: "admin", // Optional: Basic auth username (default: "opencode" when password is set)
password: "secret" // Optional: Basic auth password
},
web: {
port: 4097, // Optional: Web UI port (default: 4097)
username: "admin", // Optional: Basic auth username (default: "opencode" when password is set)
password: "secret" // Optional: Basic auth password
},
env: {
ANTHROPIC_API_KEY: "sk-ant-...", // Environment variables for OpenCode
},
config: {
model: "anthropic/claude-sonnet-4-20250514", // Default model to use
}
})| Option | Type | Default | Description |
|---|---|---|---|
server.port | number | 4096 | Port for the OpenCode API server |
server.username | string | "opencode" | Basic auth username (only used if password set) |
server.password | string | - | Basic auth password for the API server |
web.port | number | 4097 | Port for the OpenCode web UI |
web.username | string | "opencode" | Basic auth username (only used if password set) |
web.password | string | - | Basic auth password for the web UI |
env | Record<string, string> | {} | Environment variables passed to OpenCode services |
config | OpenCodeConfig | - | OpenCode configuration including model selection |
Authentication
OpenCode supports optional HTTP Basic Authentication for both the API server and web UI. Authentication is configured separately for each service, allowing different credentials for programmatic API access vs browser-based access.
Security Considerations
- If no password is set, the service runs without authentication (useful for private/internal VMs), but not recommended for publicly accessible instances
Example: With Authentication
import { freestyle, VmSpec } from "freestyle-sandboxes";
import { VmOpenCode } from "@freestyle-sh/with-opencode";
const { vm } = await freestyle.vms.create({
spec: new VmSpec({
with: {
opencode: new VmOpenCode({
server: {
username: "api-user",
password: "api-secret",
},
web: {
username: "web-user",
password: "web-secret",
},
}),
},
}),
});
// URLs will not include credentials for security
const { url } = await vm.opencode.routeWeb();
// Client is automatically configured with auth headers
const { client } = await vm.opencode.client();Providers
OpenCode supports multiple AI providers. Pass API keys via the env option and set the model in config.
See OpenCode Providers for all supported providers and their environment variables.
Note: Environment variables are stored as plain text in the VM's startup scripts. Users with direct or indirect access to the VM may be able to view these values. Use appropriately scoped API keys and rotate them if the VM is shared or exposed.
import { freestyle, VmSpec } from "freestyle-sandboxes";
import { VmOpenCode } from "@freestyle-sh/with-opencode";
const spec = new VmSpec({
with: {
opencode: new VmOpenCode({
env: {
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY!,
},
config: {
model: "anthropic/claude-sonnet-4-5-20250929",
},
}),
},
});
const { vm } = await freestyle.vms.create({ spec });API
vm.opencode.routeWeb(options?)
Exposes the OpenCode web UI on a public domain.
// Use auto-generated domain
const { url } = await vm.opencode.routeWeb();
console.log(url); // https://abc123-opencode.style.dev
// Use custom domain
const { url } = await vm.opencode.routeWeb({
domain: "my-opencode.example.com"
});Parameters:
| Option | Type | Default | Description |
|---|---|---|---|
domain | string | Auto-generated | Custom domain for the web UI |
Returns: Promise<{ url: string }>
vm.opencode.client(options?)
Creates an authenticated OpenCode SDK client connected to the API server.
const { client } = await vm.opencode.client();
// Use the client to interact with OpenCode
const sessions = await client.session.list();Parameters:
| Option | Type | Default | Description |
|---|---|---|---|
domain | string | Auto-generated | Custom domain for the API server |
Returns: Promise<{ client: OpencodeClient }>
vm.opencode.serverPort()
Returns the configured API server port.
const port = vm.opencode.serverPort(); // 4096vm.opencode.webPort()
Returns the configured web UI port.
const port = vm.opencode.webPort(); // 4097Example: Full Setup with API Client
import { freestyle, VmSpec } from "freestyle-sandboxes";
import { VmOpenCode } from "@freestyle-sh/with-opencode";
const { vm } = await freestyle.vms.create({
spec: new VmSpec({
with: {
opencode: new VmOpenCode(),
},
}),
});
// Expose the web UI for browser access
const { url } = await vm.opencode.routeWeb();
console.log(`Web UI: ${url}`);
// Create an API client for programmatic access
const { client } = await vm.opencode.client();
// Create a new coding session
const session = await client.session.create({
path: "/workspace"
});
// List files in the workspace
const files = await client.path.get({});
console.log("Files:", files.data);Example: Custom Ports
import { freestyle, VmSpec } from "freestyle-sandboxes";
import { VmOpenCode } from "@freestyle-sh/with-opencode";
const { vm } = await freestyle.vms.create({
spec: new VmSpec({
with: {
opencode: new VmOpenCode({
server: { port: 8080 },
web: { port: 3000 },
}),
},
}),
});
const { url } = await vm.opencode.routeWeb();
console.log(`OpenCode running on custom ports`);
console.log(`API: ${vm.opencode.serverPort()}`); // 8080
console.log(`Web: ${vm.opencode.webPort()}`); // 3000