npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

agentbox-sdk

v0.1.520

Published

Swappable coding agents and sandbox providers for Bun and TypeScript.

Readme

AgentBox

Live demo

Run coding agents inside sandboxes. One API, any provider.

Unlike wrappers that shell out to CLIs in non-interactive mode (e.g. claude --print), AgentBox launches each agent as a server process inside the sandbox and communicates over WebSocket or HTTP. This preserves the full interactive capabilities of each agent — approval flows, tool-use control, streaming events.

import { Agent, Sandbox } from "agentbox-sdk";

const sandbox = new Sandbox("local-docker", {
  workingDir: "/workspace",
  image: process.env.IMAGE_ID!,
  env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});

await sandbox.findOrProvision();

const run = new Agent("claude-code", {
  sandbox,
  cwd: "/workspace",
  approvalMode: "auto",
}).stream({
  model: "sonnet",
  input: "Create a hello world Express server in /workspace/server.ts",
});

for await (const event of run) {
  if (event.type === "text.delta") process.stdout.write(event.delta);
}

await sandbox.delete();

Providers are mix-and-match:

Swap either one and your app code stays the same.

Install

npm install agentbox-sdk

Requires Node >= 20. The agent CLI you want to use (claude, opencode, codex) should be installed inside your sandbox image.

Getting started

1. Build a sandbox image

AgentBox ships with built-in image presets. Build one for your sandbox provider:

npx agentbox image build --provider local-docker --preset browser-agent

This prints an image reference (a Docker tag, Modal image ID, E2B template, or Daytona snapshot depending on the provider). Set it as IMAGE_ID:

export IMAGE_ID=<printed value>

2. Run an agent

import { Agent, Sandbox } from "agentbox-sdk";

const sandbox = new Sandbox("local-docker", {
  workingDir: "/workspace",
  image: process.env.IMAGE_ID!,
  env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});

// Explicitly attach to / create the sandbox before running anything.
// Subsequent `sandbox.run`, `sandbox.gitClone`, agent runs, etc. all
// require this to have happened first.
await sandbox.findOrProvision();

const agent = new Agent("claude-code", {
  sandbox,
  cwd: "/workspace",
  approvalMode: "auto",
});

const result = await agent.run({
  model: "sonnet",
  input:
    "Explain the project structure and write a summary to /workspace/OVERVIEW.md",
});

console.log(result.text);
await sandbox.delete();

3. Stream events

agent.stream() returns an async iterable of normalized events:

const run = agent.stream({
  model: "sonnet",
  input: "Write a fizzbuzz in Python",
});

for await (const event of run) {
  if (event.type === "text.delta") {
    process.stdout.write(event.delta);
  }
}

const result = await run.finished;

Claude Code can leave work running after its turn ends (run_in_background shells, Monitor, background subagents, scheduled wakeups) and re-prompts itself when that work finishes. AgentBox reports it through background.tasks events — tasks is the full live set after each change and waiting is true while the harness has ended its turn and the run stays open only for those tasks (or, with an empty set, for the wake-up the CLI queues for a task that finished mid-turn) — and settles the run on the follow-up turn's result instead of the first one. Once background work has been seen, a turn end settles the run when Claude emits session_state_changed: idle and the live task set is empty. AgentBox enables this documented CLI event with CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1; there is no extra model call or fixed completion delay (upstream opt-in documentation). Older/custom CLIs that never emit session state use a 15s grace as a compatibility fallback. A final background.tasks with tasks: [] and waiting: false precedes the settle. backgroundTaskTimeoutMs bounds the total time spent waiting across the run: default 30 minutes, 0 settles at the first turn end as before, Infinity waits forever. On expiry the tasks are stopped best-effort and the run completes with the last turn's text.

Event retention

AgentResult.events and AgentResult.rawEvents replay everything a run emitted, which means a run holds its whole transcript — every tool result, every file the agent read — in memory until it settles. A host that consumes the live stream (for await (const event of run) / run.rawEvents()) never reads those arrays, so it can turn retention off:

const agent = new Agent("claude-code", { sandbox, retainEvents: false });

Both arrays then settle as []. Streamed events, text and costData are unaffected — cost is accumulated as payloads arrive rather than recomputed from a retained transcript.

Harness commands

