OpenCode SDK for Elixir

Copy Markdown View Source

An unofficial Elixir SDK for OpenCode that mirrors the JS SDK (@opencode-ai/sdk). The client and types are generated from the OpenCode OpenAPI spec.

hex.pm link: https://hex.pm/packages/opencode_sdk/

Installation

Add opencode_sdk to your dependencies in mix.exs:

def deps do
  [
    {:opencode_sdk, "~> 0.1.13"}
  ]
end

Quickstart

Start an OpenCode server and get a connected client:

{:ok, %{client: client, server: server}} = OpenCode.create()

{:ok, health} = OpenCode.Generated.Operations.global_health(client)
IO.inspect(health, label: "health")
# => %{"healthy" => true, "version" => "1.1.53"}

OpenCode.close(%{server: server})

Connect to an existing server:

client = OpenCode.create_client(base_url: "http://127.0.0.1:4096")
{:ok, projects} = OpenCode.Generated.Operations.project_list(client)

Create API

OpenCode.create/1 options

OpenCode.create/1 forwards to OpenCode.create_server/1 and returns %{client, server}.

OptionTypeDescriptionDefault
:hostnameString.t()Server hostname"127.0.0.1"
:portinteger()Server port4096
:timeoutinteger()Startup timeout in ms5000
:configmap()Config passed via OPENCODE_CONFIG_CONTENT%{}

OpenCode.create_client/1 options

OptionTypeDescriptionDefault
:base_urlString.t()OpenCode server URL"http://127.0.0.1:4096"
:directoryString.t()Project directory sent via x-opencode-directorynil
:headersmap() | keyword()Extra HTTP headers[]
:timeoutinteger() | :infinityRequest timeout:infinity

Configuration

Pass a :config map to override settings. The server still reads your opencode.json, but inline config takes precedence:

{:ok, %{client: client, server: server}} =
  OpenCode.create(config: %{model: "opencode/big-pickle"})

API Reference

All operations live in OpenCode.Generated.Operations. The client keyword list is always the last argument.

Global

FunctionDescriptionResponse
global_health(client)Check server health and version%{"healthy" => true, "version" => "..."}
global_dispose(client)Shut down the serverboolean
global_config_get(client)Get global configConfig
global_config_update(body, client)Update global configConfig
{:ok, health} = Operations.global_health(client)
IO.puts(health["version"])

Sessions

FunctionDescriptionResponse
session_create(body, client)Create a new sessionSession
session_list(client)List all sessions[Session]
Session.session_get(id, client)Get a session by IDSession
Session.session_children(id, client)List child sessions[Session]
session_delete(id, client)Delete a sessionboolean
session_update(id, body, client)Update session propertiesSession
session_abort(id, client)Abort a running sessionboolean
session_share(id, client)Share a sessionSession
session_unshare(id, client)Unshare a sessionSession
session_summarize(id, body, client)Summarize a sessionboolean
# Create a session
{:ok, session} = Operations.session_create(%{title: "My session"}, client)

# List recent sessions (with optional filters)
{:ok, sessions} = Operations.session_list(Keyword.merge(client, limit: 10, search: "my"))

# Get a specific session
{:ok, session} = OpenCode.Generated.Session.session_get("session-id", client)

# Delete a session
{:ok, true} = Operations.session_delete("session-id", client)

Messages and prompts

FunctionDescriptionResponse
session_prompt(id, body, client)Send a prompt, get AI response%{"info" => AssistantMessage, "parts" => [Part]}
session_prompt_async(id, body, client)Send a prompt asynchronously:ok
session_messages(id, client)List messages in a session[%{"info" => Message, "parts" => [Part]}]
session_message(id, msg_id, client)Get a specific message%{"info" => Message, "parts" => [Part]}
session_command(id, body, client)Send a command to a session%{"info" => AssistantMessage, "parts" => [Part]}
session_shell(id, body, client)Run a shell commandAssistantMessage
session_diff(id, client)Get file diffs from a session[FileDiff]
session_revert(id, body, client)Revert a messageSession
session_unrevert(id, client)Restore reverted messagesSession
# Send a prompt
{:ok, result} =
  Operations.session_prompt(
    session["id"],
    %{parts: [%{type: "text", text: "Summarize this project in 3 bullets."}]},
    client
  )

# Extract text from the response
for %{"type" => "text", "text" => text} <- result["parts"] do
  IO.puts(text)
end

# Access token usage from the response info
info = result["info"]
IO.inspect(info["tokens"])
# => %{"input" => 1234, "output" => 567, ...}

# Specify a model in the prompt
{:ok, result} =
  Operations.session_prompt(
    session["id"],
    %{
      model: %{providerID: "opencode", modelID: "big-pickle"},
      parts: [%{type: "text", text: "Hello!"}]
    },
    client
  )

# Inject context without triggering AI response
{:ok, _} =
  Operations.session_prompt(
    session["id"],
    %{
      noReply: true,
      parts: [%{type: "text", text: "You are a helpful assistant."}]
    },
    client
  )

Response structure

session_prompt/3 returns {:ok, %{"info" => info, "parts" => parts}}:

  • info — assistant message metadata: "id", "role", "model_id", "provider_id", "cost", "tokens", "time".
  • parts — list of part maps, each with a "type" field:
