Start typing to search the documentation.

Build navigation

JavaScript

@opencode/client is the TypeScript client for the OpenCode HTTP API. Use it when your application connects to an OpenCode server over the network. Its native types and methods are generated from the same contract as the API reference. Plugin RPC types come from imported RPC definitions.

Install

bun add @opencode/client@beta

Create a client

Create a client with the server URL, then call methods grouped by API resource:

import { OpenCode } from "@opencode/client"

const client = OpenCode.make({
  baseUrl: "http://localhost:4096",
})

const session = await client.session.create({
  location: { directory: "/workspace" },
})

await client.session.prompt({
  sessionID: session.id,
  text: "Review the current changes",
})

Headers and requests

Pass default authentication or application headers to OpenCode.make with headers. You can also supply a custom fetch implementation. Each operation accepts request options as its final argument for an AbortSignal or per-request headers. Event subscriptions are the exception: they accept only subscriber-local cancellation and use the base client’s headers.

const client = OpenCode.make({
  baseUrl: "https://opencode.example.com",
  headers: {
    authorization: `Bearer ${process.env.OPENCODE_TOKEN}`,
  },
})

await client.session.list(undefined, {
  signal: AbortSignal.timeout(10_000),
})

Stream events

Streaming endpoints return async iterables:

for await (const event of client.event.subscribe()) {
  console.log(event.type)
}

Native and RPC event subscribers share one lazy connection per client. Client, handle, and iterable creation open no event connection; consumption starts it. Breaking iteration or aborting a subscriber ends only that iterator. The last subscriber leaving closes the connection. The shared source waits for active subscribers to accept each event; consumers should buffer before performing slow work.

Subscriptions are live-only, with no replay or automatic reconnection. A source failure ends current subscriptions; subscribe again after recovery. A late native subscriber receives the current server.connected marker, not past business events.

Plugin RPC

Import a plugin’s shared contract and pass it to client.rpc:

import { OpenCode } from "@opencode/client"
import { Acme } from "opencode-acme-plugin/rpc"

const acme = client.rpc(Acme)
const result = await acme.search(
  { query: "hello" },
  { location: { directory: "/workspace" }, signal: AbortSignal.timeout(10_000) },
)

const unsubscribe = acme.events.on("updated", (event) => {
  console.log(event.type, event.location.directory, event.data.text)
})

The second optional method argument holds location, signal, and headers, separate from the plugin-defined input. Omitted location follows native request defaults: base location headers, then the server’s working directory. No location is selected when constructing the subclient.

Methods infer arguments and results from the definition. Schema parsing belongs to the contract boundary; callers send the accepted input representation, while handlers receive parsed values. The Promise client accepts Standard Schema or plain JSON Schema definitions and does not run schema parsers locally: the server returns already parsed and transformed output. Effect Schema definitions require the Effect client.

Declared method errors reject like errors from other Promise client endpoints, and caught errors remain untyped. Generic HTTP RPC error wrappers are removed by the typed subclient. Reserved rpc.* framework failures remain plain RPC failures, while unrelated authentication, transport, and protocol errors keep their normal client representations.

RPC subscriptions use local names and receive that RPC’s events across all locations. Inspect the required event.location to filter them. events.subscribe matches the native async iterable API:

for await (const event of acme.events.subscribe("updated")) {
  console.log(event.data.text)
}

events.on is a convenience wrapper over the same source. It returns unsubscribe; async callbacks are awaited sequentially. Callback or source failures are logged and end that listener. Native and typed subscriptions receive the same normal rpc.<rpcID>.<event> envelope with direct object event data. Live subscriptions do not replay missed events.

The server plugin must be configured and implement the RPC; importing a definition does not register it. See plugin RPC for the definition and registration API. Any HTTP client can also invoke the generic POST /api/rpc/{rpcID}/{method} route with { "input": ... } and receive { "output": ... }. Omitted input/output fields represent no value.

Local background service

The main client entrypoints are browser-compatible and do not include local process management. In a Node application, import the native Promise service API from @opencode/client/service.

  • Service.discover() returns a healthy registered endpoint without starting a process.
  • Service.ensure() returns a compatible service, starting one when needed.
  • Service.stop() stops the exact registered service instance.
  • Service.headers(endpoint) creates the authentication headers for a client.
import { OpenCode } from "@opencode/client"
import { Service } from "@opencode/client/service"

const endpoint = await Service.ensure()
const client = OpenCode.make({
  baseUrl: endpoint.url,
  headers: Service.headers(endpoint),
})

const health = await client.health.get()

Service.ensure() accepts an optional registration file, version, service command, and onStart callback. version accepts either an exact value or a compatibility predicate:

const endpoint = await Service.ensure({
  file: "/var/run/opencode/service.json",
  version: (version) => version.startsWith("2."),
  command: ["opencode", "serve", "--service"],
  onStart(reason, previousVersion) {
    console.log(reason, previousVersion)
  },
})

Omit these options to use the standard registration path and opencode serve --service command.