# 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](/api). Plugin RPC types come from imported RPC definitions.

## Install

```sh
bun add @opencode/client
```

## Create a client

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

```ts
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.

```ts
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:

```ts
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.

Pass `types` to receive only some event types. The events are typed to match, and
`server.connected` always comes through so a filtered subscriber still knows when to resync.
Filtering happens on the shared connection, inside the client.

```ts
for await (const event of client.event.subscribe({ types: ["config.updated"] })) {
  // event.type is "config.updated" | "server.connected"
}
```

## Plugin RPC

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

```ts
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:

```ts
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](/build/plugins/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.

```ts
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 info = await client.server.info()
```

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

```ts
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.