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/
Add opencode_sdk to your dependencies in mix.exs:
def deps do
[
{:opencode_sdk, "~> 0.1.8"}
]
endStart 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)OpenCode.create/1 forwards to OpenCode.create_server/1 and returns %{client, server}.
| Option | Type | Description | Default |
|---|---|---|---|
:hostname |
String.t() |
Server hostname | "127.0.0.1" |
:port |
integer() |
Server port | 4096 |
:timeout |
integer() |
Startup timeout in ms | 5000 |
:config |
map() |
Config passed via OPENCODE_CONFIG_CONTENT |
%{} |
| Option | Type | Description | Default |
|---|---|---|---|
:base_url |
String.t() |
OpenCode server URL | "http://127.0.0.1:4096" |
:directory |
String.t() |
Project directory sent via x-opencode-directory |
nil |
:headers |
map() | keyword() |
Extra HTTP headers | [] |
:timeout |
integer() | :infinity |
Request timeout | :infinity |
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"})All operations live in OpenCode.Generated.Operations. The client keyword list is always the last argument.
| Function | Description | Response |
|---|---|---|
global_health(client) |
Check server health and version | %{"healthy" => true, "version" => "..."} |
global_dispose(client) |
Shut down the server | boolean |
global_config_get(client) |
Get global config | Config |
global_config_update(body, client) |
Update global config | Config |
{:ok, health} = Operations.global_health(client)
IO.puts(health["version"])| Function | Description | Response |
|---|---|---|
session_create(body, client) |
Create a new session | Session |
session_list(client) |
List all sessions | [Session] |
Session.session_get(id, client) |
Get a session by ID | Session |
Session.session_children(id, client) |
List child sessions | [Session] |
session_delete(id, client) |
Delete a session | boolean |
session_update(id, body, client) |
Update session properties | Session |
session_abort(id, client) |
Abort a running session | boolean |
session_share(id, client) |
Share a session | Session |
session_unshare(id, client) |
Unshare a session | Session |
session_summarize(id, body, client) |
Summarize a session | boolean |
# 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)| Function | Description | Response |
|---|---|---|
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 command | AssistantMessage |
session_diff(id, client) |
Get file diffs from a session | [FileDiff] |
session_revert(id, body, client) |
Revert a message | Session |
session_unrevert(id, client) |
Restore reverted messages | Session |
# 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
)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 type | Key fields | Description |
|---|---|---|
"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 |
| Function | Description | Response |
|---|---|---|
app_agents(client) |
List available agents | [Agent] |
app_log(body, client) |
Write a log entry | boolean |
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
)| Function | Description | Response |
|---|---|---|
file_list(client) |
List files in a path | [FileNode] |
file_read(client) |
Read file content | FileContent |
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 info | Path |
# 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)| Function | Description | Response |
|---|---|---|
config_get(client) |
Get config | Config |
config_update(body, client) |
Update config | Config |
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)| Function | Description | Response |
|---|---|---|
auth_set(providerID, body, client) |
Set auth credentials | boolean |
auth_remove(providerID, client) |
Remove auth credentials | boolean |
Operations.auth_set("anthropic", %{type: "api", key: "sk-..."}, client)| Function | Description | Response |
|---|---|---|
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)| Function | Description | Response |
|---|---|---|
permission_list(client) |
List pending permissions | [Permission] |
permission_reply(id, body, client) |
Reply to a permission request | boolean |
question_list(client) |
List pending questions | [Question] |
question_reply(id, body, client) |
Reply to a question | boolean |
question_reject(id, client) |
Reject a question | boolean |
| Function | Description | Response |
|---|---|---|
mcp_status(client) |
Get MCP server status | McpStatus |
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)| Function | Description | Response |
|---|---|---|
pty_list(client) |
List PTY sessions | [Pty] |
pty_create(body, client) |
Create a PTY session | Pty |
pty_get(id, client) |
Get a PTY session | Pty |
pty_remove(id, client) |
Remove a PTY session | boolean |
| Function | Description | Response |
|---|---|---|
tui_append_prompt(body, client) |
Append text to the prompt | boolean |
tui_submit_prompt(client) |
Submit the current prompt | boolean |
tui_clear_prompt(client) |
Clear the prompt | boolean |
tui_execute_command(body, client) |
Execute a command | boolean |
tui_show_toast(body, client) |
Show a toast notification | boolean |
tui_open_help(client) |
Open help dialog | boolean |
tui_open_sessions(client) |
Open session selector | boolean |
tui_open_models(client) |
Open model selector | boolean |
tui_open_themes(client) |
Open theme selector | boolean |
Operations.tui_append_prompt(%{text: "Add this to prompt"}, client)
Operations.tui_show_toast(%{message: "Done!", variant: "success"}, client){:ok, tui} = OpenCode.create_tui(project: "/path/to/project")
OpenCode.Tui.close(tui)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")
endSee 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.exsAll OpenAPI types are generated under OpenCode.Generated.* (for example, OpenCode.Generated.Session).
API functions and types are documented in generated module docs, primarily:
OpenCodeOpenCode.Generated.OperationsOpenCode.Generated.*type modules
The OpenAPI spec is at priv/opencode_openapi.json.
Regenerate the client with:
mix opencode.gen.client --spec priv/opencode_openapi.jsonThis project is unofficial and is not affiliated with the OpenCode team.