A run whose input starts with /name args carries the harness's own slash command. resolveHarnessCommand(provider, input) keeps the text as typed and returns command: { name, args } for AgentRunConfig.command; /plan, /agent, and /goal stay harness modes as before. Each adapter dispatches what its CLI supports headlessly:

  • Claude Code parses slash commands from the user message itself (/compact, /context, /init, skills, custom commands, plugin commands).
  • Codex maps /compact to thread/compact/start, /review [instructions] to review/start, /init to the TUI's AGENTS.md prompt, and an installed skill to its $name mention.
  • OpenCode maps /compact to POST /session/:id/summarize, a configured command to POST /session/:id/command, and an installed skill to a directive the model follows. Configured command calls preserve file and image attachments. They reject a per-run systemPrompt because OpenCode's command endpoint cannot accept it; use a normal prompt or the setup-time OpenCodeAgentOptions.systemPrompt instead. This also applies to the runtime system appendix generated from configured MCPs, skills, sub-agents, or commands.

Anything the harness does not know is sent as plain text. Each run emits one harness.commands event listing what it can run (HarnessCommandDescriptor[]: name, description, argument hint, source), so a host can build its / menu; builtinHarnessCommands(provider) gives the static list before a run exists. Tasks marked ambient by Claude (such as live-update watchers) do not keep the run waiting. The SDK's full task snapshots take precedence over task start and finish events, whose ordering relative to those snapshots is unspecified.

AgentBox does not inject a Stop-hook prompt or infer from the answer that a running task is unwanted. A genuinely live polling helper keeps the run open until it finishes, is stopped, or reaches the wait budget. Unlike the interactive CLI, an AgentBox run owns the query lifetime: completing it closes the process. Keep the streaming input open while waiting so background continuations retain their hooks, permissions, and SDK MCP control channel.

The host decides when a wait has stopped being useful. A foreground command that outlives Bash's timeout is moved to the background by the CLI, and a model that answers without it leaves the run open for work nobody needs. run.finishBackgroundWait() ends the wait: a run that is only waiting stops what is left and completes with the answer it already has (run.completed, not run.cancelled as with abort()). The request latches, so calling it while a turn is active applies at that turn's end; a run that never waits ignores it. Use it when the user moves on, for example by sending a follow-up. It applies equally to Codex native goal waits and OpenCode background subagents.

for await (const event of run) {
  if (event.type === "background.tasks" && event.waiting && userMovedOn()) {
    await run.finishBackgroundWait();
  }
}

Parked runs (claude-code in a sandbox). A sandbox outlives the run, and so does the in-sandbox daemon, so a run there does not have to stay open at all. With provider.parkBackgroundWork: { wakeUrl, wakeToken } a run whose turn ends with background work still live completes at its answer and the daemon keeps the CLI for that work; the last background.tasks event carries parked: true and the tasks still running. A turn that ends with nothing live is untouched: the CLI winds down as before. While parked:

  • The next run that resumes the session takes the same CLI over (never a second one on the session). It learns what was live, skips the results of turns that ran before its own input, answers, and parks again if work is still live.
  • When the CLI starts a turn on its own with nobody attached, the daemon POSTs { runId, sessionId } to wakeUrl with Authorization: Bearer <wakeToken>, retrying until it gets a 2xx. Stream that turn with agent.stream({ input: "", resumeSessionId, resumeParked: true }); it completes with empty text if nothing is parked. Whether to act on a wake is the host's call: a host already running that session is attached to the same CLI, which delivers the turn inside that run.
  • The park lasts what is left of backgroundTaskTimeoutMs, then the CLI winds down as at the wait ceiling. A rewind (forkSessionId) and an explicit DELETE end it too. With parking on, finishBackgroundWait() parks rather than stops the work. Output is buffered while nobody is attached (partial deltas excepted), up to 16 MB.

Codex owns command polling (write_stdin), yielded code-mode waits (wait), and subagent waits (wait_agent). AgentBox finishes an ordinary Codex run on its root turn/completed, regardless of shell processes still running. It does not classify commands, wait for their exit, or inject synthetic follow-up turns. Raw tool events remain available. A command ending after the final turn does not restart the model. Remote cleanup disconnects from the shared app-server; local cleanup shuts down the app-server it owns.

Native Codex goals are a separate lifecycle: an active root-thread goal keeps the run open between turns so Codex can continue on its own. AgentBox reports these gaps with background.tasks { tasks: [], waiting: true } and clears the wait when the next native turn starts. A root goal becoming complete or blocked ends the run after its final turn; a goal cleared or made inactive while idle settles immediately. Child-thread and stale-turn goal updates do not end the root run. blocked preserves the final answer without implying that the objective was achieved.

