An Elixir client for the OpenCode HTTP daemon.
Talks to a running opencode serve process over its HTTP + Server-Sent-Events
API. Handles session lifecycle, streaming prompts, tool events, and usage
accounting. Supports both v1 and v2 API shapes (auto-detected).
⚠️ Status: 0.1.0-alpha. Public API may break before 0.2. Tested against OpenCode 1.18 (v1) and thesst/v2branch (v2). See the compatibility notes at the bottom.
OpenCode is a provider-agnostic AI coding agent. It exposes an HTTP API that wraps ~15 model providers (Kimi, GLM, DeepSeek, Grok, Qwen, plus direct Anthropic/OpenAI/Google), handles MCP tools, code-mode execution, permission gating, and streaming — all in one daemon. Wrapping it from Elixir gives you those N providers for the cost of one client library.
Not yet on hex.pm. Path or git dep for now:
def deps do
[
# From a local checkout:
{:opencode_ex, path: "../opencode_ex"},
# Or from GitHub:
# {:opencode_ex, github: "paperview/opencode_ex", tag: "0.1.0-alpha"}
]
end# 1. Boot the daemon separately (or via a launcher script):
# OPENCODE_SERVER_PASSWORD=dev opencode serve --port 4096
# 2. Configure via app env (or pass keywords per-call):
config :opencode_ex,
base_url: "http://127.0.0.1:4096",
password: {:system, "OPENCODE_SERVER_PASSWORD"}
# 3. Use it:
client = OpencodeEx.Client.new(directory: File.cwd!())
{:ok, session} = OpencodeEx.Session.create(client)
{:ok, result} =
OpencodeEx.Session.prompt(client, session["id"], "Say hi in 3 words.",
model: {"opencode", "hy3-free"},
on_event: fn evt ->
case evt.type do
"session.text.delta" -> IO.write(evt.data["delta"])
_ -> :ok
end
end
)
IO.inspect(result.text) # => "Hi there, friend!"
IO.inspect(result.usage) # => %{input_tokens: 3159, output_tokens: 4, ...}
:ok = OpencodeEx.Session.delete(client, session["id"])Session.prompt/4 returns {:ok, %{text, usage, cost, finish_reason, tool_calls, events}}:
text— full assembled assistant responseusage— normalized:%{input_tokens, output_tokens, reasoning_tokens, cache_read_tokens, cache_creation_tokens}(sums across steps in multi-step turns)cost— USD, summed across stepsfinish_reason—"stop","tool-calls", etc.tool_calls— list ofsession.tool.calledevent dataevents— full ordered list of%OpencodeEx.Events.Event{}structs for post-hoc inspection
Every failing path returns {:error, %OpencodeEx.Error{}} with a discriminator:
:http— daemon returned non-2xx (hasstatus,body,endpoint):transport— request never reached the daemon (hascause):step_failed— daemon emitted a mid-turn failure (has failed-step payload):incomplete— SSE stream closed before terminal event (has partial state):timeout— hit a specific timeout
App env keys (all overrideable via Client.new/1 keyword args):
| Key | Type | Default | Notes |
|---|---|---|---|
:base_url |
string | required | e.g. "http://127.0.0.1:4096" |
:password |
string | {:system, "VAR"} |
required | HTTP Basic; user is always opencode |
:directory |
string | nil | Working directory for sessions (x-opencode-directory header) |
:receive_timeout |
integer | 30_000 | Per-request HTTP timeout in ms |
The {:system, "VAR"} tuple defers env lookup to runtime.
OpenCode is mid-rewrite. Two API shapes are supported:
v1 (stable, npm/Homebrew ship this) — session.next.text.delta event
names, {prompt: {text: "..."}} prompt body. Each turn is a single step;
halt on first step.ended.
v2 (in-development on sst/v2 branch) — session.text.delta event names
(no next. infix), flattened {text: "..."} prompt body. Turns can have
multiple steps wrapped in execution.started / execution.succeeded.
The client auto-detects which version by watching for execution.started.
No caller-visible API difference.
mix deps.get
mix compile
mix test # unit tests
# Manual smoke tests (require a running daemon):
mix run priv/smoke.exs # basic text prompt
mix run priv/smoke_codemode.exs # code-mode + MCP tool call- Retry/backoff on transient failures
Telemetryevents for observabilityquestion/permissionreply APIs (interactive-mode support)- Session history read + compaction/summarize helpers
- GenServer wrapper for long-lived session reuse
- More test coverage — v1 event decoding, error paths
MIT © 2026 Phil Pape.
Not affiliated with OpenCode / SST.