Part typeKey fieldsDescription
"text""text"The assistant's text response
"tool-invocation""name", "args", "state", "result"A tool call and its result
"reasoning""text"Model reasoning/thinking
"step-start"Start of a multi-step sequence
"step-finish"End of a multi-step sequence
"file""filename", "url", "mime", "source"File attachment
"patch""files", "hash"File diff/patch

App

FunctionDescriptionResponse
app_agents(client)List available agents[Agent]
app_log(body, client)Write a log entryboolean
app_skills(client)List available skills[Skill]
{:ok, agents} = Operations.app_agents(client)

Operations.app_log(
  %{service: "my-app", level: "info", message: "Operation completed"},
  client
)
FunctionDescriptionResponse
file_list(client)List files in a path[FileNode]
file_read(client)Read file contentFileContent
file_status(client)Get git status of files[File]
find_files(client)Search files by name/pattern[String]
find_text(client)Search text with ripgrep[Match]
find_symbols(client)Search workspace symbols (LSP)[Symbol]
path_get(client)Get current path infoPath
# Search for text across the project
{:ok, results} = Operations.find_text(Keyword.merge(client, pattern: "defmodule"))

# Find files by pattern
{:ok, files} = Operations.find_files(Keyword.merge(client, query: "*.ex", type: "file"))

# Read a specific file
{:ok, content} = Operations.file_read(Keyword.merge(client, path: "lib/my_app.ex"))

# Get git status
{:ok, status} = Operations.file_status(client)

Config and providers

FunctionDescriptionResponse
config_get(client)Get configConfig
config_update(body, client)Update configConfig
config_providers(client)List providers and default models%{"providers" => [...], "default" => %{...}}
provider_list(client)List providers[Provider]
provider_auth(client)Get provider auth status
{:ok, config} = Operations.config_get(client)
{:ok, %{"providers" => providers, "default" => defaults}} = Operations.config_providers(client)

Auth

FunctionDescriptionResponse
auth_set(providerID, body, client)Set auth credentialsboolean
auth_remove(providerID, client)Remove auth credentialsboolean
Operations.auth_set("anthropic", %{type: "api", key: "sk-..."}, client)

Events (SSE)

FunctionDescriptionResponse
event_subscribe(client)Subscribe to real-time events%{stream: Stream}
global_event(client)Subscribe to global events%{stream: Stream}
{:ok, %{stream: stream}} = Operations.event_subscribe(client)

Enum.each(stream, fn event ->
  IO.inspect(event, label: "event")
end)

Permissions and questions

FunctionDescriptionResponse
permission_list(client)List pending permissions[Permission]
permission_reply(id, body, client)Reply to a permission requestboolean
question_list(client)List pending questions[Question]
question_reply(id, body, client)Reply to a questionboolean
question_reject(id, client)Reject a questionboolean

MCP

FunctionDescriptionResponse
mcp_status(client)Get MCP server statusMcpStatus
mcp_add(body, client)Add an MCP server
mcp_connect(name, client)Connect to an MCP server
mcp_disconnect(name, client)Disconnect from an MCP server
{:ok, mcp} = Operations.mcp_status(client)

PTY

FunctionDescriptionResponse
pty_list(client)List PTY sessions[Pty]
pty_create(body, client)Create a PTY sessionPty
pty_get(id, client)Get a PTY sessionPty
pty_remove(id, client)Remove a PTY sessionboolean

TUI

FunctionDescriptionResponse
tui_append_prompt(body, client)Append text to the promptboolean
tui_submit_prompt(client)Submit the current promptboolean
tui_clear_prompt(client)Clear the promptboolean
tui_execute_command(body, client)Execute a commandboolean
tui_show_toast(body, client)Show a toast notificationboolean
tui_open_help(client)Open help dialogboolean
tui_open_sessions(client)Open session selectorboolean
tui_open_models(client)Open model selectorboolean
tui_open_themes(client)Open theme selectorboolean
Operations.tui_append_prompt(%{text: "Add this to prompt"}, client)
Operations.tui_show_toast(%{message: "Done!", variant: "success"}, client)

TUI process lifecycle

{:ok, tui} = OpenCode.create_tui(project: "/path/to/project")
OpenCode.Tui.close(tui)

Error handling

All operations return {:ok, result} on success. Failures return {:error, {status, body}} for HTTP errors or {:error, reason} for connection issues:

case Operations.session_prompt(session_id, body, client) do
  {:ok, result} ->
    result

  {:error, {404, _body}} ->
    IO.puts("Session not found")

  {:error, {400, body}} ->
    IO.puts("Bad request: #{inspect(body)}")

  {:error, %Req.TransportError{reason: :econnrefused}} ->
    IO.puts("Cannot connect to server")
end

Examples

See the examples/ directory:

  • hello.exs — minimal example: start server, create session, send one prompt, print response.
  • chat.exs — interactive CLI chat REPL with session management, slash commands, and token usage display.

Run an example:

mix run examples/hello.exs
mix run examples/chat.exs

Types and Docs

All OpenAPI types are generated under OpenCode.Generated.* (for example, OpenCode.Generated.Session).

API functions and types are documented in generated module docs, primarily:

Regenerating

The OpenAPI spec is at priv/opencode_openapi.json. Regenerate the client with:

mix opencode.gen.client --spec priv/opencode_openapi.json

Note

This project is unofficial and is not affiliated with the OpenCode team.