Only these native goal waits use backgroundTaskTimeoutMs for Codex. The 15-second idle grace and total wait ceiling bound the gap between native turns; 0 settles at the first turn. Expiry settles with the last answer without terminating remote shell processes. A user message can resume an idle goal wait. Stateless cancellation (Agent.attach(...).abort()) interrupts an active turn, or starts a turn only to interrupt it if the thread is idle, then stops the idle thread's leftover terminals. Claude Code and OpenCode retain their own background-work handling described above and below.

OpenCode has no background shells, monitors, or wake-ups; its only work that outlives a turn is task {background: true}, which the server accepts only when it runs with OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true (or the OPENCODE_EXPERIMENTAL umbrella) in its environment. With that flag the parent session goes idle while the child session runs and OpenCode itself re-prompts the parent with the child's result. AgentBox recognises a background child from the parent's task tool call (metadata.background), so nothing depends on the caller's env: while such a child is live at the parent's idle the run stays open, the children are reported through background.tasks (type: "subagent", description is the task description / child session title), waiting flips to false when the parent resumes, and the run settles on the next parent idle with nothing live — its text is the parent's last assistant message, never the injected result. Child liveness comes from SSE frames reconciled against GET /session/status at each parent idle and while waiting, and the injected <task id=… state=…> result also counts as the child's completion, so a lost frame cannot hold the run open. A child that ends without waking the parent settles the run after the 15s grace. backgroundTaskTimeoutMs bounds the total wait as for Claude Code; on expiry, or when the run fails with a child still live, the leftover subagents are stopped best-effort via POST /session/:id/abort. Aborting the run while waiting cancels the children through the same endpoint. Without the flag nothing changes: a parent idle is the end of the run.

Agents

Four agent providers are supported. Each wraps a CLI that runs inside the sandbox:

| Provider | CLI | Model format | | ------------- | ---------- | ----------------------------------------------- | | claude-code | claude | sonnet, opus, haiku | | opencode | opencode | anthropic/claude-sonnet-4-6, openai/gpt-4.1 | | codex | codex | gpt-5.3-codex, gpt-5.4 |

new Agent("claude-code", { sandbox, cwd: "/workspace", approvalMode: "auto" });
new Agent("open-code", { sandbox, cwd: "/workspace", approvalMode: "auto" });
new Agent("codex", { sandbox, cwd: "/workspace", approvalMode: "auto" });

Reasoning effort

Pass an optional reasoning level alongside model on any run. It maps to each provider's native reasoning control: Codex's effort on turn/start, Claude Code's --effort flag, and OpenCode's reasoningEffort agent variant.

await agent.run({
  model: "sonnet",
  reasoning: "high", // "low" | "medium" | "high" | "xhigh" | "max" | "ultra"
  input: "Refactor this module and explain your reasoning.",
});

xhigh requires a model that supports it (e.g. Claude Opus 4.7+, Codex gpt-5.4).

Codex also supports max (maximum reasoning) and ultra (maximum reasoning with automatic task delegation). Astra, GPT-5.6 Sol, and GPT-5.6 Terra support both; GPT-5.6 Luna supports max. Check the runtime's model/listsupportedReasoningEfforts for model availability. Both values are forwarded unchanged to turn/start.effort, including resumed and plan-mode turns; other AgentBox providers reject them.

Open-source & custom models (OpenRouter, OSS)

Codex isn't limited to OpenAI models — it can route through any OpenAI-compatible endpoint (OpenRouter, a local Ollama/LM Studio/vLLM server, a proxy). Just like the opencode provider lights up OpenRouter from OPENROUTER_API_KEY, the codex provider does too: set the key in the agent env and pass an OpenRouter model slug.

const agent = new Agent("codex", {
  sandbox,
  cwd: "/workspace",
  approvalMode: "auto",
  env: { OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY! },
});

await agent.run({
  model: "openai/gpt-5.3-codex", // any OpenRouter model slug
  input: "Explain the project structure.",
});

When OPENROUTER_API_KEY is present (and OPENAI_API_KEY is not), AgentBox auto-registers an openrouter model provider pointing at https://openrouter.ai/api/v1 and selects it. Override the endpoint with OPENROUTER_BASE_URL.

