> ## Documentation Index
> Fetch the complete documentation index at: https://docs.novita.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenCode Sandbox: Run Coding Agents with Novita

> Learn how to set up an OpenCode sandbox with Novita AI. Configure your coding agent and run OpenCode in an isolated environment with this step-by-step guide.

This guide shows you how to set up an OpenCode sandbox using Novita Sandbox. You'll configure the environment, launch OpenCode, and run your coding agent in an isolated runtime.

## What you'll build

You will build a reusable sandbox template with OpenCode pre-installed, then start a sandbox from it and hand OpenCode a coding task. OpenCode runs entirely inside the sandbox: it writes files, runs shell commands, and executes the code it generates there, while a Novita-hosted model does the reasoning. Your machine only sends the task and reads back the result.

```mermaid theme={"system"}
flowchart TB
  App["Your app<br/>(novita-sandbox SDK)"] -- "create sandbox,<br/>run opencode" --> Sandbox
  subgraph Sandbox["Novita Sandbox (isolated runtime)"]
    OpenCode["OpenCode CLI"] --> Tools["Files · Shell · Code"]
  end
  OpenCode -- "OpenAI-compatible API" --> Model["Novita LLM API<br/>api.novita.ai/openai/v1"]
```

## Prerequisites

* **Novita API key** — create one in [Key Management](https://novita.ai/settings/key-management) and export it as `NOVITA_API_KEY`. The same key authenticates both the sandbox and the model calls.
* **Novita Sandbox SDK** — Python 3.10+ or Node.js 20+:

```bash Python icon="python" theme={"system"}
pip install novita-sandbox
```

```bash JavaScript & TypeScript icon="js" theme={"system"}
npm install novita-sandbox
```

* **OpenCode version** — this guide installs the `opencode-ai` npm package and was written against OpenCode **1.19**. The configuration uses the `provider` / `permission` format that OpenCode 1.19 reads. The `providers` / `permissions` format in the newer published schema is not supported by 1.19 and is silently ignored or rejected.

```bash theme={"system"}
export NOVITA_API_KEY=sk_your_api_key
```

## Quickstart

### 1. Build an OpenCode sandbox template (one time)

A [template](https://docs.novita.ai/guides/sandbox-template-build-operations.md) bakes OpenCode into the sandbox image, so each new sandbox starts in seconds instead of reinstalling it. Build it once; reuse it for every run.

```python Python icon="python" theme={"system"}
from novita_sandbox import Novita

novita = Novita()
template = novita.template.new().from_node_image("22").npm_install("opencode-ai", g=True)

build = novita.template.build(template, "opencode-agent", cpu_count=2, memory_mb=2048)
print("Template ID:", build.template_id)
```

```js JavaScript & TypeScript icon="js" theme={"system"}
import { Novita } from 'novita-sandbox'

const novita = new Novita()
const template = novita.template.new().fromNodeImage('22').npmInstall('opencode-ai', { g: true })

const build = await novita.template.build(template, 'opencode-agent', { cpuCount: 2, memoryMB: 2048 })
console.log('Template ID:', build.templateId)
```

### 2. Run OpenCode in a sandbox

The script below writes an OpenCode config that points at the Novita LLM API, runs one non-interactive task with `opencode run`, and kills the sandbox.

```python Python icon="python" theme={"system"}
import json, os
from novita_sandbox import Novita

MODEL = "deepseek/deepseek-v4.1-flash"
config = {
    "model": f"novita/{MODEL}",
    "provider": {"novita": {
        "name": "Novita AI", "npm": "@ai-sdk/openai-compatible",
        "options": {"baseURL": "https://api.novita.ai/openai/v1", "apiKey": "{env:NOVITA_API_KEY}"},
        "models": {MODEL: {"name": MODEL}}}},
    "permission": {"edit": "allow", "bash": "allow"},
}

sandbox = Novita().sandbox.create("opencode-agent", timeout=1800, envs={"NOVITA_API_KEY": os.environ["NOVITA_API_KEY"]})
try:
    sandbox.files.write("/home/user/.config/opencode/opencode.json", json.dumps(config))
    sandbox.commands.run('opencode run "Write fizzbuzz.js, run it with node, and show the output"',
                         cwd="/home/user", on_stdout=lambda d: print(d, end=""), timeout=0)
finally:
    sandbox.kill()
```

```js JavaScript & TypeScript icon="js" theme={"system"}
import { Novita } from 'novita-sandbox'

const MODEL = 'deepseek/deepseek-v4.1-flash'
const config = {
  model: `novita/${MODEL}`,
  provider: { novita: {
    name: 'Novita AI', npm: '@ai-sdk/openai-compatible',
    options: { baseURL: 'https://api.novita.ai/openai/v1', apiKey: '{env:NOVITA_API_KEY}' },
    models: { [MODEL]: { name: MODEL } } } },
  permission: { edit: 'allow', bash: 'allow' },
}

const sandbox = await new Novita().sandbox.create('opencode-agent', { timeoutMs: 1_800_000, envs: { NOVITA_API_KEY: process.env.NOVITA_API_KEY } })
try {
  await sandbox.files.write('/home/user/.config/opencode/opencode.json', JSON.stringify(config))
  await sandbox.commands.run('opencode run "Write fizzbuzz.js, run it with node, and show the output"',
    { cwd: '/home/user', onStdout: (d) => process.stdout.write(d), timeoutMs: 0 })
} finally {
  await sandbox.kill()
}
```

> [!NOTE]
>
> **`opencode run "<prompt>"`** runs OpenCode without its terminal UI: it processes the prompt, prints the result, and exits, which makes it scriptable. OpenCode already allows most tool use by default; the explicit `permission` settings pin file edits (`edit`) and shell commands (`bash`) to `allow`, so a project-level config in a cloned repo can't switch them to prompting and stall a headless run. That is safe here because everything happens inside a disposable sandbox.

## How it works

OpenCode gets a full Linux environment inside the sandbox. You drive it with the SDK: `files` to move data in and out, `commands` to run OpenCode and anything else. The snippets below assume a `sandbox` created and configured as in the Quickstart.

### Execute generated code

OpenCode writes code and runs it in the sandbox's shell. Its stdout and stderr stream back to you through `on_stdout` / `on_stderr`, and the call returns the exit code.

```python Python icon="python" theme={"system"}
result = sandbox.commands.run(
    'opencode run "Create server.js with a /health route using Node\'s http module, start it, and test it with curl"',
    cwd="/home/user",
    on_stdout=lambda d: print(d, end=""),
    on_stderr=lambda d: print(d, end=""),
    timeout=0,
)
print("exit code:", result.exit_code)
```

```js JavaScript & TypeScript icon="js" theme={"system"}
const result = await sandbox.commands.run(
  'opencode run "Create server.js with a /health route using Node\'s http module, start it, and test it with curl"',
  {
    cwd: '/home/user',
    onStdout: (d) => process.stdout.write(d),
    onStderr: (d) => process.stderr.write(d),
    timeoutMs: 0,
  }
)
console.log('exit code:', result.exitCode)
```

### Read and write files

Upload inputs before the run and read the agent's output afterwards. For whole repositories, use [`sandbox.git.clone`](https://docs.novita.ai/guides/sandbox-git-integration); for large files, see [Upload](https://docs.novita.ai/guides/sandbox-filesystem-upload.md) and [Download](https://docs.novita.ai/guides/sandbox-filesystem-download.md).

```python Python icon="python" theme={"system"}
sandbox.files.write("/home/user/data/sales.csv", open("sales.csv").read())
sandbox.commands.run('opencode run "Summarize data/sales.csv into report.md"', cwd="/home/user", timeout=0)

print(sandbox.files.read("/home/user/report.md"))
```

```js JavaScript & TypeScript icon="js" theme={"system"}
import { readFile } from 'node:fs/promises'

await sandbox.files.write('/home/user/data/sales.csv', await readFile('sales.csv', 'utf8'))
await sandbox.commands.run('opencode run "Summarize data/sales.csv into report.md"', { cwd: '/home/user', timeoutMs: 0 })

console.log(await sandbox.files.read('/home/user/report.md'))
```

### Run commands yourself

You can run any command next to the agent — install dependencies before it starts, or verify its work afterwards without trusting its own report.

```python Python icon="python" theme={"system"}
sandbox.commands.run("npm init -y && npm install --save-dev jest", cwd="/home/user", timeout=300)
sandbox.commands.run('opencode run "Add Jest unit tests for server.js"', cwd="/home/user", timeout=0)

check = sandbox.commands.run("npx jest", cwd="/home/user")
print(check.stdout)
```

```js JavaScript & TypeScript icon="js" theme={"system"}
await sandbox.commands.run('npm init -y && npm install --save-dev jest', { cwd: '/home/user', timeoutMs: 300_000 })
await sandbox.commands.run('opencode run "Add Jest unit tests for server.js"', { cwd: '/home/user', timeoutMs: 0 })

const check = await sandbox.commands.run('npx jest', { cwd: '/home/user' })
console.log(check.stdout)
```

> [!TIP]
>
> A command that exits non-zero raises `CommandExitException` (Python) or `CommandExitError` (JavaScript). Catch it when a failing test run is an expected outcome.

## Use Novita models

The Quickstart already routes OpenCode through Novita's [OpenAI-compatible API](https://docs.novita.ai/guides/opencode.md). To switch models, change one line — `MODEL` — to any model ID from the [Novita model library](https://novita.ai/models), for example `moonshotai/kimi-k2.7-code` or `zai-org/glm-5.3`.

```python theme={"system"}
MODEL = "moonshotai/kimi-k2.7-code"
```

The model ID must match the Model Hub value exactly. Model usage is billed separately from sandbox runtime, at the model's own per-token rate.

## Production tips

* **Timeouts** — `timeout` (seconds; `timeoutMs` in JavaScript) on `sandbox.create` is the sandbox's maximum lifetime; extend it while the agent works with [`set_timeout`](https://docs.novita.ai/guides/sandbox-timeout-handle). Pass `timeout=0` to `commands.run` so a long agent turn isn't cut off at the SDK's default command timeout.
* **Pause and resume** — for multi-step jobs, [pause](https://docs.novita.ai/guides/sandbox-pause-resume.md) the sandbox between steps instead of killing it. Files, installed packages and OpenCode's session state survive, and CPU and RAM billing stops while it is paused.
* **Concurrency** — each sandbox is isolated, so you can run one OpenCode task per sandbox in parallel. Concurrent sandboxes are capped by your account tier (5 on Free, 100 on Paid); see [Quota Limits](https://docs.novita.ai/guides/sandbox-quota-limit.md).
* **Cost** — sandboxes are billed per second while running. A 2 vCPU / 2 GiB sandbox costs about \$0.0000260/s, so a 10-minute task is roughly **\$0.016** in sandbox time, plus model tokens. See [Pricing](https://docs.novita.ai/guides/sandbox-pricing.md).
* **Always clean up** — kill sandboxes in a `finally` block (as above) so a crash in your script doesn't leave one running until its timeout.

## Troubleshooting

> [accordion] 402: insufficient balance when creating a sandbox
>
> Your account balance can't cover a new sandbox. Top up on the [Billing](https://novita.ai/billing) page or claim the new-user credits described in [Pricing](https://docs.novita.ai/guides/sandbox-pricing.md), then retry.

> [accordion] Template not found when creating the sandbox
>
> The name passed to `sandbox.create` must match the name you built (`opencode-agent` above) in the same team as your API key. Run `novita.template.list()` to see your templates, or pass `build.template_id` directly.

> [accordion] OpenCode exits with a model or provider error
>
> Check that the `model` value is `novita/<model ID>`, that the same ID is a key under `provider.novita.models`, and that `NOVITA_API_KEY` was passed in `envs` — run `echo $NOVITA_API_KEY` in the sandbox to check it isn't empty, because an unset variable becomes an empty key and shows up as `Unauthorized`. Run `opencode debug config` in the sandbox — it prints the resolved configuration and reports an invalid file — and `opencode models` to check that `novita/<model ID>` is listed.

## Next steps

> [card] [Sandbox SDK reference](https://docs.novita.ai/guides/sandbox-create.md)
>
> Every option for creating and managing sandboxes.

> [card] [Build templates](https://docs.novita.ai/guides/sandbox-template-build-operations.md)
>
> Add your own tools and dependencies to the OpenCode image.

> [card] [Sandbox cookbook](https://github.com/novitalabs/sandbox-cookbook)
>
> Runnable Novita Sandbox examples on GitHub.

> [card] [More agent integrations](https://docs.novita.ai/guides/sandbox-claude-code.md)
>
> Run Claude Code, Codex, or OpenClaw in a sandbox.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.