Start typing to search the documentation.

Build navigation

Effect

@opencode/client/effect is the Effect-native client for the OpenCode HTTP API. It returns typed Effects and Streams and decodes responses into OpenCode schema values.

bun add @opencode/client effect

Create a client

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

import { AbsolutePath, Location, OpenCode } from "@opencode/client/effect"
import { Effect } from "effect"
import { FetchHttpClient } from "effect/unstable/http"

const program = Effect.gen(function* () {
  const client = yield* OpenCode.make({ baseUrl: "http://localhost:4096" })
  const session = yield* client.session.create({
    location: Location.Ref.make({ directory: AbsolutePath.make("/workspace") }),
  })
  yield* client.session.prompt({
    sessionID: session.id,
    text: "Review the current changes",
  })
  return session
})

const session = await Effect.runPromise(program.pipe(Effect.provide(FetchHttpClient.layer)))

Headers and requests

Configure default headers on the supplied HttpClient. Native operations use normal Effect interruption for cancellation. RPC methods additionally accept per-call location, header, and signal options.

import { HttpClient, HttpClientRequest } from "effect/unstable/http"

const httpClient = yield * HttpClient.HttpClient
const client =
  yield *
  OpenCode.make({ baseUrl: "https://opencode.example.com" }).pipe(
    Effect.provideService(
      HttpClient.HttpClient,
      HttpClient.mapRequest(httpClient, (request) =>
        HttpClientRequest.setHeaders(request, { authorization: `Bearer ${process.env.OPENCODE_TOKEN}` }),
      ),
    ),
  )

const sessions = yield * client.session.list()

Stream events

Streaming operations such as event.subscribe() and session.log() return Effect Streams.

import { Effect, Stream } from "effect"

yield *
  client.event.subscribe().pipe(Stream.runForEach((event) => Effect.logInfo("OpenCode event", { type: event.type })))

Native and RPC event Streams share one lazy connection per client. Constructing a Stream does not connect; consuming it does. Stopping one consumer leaves others running, and the last consumer leaving closes the source. Source EOF or failure ends current subscriptions without automatic retry or replay. Late native consumers receive the current connection marker before live events.

Plugin RPC

Use the same shared contract as Promise clients and server plugins:

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

const acme = client.rpc(Acme)
const result = yield * acme.search({ query: "hello" }, { location: { directory: "/workspace" } })

yield *
  acme.events
    .subscribe("updated")
    .pipe(
      Stream.runForEach((event) =>
        Effect.logInfo("Plugin event", { type: event.type, location: event.location, text: event.data.text }),
      ),
    )

Method arguments and results are inferred from the contract. The second optional argument holds location, signal, and headers; omitted location uses the normal request defaults. Calls are interrupted with their consuming Effect. Method error maps are inferred in the Effect error channel. Declared errors are decoded through their data schemas. The typed subclient removes the generic HTTP RPC error wrapper; reserved rpc.* types identify framework failures.

RPC events are typed Streams, not callback-style on listeners. They receive the RPC’s events from all locations, each with required location and a normal prefixed type such as rpc.acme.updated. This differs from server-plugin handles, which are fixed to their own location. See Effect plugin RPC for definitions, schemas, registration, and live subscription semantics.

Local background service

The Node-only @opencode/client/effect/service entrypoint discovers, starts, authenticates, and stops the local background service as Effects.

bun add @effect/platform-node

Create an authenticated client for the ensured service.

import { NodeFileSystem } from "@effect/platform-node"
import { OpenCode } from "@opencode/client/effect"
import { Service } from "@opencode/client/effect/service"
import { Effect } from "effect"
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"

const program = Effect.gen(function* () {
  const endpoint = yield* Service.ensure()
  const httpClient = yield* HttpClient.HttpClient
  const client = yield* OpenCode.make({ baseUrl: endpoint.url }).pipe(
    Effect.provideService(
      HttpClient.HttpClient,
      HttpClient.mapRequest(httpClient, (request) => HttpClientRequest.setHeaders(request, Service.headers(endpoint))),
    ),
  )
  return yield* client.server.info()
})

const health = await Effect.runPromise(
  program.pipe(Effect.provide(FetchHttpClient.layer), Effect.provide(NodeFileSystem.layer)),
)

Discover without starting, or stop the exact registered service.

const endpoint = yield * Service.discover()
yield * Service.stop()