For any other OpenAI-compatible endpoint, declare providers explicitly via provider.modelProviders and pick one with provider.modelProvider:

new Agent("codex", {
  sandbox,
  cwd: "/workspace",
  env: { TOGETHER_API_KEY: process.env.TOGETHER_API_KEY! },
  provider: {
    modelProvider: "together",
    modelProviders: {
      together: {
        name: "Together",
        baseUrl: "https://api.together.xyz/v1",
        envKey: "TOGETHER_API_KEY",
        wireApi: "responses", // codex removed the "chat" wire API
      },
    },
  },
});

These are written into Codex's config.toml as [model_providers.*] blocks, which the codex app-server reads via CODEX_HOME. The model slug stays a per-run value; the provider is agent-level config. Note that codex dropped the Chat Completions wire API in early 2026 — providers must speak the Responses API (wire_api = "responses"), which OpenRouter and LM Studio support; chat-only backends need a responses→chat proxy.

Sandboxes

Five sandbox providers are supported. Each gives you an isolated environment with the same interface:

| Provider | What it is | Auth | | -------------- | ---------------------- | ------------------------------------------------------- | | local-docker | Local Docker container | Docker daemon | | e2b | Cloud micro-VM | E2B_API_KEY | | modal | Cloud container | MODAL_TOKEN_ID + MODAL_TOKEN_SECRET | | daytona | Cloud dev environment | DAYTONA_API_KEY | | vercel | Ephemeral cloud VM | VERCEL_TOKEN + VERCEL_TEAM_ID + VERCEL_PROJECT_ID |

Every sandbox supports: findOrProvision(), run(), runAsync(), gitClone(), uploadAndRun(), openPort(), getPreviewLink(), snapshot(), stop(), delete().

Provisioning lifecycle

new Sandbox(...) only stores configuration — it does not create or attach to a real sandbox. Call findOrProvision() once when you're ready to start using it, and every subsequent operation (run, gitClone, uploadAndRun, agent runs, …) reuses that sandbox:

const sandbox = new Sandbox("modal", {
  /* … */
});

await sandbox.findOrProvision(); // attach to existing tagged sandbox or create a fresh one
await sandbox.gitClone({ repoUrl: "…" });
const result = await sandbox.run("pnpm install");

Calling a method that needs a live sandbox before findOrProvision() throws a clear error. This makes the (potentially slow) attach / create step explicit and lets you control exactly when it happens.

Vercel sandboxes use runtime snapshots instead of pre-built images — call sandbox.snapshot() to capture state and pass the returned id via provider.snapshotId on the next run.

Vercel also requires ports to be declared at create time via provider.portsopenPort() is a no-op at runtime, so any port the agent (or your own code) will listen on must be listed up front:

const sandbox = new Sandbox("vercel", {
  provider: {
    snapshotId: process.env.VERCEL_SNAPSHOT_ID!,
    ports: [4096], // e.g. opencode; codex/claude-code use 43180
  },
});

Skills

Attach GitHub repos as agent skills. They're cloned into the sandbox and surfaced to the agent:

const agent = new Agent("claude-code", {
  sandbox,
  cwd: "/workspace",
  approvalMode: "auto",
  skills: [
    {
      name: "agent-browser",
      repo: "https://github.com/vercel-labs/agent-browser",
    },
  ],
});

You can also embed skills inline:

skills: [
  {
    source: "embedded",
    name: "lint-fix",
    files: {
      "SKILL.md": "Run `npm run lint:fix` and verify the output is clean.",
    },
  },
],

Sub-agents

Delegate tasks to specialized sub-agents:

const agent = new Agent("claude-code", {
  sandbox,
  cwd: "/workspace",
  approvalMode: "auto",
  subAgents: [
    {
      name: "reviewer",
      description: "Reviews code for bugs and security issues",
      instructions:
        "Flag bugs, security issues, and missing edge cases. Be concise.",
      tools: ["bash", "read"],
    },
  ],
});

MCP servers

Connect MCP servers to give agents access to external tools:

const agent = new Agent("claude-code", {
  sandbox,
  cwd: "/workspace",
  approvalMode: "auto",
  mcps: [
    {
      name: "filesystem",
      type: "local",
      command: "npx",
      args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"],
    },
    {
      name: "my-api",
      type: "remote",
      url: "https://mcp.example.com/sse",
    },
  ],
});

Custom commands

