Skip to content

paperview/opencode_ex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

opencode_ex

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 the sst/v2 branch (v2). See the compatibility notes at the bottom.

Why

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.

Installation

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

Quickstart

# 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"])

The result shape

Session.prompt/4 returns {:ok, %{text, usage, cost, finish_reason, tool_calls, events}}:

  • text — full assembled assistant response
  • usage — normalized: %{input_tokens, output_tokens, reasoning_tokens, cache_read_tokens, cache_creation_tokens} (sums across steps in multi-step turns)
  • cost — USD, summed across steps
  • finish_reason"stop", "tool-calls", etc.
  • tool_calls — list of session.tool.called event data
  • events — full ordered list of %OpencodeEx.Events.Event{} structs for post-hoc inspection

Errors

Every failing path returns {:error, %OpencodeEx.Error{}} with a discriminator:

  • :http — daemon returned non-2xx (has status, body, endpoint)
  • :transport — request never reached the daemon (has cause)
  • :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

Configuration

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.

Compatibility

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.

Development

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

What's missing (roadmap to 0.2)

  • Retry/backoff on transient failures
  • Telemetry events for observability
  • question / permission reply 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

License

MIT © 2026 Phil Pape.

Not affiliated with OpenCode / SST.

About

Elixir client for the OpenCode HTTP daemon — HTTP + SSE, session lifecycle, streaming prompts, tool events, usage accounting.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages