Skip to content
Build docs

Effect

Contents

@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.

Terminal
bun add @opencode/client effect@4.0.1

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

TypeScript
import { AbsolutePath, Location, OpenCode } from "@opencode/client/effect"
import { Effect } from "effect"
import { FetchHttpClient } from "effect/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)))

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.

TypeScript
import { HttpClient, HttpClientRequest } from "effect/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()

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

TypeScript
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.

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

TypeScript
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.

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

Terminal
bun add @effect/platform-node@4.0.1

Create an authenticated client for the ensured service.

TypeScript
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/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.

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