Register slash commands the agent can use:

const agent = new Agent("open-code", {
  sandbox,
  cwd: "/workspace",
  approvalMode: "auto",
  commands: [
    {
      name: "triage",
      description: "Triage a bug report into root cause + fix plan",
      template:
        "Analyze the bug report. Return: root cause, files to change, and tests to add.",
    },
  ],
});

Multimodal input

Pass images and files alongside text:

import { pathToFileURL } from "node:url";

const result = await agent.run({
  model: "sonnet",
  input: [
    { type: "text", text: "Describe this mockup and suggest improvements." },
    { type: "image", image: pathToFileURL("/workspace/mockup.png") },
  ],
});

Provider support: opencode (text, images, files), claude-code (text, images, PDFs), codex (text, images).

Custom sandbox images

Define your own image when the built-in presets don't cover your needs.

Create my-image.mjs:

export default {
  name: "playwright-sandbox",
  base: "node:20-bookworm",
  env: { PLAYWRIGHT_BROWSERS_PATH: "/ms-playwright" },
  run: [
    "apt-get update && apt-get install -y git python3 ca-certificates",
    "npm install -g pnpm @anthropic-ai/claude-code",
    "npx playwright install --with-deps chromium",
  ],
  workdir: "/workspace",
  cmd: ["sleep", "infinity"],
};

Build it:

npx agentbox image build --provider local-docker --file ./my-image.mjs

This works with all providers. For cloud providers, the printed value will be that provider's native image reference.

Hooks

Hooks let you run code at specific points in the agent lifecycle. Each provider has its own hook format:

Claude Code — native hook settings:

new Agent("claude-code", {
  sandbox,
  cwd: "/workspace",
  provider: {
    hooks: {
      PostToolUse: [
        { matcher: "Bash", hooks: [{ type: "command", command: "echo done" }] },
      ],
    },
  },
});

Codex — similar to Claude Code:

new Agent("codex", {
  sandbox,
  cwd: "/workspace",
  provider: {
    hooks: {
      PostToolUse: [
        { matcher: "Bash", hooks: [{ type: "command", command: "echo done" }] },
      ],
    },
  },
});

OpenCode — plugin-based hooks:

new Agent("open-code", {
  sandbox,
  cwd: "/workspace",
  provider: {
    plugins: [
      {
        name: "session-notifier",
        hooks: [{ event: "session.idle", body: 'return "session-idle";' }],
      },
    ],
  },
});

Examples

The examples/ directory has short, runnable scripts that each demonstrate one feature:

| Example | What it shows | | --------------------------------------------------------------- | ----------------------------- | | basic.ts | Minimal agent + sandbox | | streaming.ts | Stream and handle events | | interactive-approval.ts | Approve tool calls from stdin | | skills.ts | Attach a GitHub skill | | sub-agents.ts | Delegate to sub-agents | | mcp-server.ts | Connect an MCP server | | multimodal.ts | Send images to the agent | | custom-image.ts | Build a custom sandbox image | | cloud-sandbox.ts | Use E2B, Modal, or Daytona | | basic-vercel.ts | Use a Vercel sandbox | | git-clone.ts | Clone a repo into the sandbox |

All examples import from "agentbox-sdk" like a normal dependency. Run them with:

npx tsx examples/basic.ts

Package exports

import { Agent, Sandbox } from "agentbox-sdk"; // main entrypoint
import type { AgentRun } from "agentbox-sdk/agents"; // agent types
import type { CommandResult } from "agentbox-sdk/sandboxes"; // sandbox types
import type { NormalizedAgentEvent } from "agentbox-sdk/events"; // event types

Contributing

npm install
npm run build
npm run typecheck
npm test

npm run build generates the dist/ directory. You need to build before the examples or CLI work locally.

To test your local build from another project:

npm run build && npm pack
# then in your project:
npm install /path/to/agentbox-sdk-0.1.0.tgz

Tests

npm test                                              # fast, no real providers
AGENTBOX_RUN_SMOKE_TESTS=1 npm run test:smoke         # live smoke tests
AGENTBOX_RUN_MATRIX_E2E=1 npm run test:e2e:matrix     # provider matrix

Live test suites are opt-in because they provision real infrastructure.

Host execution settings

Use configuration: "native" to run a host harness with its own configuration, built-in prompt, credentials, and repository instructions. AgentBox does not generate settings, skills, commands, subagents, hooks, plugins, or MCP definitions in this mode. It cannot be combined with a sandbox or AgentBox-managed skills, MCPs, commands, subagents, or RTK. Omit systemPrompt when starting a turn to keep the harness's built-in instructions unchanged.

const agent = new Agent("codex", {
  cwd: "/absolute/path/to/project",
  configuration: "native",
  approvalMode: "interactive",
});
await agent.setup();

Start a turn with agent.stream({ input }), consume its async event iterator, and answer permission requests with run.respondToPermission(). Await run.finished for the result and call agent.killServer() to release the runtime.

The default configuration: "managed" retains AgentBox-generated configuration for host and sandbox execution.

stateDirectory selects a private, persistent directory for generated agent configuration and session state on the host. It must be an absolute path and cannot be combined with sandbox. Use a different directory for each execution environment to prevent unrelated local jobs from overwriting configuration. This setting does not copy credentials from the user's account.

With native configuration, Codex uses the user's sandbox and approval settings. Managed host execution defaults to read-only. An explicit provider: { sandboxMode: "workspace-write" } enables a host write policy; writableRoots adds allowed directories and networkAccess enables network access for that policy (disabled by default). The shared approvalMode: "interactive" routes permission requests to the caller; it does not itself change the native harness's policy. Cloud sandbox defaults are unchanged.

Host Claude runs the Anthropic SDK with the SDK-matched CLI by default. provider.binary explicitly selects another compatible CLI. Sign-in and session storage remain CLI-owned. Managed configuration loads generated skills, commands, and subagents as a private local plugin. Native configuration loads user, project, and local settings instead. Neither mode copies the CLI's credentials. When backgroundTaskTimeoutMs expires, host Claude stops the leftover background tasks through the SDK; in a sandbox the daemon lets the CLI wind down on disconnect, bounded by CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=30000, which AgentBox sets in the CLI environment unless env already defines it.

Each host OpenCode Agent owns an authenticated loopback server on an ephemeral port. Managed configuration uses an isolated configuration directory; native configuration preserves the user's configuration paths. killServer() stops only that Agent's process. It never discovers or kills another server by port number.

All three providers normalize interactive questions into permission.requested events with kind: "question". Respond with decision: "allow" and an answers array containing each questionId and its selected or custom values, or use decision: "deny" to skip. Invalid answers leave the request pending for correction. Question answers cannot modify unrelated tool arguments. Ordinary tool requests use the same API without answers. Denying a Codex command or file change sends decline, so the agent can continue without that action. Stopping the whole run is a separate operation (run.abort()).

Codex can also ask without pausing (request_user_input_async): the app-server accepts the call itself and the turn keeps working. That ask is not a permission request; it arrives as a message.completed event carrying questions (same AgentUserQuestion shape, bare-string options become labels) next to the text Codex rendered for it. Nothing is pending, so there is nothing to respond to: relay the user's choice as a follow-up message (run.sendMessage() while the run is live, otherwise the next run of the session). Claude Code and OpenCode have no non-blocking ask; their questions always pause as permission.requested.

Native runtimes own a POSIX process group by default. Termination is bounded and escalates to SIGKILL if the process ignores SIGTERM. A supervisor that launches each run in its own process group can set processGroup: "inherited"; that supervisor is then responsible for stopping the complete group before reporting that a run has stopped. This option is unavailable for cloud sandboxes.

Packaging

npm pack builds the package from maintained TypeScript source before creating the archive. Host execution includes the pinned Anthropic SDK and its Zod peer as runtime dependencies; consumers do not need package-manager extensions. Run npm run check before publishing. Provider integration tests use fake local CLIs and SDK mocks; live tests remain opt-in.

License

MIT

For native speed selection, use provider: { serviceTier: "fast" } with Codex (or "default" for standard speed), and provider: { fastMode: true } with Claude Code. Omit these options to inherit harness settings. Availability and usage charges are enforced by the harness.

Preparing local Codex before a prompt

For an interactive host, provider: { prewarm: true } makes await agent.setup() start and initialize the next Codex app-server. It does not create a thread, send a prompt, or run tools. The next stream()/run() consumes that prepared process and stops it normally when the run ends; abort still stops the owned process. A prepared process that exited while idle is replaced before execution. Call await agent.killServer() to dispose an unused prepared process. Prewarming is host-only and opt-in. Use a new Agent for a changed cwd, environment, or policy.