OpencodeEx (beam_agent_ex v0.1.0)

Copy Markdown View Source

Elixir wrapper for the OpenCode HTTP agent SDK.

Provides idiomatic Elixir access to the OpenCode HTTP REST + SSE transport. OpenCode exposes a richer API surface than port-based adapters, including session management, permission handling, and server health checks.

Quick Start

{:ok, session} = OpencodeEx.start_session(directory: "/my/project")
{:ok, messages} = OpencodeEx.query(session, "What does this code do?")
OpencodeEx.stop(session)

Streaming

session
|> OpencodeEx.stream!("Explain this module")
|> Enum.each(&IO.inspect/1)

Custom Base URL

{:ok, session} = OpencodeEx.start_session(
  base_url: "http://localhost:4096",
  directory: "/my/project"
)

Permission Handling

handler = fn perm_id, metadata, _opts ->
  IO.puts("Permission requested: #{inspect(metadata)}")
  {:allow, %{}}
end

{:ok, session} = OpencodeEx.start_session(
  directory: "/my/project",
  permission_handler: handler
)

Summary

Functions

Abort the current active query.

Get account information.

List active beta features from the system init data.

Add a native OpenCode MCP server.

Get the API key source from the system init data.

Get native OpenCode app info.

Initialize native OpenCode app state.

Write a native OpenCode log entry.

List native OpenCode app modes.

Convert a single content_block into a flat message.

Supervisor child specification for an opencode_session process.

Get the CLI version from the system init data.

Run a command via universal command execution.

List configured/default native OpenCode providers.

Read native OpenCode config.

Update native OpenCode config.

Get the current model from session info.

Get the current permission mode from session info.

Delete a session by ID on the OpenCode server (native REST).

Delete a session and its messages.

Non-raising OpenCode /event stream.

Stream normalized OpenCode /event messages lazily.

Subscribe to the native OpenCode /event stream.

Unsubscribe from the native OpenCode /event stream.

Extract all TodoWrite items from a list of messages.

List files in the workspace using native OpenCode file APIs.

Read a file using native OpenCode file APIs.

Get native OpenCode file status information.

Filter todo items by status.

Find files in the current workspace using native OpenCode search.

Find workspace symbols using native OpenCode search.

Find text in the current workspace using native OpenCode search.

Flatten an assistant message (with content_blocks) into individual messages.

Fork a tracked session into a new session ID.

Get details for a specific session by ID from the OpenCode server (native REST).

Get session metadata by ID.

Get messages for a session.

Get messages with options.

Query session health.

Interrupt the current active query.

List available agents from the system init data.

List native OpenCode slash commands.

List configured MCP servers from the system init data.

List available plugins from the system init data.

List native OpenCode agents.

List all active sessions on the OpenCode server (native REST).

List all tracked sessions.

List sessions with filters.

List available skills from the system init data.

List available tools from the system init data.

Create an in-process MCP server definition.

Get status of all MCP servers.

Get native OpenCode MCP server status.

Create an in-process MCP tool definition.

Convert a single flat message into a content_block.

Convert a list of flat messages into content_block format.

Normalize a list of messages from any adapter into a uniform flat stream.

Get the output style from the system init data.

Send a prompt asynchronously using native OpenCode prompt_async.

List native OpenCode provider auth methods.

List native OpenCode providers.

Start a native OpenCode provider OAuth authorize flow.

Complete a native OpenCode provider OAuth callback flow.

Send a query and collect all response messages (blocking).

Receive the next normalized event from the native OpenCode /event stream.

Reconnect a failed MCP server.

Revert the visible session history to a prior boundary.

Revert file changes to a checkpoint via universal checkpointing.

Create an SDK lifecycle hook.

Create an SDK lifecycle hook with a matcher.

Send a command to the current session.

Send a raw control message via universal control dispatch.

Check the health of the OpenCode server.

Query session info (session id, directory, model, transport).

Initialize the current native OpenCode session.

Fetch native server-side messages for the current session.

Fetch native server-side messages for the current session with options.

Set maximum thinking tokens via universal control.

Replace MCP server configurations.

Change the model at runtime.

Change the permission mode at runtime via universal control.

Create or replace share state for the current session.

Run a native OpenCode shell command for the current session.

Start an OpenCode HTTP session.

Stop an OpenCode session.

Stop a running agent task via universal task tracking.

Returns a Stream that yields {:ok, msg} or {:error, reason} tuples.

Returns a Stream that yields messages as they arrive.

Submit feedback via universal feedback tracking.

Generate and store a summary for the current session.

List available agents.

List available slash commands.

List available models.

Fork an existing thread.

List all threads for this session.

Read thread metadata, optionally including visible messages.

Resume an existing thread.

Rollback the visible thread history.

Start a new conversation thread.

Get a summary of todo counts by status.

Enable or disable an MCP server.

Append prompt text to the native OpenCode TUI.

Open the native OpenCode help dialog.

Respond to an agent request via universal turn response.

Clear any stored session revert state.

Revoke share state for the current session.

Get the working directory from the system init data.

Functions

abort(session)

@spec abort(pid()) :: :ok | {:error, term()}

Abort the current active query.

account_info(session)

@spec account_info(pid()) :: {:ok, map()} | {:error, term()}

Get account information.

active_betas(session)

@spec active_betas(pid()) :: {:ok, list()} | {:error, term()}

List active beta features from the system init data.

add_mcp_server(session, body)

@spec add_mcp_server(pid(), map()) :: {:ok, map()} | {:error, term()}

Add a native OpenCode MCP server.

api_key_source(session)

@spec api_key_source(pid()) :: {:ok, binary() | nil} | {:error, term()}

Get the API key source from the system init data.

app_info(session)

@spec app_info(pid()) :: {:ok, map()} | {:error, term()}

Get native OpenCode app info.

app_init(session)

@spec app_init(pid()) :: {:ok, term()}

Initialize native OpenCode app state.

app_log(session, body)

@spec app_log(pid(), map()) :: {:ok, term()} | {:error, term()}

Write a native OpenCode log entry.

app_modes(session)

@spec app_modes(pid()) :: {:ok, [binary()]}

List native OpenCode app modes.

block_to_message(block)

@spec block_to_message(content_block()) :: %{
  type: :raw | :text | :thinking | :tool_result | :tool_use,
  content: term(),
  raw: term(),
  tool_input: term(),
  tool_name: term(),
  tool_use_id: term()
}

Convert a single content_block into a flat message.

child_spec(opts)

@spec child_spec(keyword() | map()) :: Supervisor.child_spec()

Supervisor child specification for an opencode_session process.

Accepts keyword list or map. Uses :session_id from opts as child id when available.

cli_version(session)

@spec cli_version(pid()) :: {:ok, binary() | nil} | {:error, term()}

Get the CLI version from the system init data.

command_run(session, command, opts \\ %{})

@spec command_run(pid(), binary(), map()) ::
  {:ok, %{exit_code: integer(), output: binary()}}
  | {:error,
     {:port_exit, term()} | {:port_failed, term()} | {:timeout, timeout()}}

Run a command via universal command execution.

config_providers(session)

@spec config_providers(pid()) :: {:ok, map()} | {:error, term()}

List configured/default native OpenCode providers.

config_read(session)

@spec config_read(pid()) :: {:ok, map()} | {:error, term()}

Read native OpenCode config.

config_update(session, body)

@spec config_update(pid(), map()) :: {:ok, map()} | {:error, term()}

Update native OpenCode config.

current_model(session)

@spec current_model(pid()) :: {:ok, binary() | nil} | {:error, term()}

Get the current model from session info.

Extracts from the session's model field or system init data.

current_permission_mode(session)

@spec current_permission_mode(pid()) ::
  {:ok, atom() | binary() | nil} | {:error, term()}

Get the current permission mode from session info.

delete_server_session(session, id)

@spec delete_server_session(pid(), binary()) :: {:ok, term()} | {:error, term()}

Delete a session by ID on the OpenCode server (native REST).

delete_session(session_id)

@spec delete_session(binary()) :: :ok

Delete a session and its messages.

event_stream(session, opts \\ %{})

@spec event_stream(pid(), keyword() | map()) :: Enumerable.t()

Non-raising OpenCode /event stream.

event_stream!(session, opts \\ %{})

@spec event_stream!(pid(), keyword() | map()) :: Enumerable.t()

Stream normalized OpenCode /event messages lazily.

event_subscribe(session)

@spec event_subscribe(pid()) :: {:ok, reference()}

Subscribe to the native OpenCode /event stream.

event_unsubscribe(session, ref)

@spec event_unsubscribe(pid(), reference()) :: :ok | {:error, term()}

Unsubscribe from the native OpenCode /event stream.

extract_todos(messages)

@spec extract_todos([message_map()]) :: [todo_item()]

Extract all TodoWrite items from a list of messages.

file_list(session, path)

@spec file_list(pid(), binary()) :: {:ok, term()} | {:error, term()}

List files in the workspace using native OpenCode file APIs.

file_read(session, path)

@spec file_read(pid(), binary()) :: {:ok, term()} | {:error, term()}

Read a file using native OpenCode file APIs.

file_status(session)

@spec file_status(pid()) :: {:ok, term()} | {:error, term()}

Get native OpenCode file status information.

filter_todos(todos, status)

Filter todo items by status.

find_files(session, opts)

@spec find_files(pid(), map()) :: {:ok, term()} | {:error, term()}

Find files in the current workspace using native OpenCode search.

find_symbols(session, query)

@spec find_symbols(pid(), binary()) :: {:ok, term()} | {:error, term()}

Find workspace symbols using native OpenCode search.

find_text(session, pattern)

@spec find_text(pid(), binary()) :: {:ok, term()} | {:error, term()}

Find text in the current workspace using native OpenCode search.

flatten_assistant(message)

@spec flatten_assistant(map()) :: [map()]

Flatten an assistant message (with content_blocks) into individual messages.

fork_session(session, opts)

@spec fork_session(pid(), map()) :: {:ok, session_info_map()} | {:error, :not_found}

Fork a tracked session into a new session ID.

get_server_session(session, id)

@spec get_server_session(pid(), binary()) :: {:ok, map()} | {:error, term()}

Get details for a specific session by ID from the OpenCode server (native REST).

get_session(session_id)

@spec get_session(binary()) :: {:ok, session_info_map()} | {:error, :not_found}

Get session metadata by ID.

get_session_messages(session_id)

@spec get_session_messages(binary()) :: {:ok, [message_map()]} | {:error, :not_found}

Get messages for a session.

get_session_messages(session_id, opts)

@spec get_session_messages(binary(), message_filter_opts()) ::
  {:ok, [message_map()]} | {:error, :not_found}

Get messages with options.

health(session)

@spec health(pid()) :: atom()

Query session health.

interrupt(session)

@spec interrupt(pid()) :: :ok | {:error, term()}

Interrupt the current active query.

Sends an interrupt signal to the session.

list_agents(session)

@spec list_agents(pid()) :: {:ok, list()} | {:error, term()}

List available agents from the system init data.

list_commands(session)

@spec list_commands(pid()) :: {:ok, map()} | {:error, term()}

List native OpenCode slash commands.

list_mcp_servers(session)

@spec list_mcp_servers(pid()) :: {:ok, list()} | {:error, term()}

List configured MCP servers from the system init data.

list_plugins(session)

@spec list_plugins(pid()) :: {:ok, list()} | {:error, term()}

List available plugins from the system init data.

list_server_agents(session)

@spec list_server_agents(pid()) :: {:ok, map()} | {:error, term()}

List native OpenCode agents.

list_server_sessions(session)

@spec list_server_sessions(pid()) :: {:ok, [map()]} | {:error, term()}

List all active sessions on the OpenCode server (native REST).

list_sessions()

@spec list_sessions() :: {:ok, [session_info_map()]}

List all tracked sessions.

list_sessions(opts)

@spec list_sessions(session_filter_opts()) :: {:ok, [session_info_map()]}

List sessions with filters.

list_skills(session)

@spec list_skills(pid()) :: {:ok, list()} | {:error, term()}

List available skills from the system init data.

list_tools(session)

@spec list_tools(pid()) :: {:ok, list()} | {:error, term()}

List available tools from the system init data.

mcp_server(name, tools)

@spec mcp_server(binary(), [
  %{
    description: binary(),
    handler: (map() -> {term(), term()}),
    input_schema: map(),
    name: binary()
  }
]) :: mcp_server_def()

Create an in-process MCP server definition.

mcp_server_status(session)

@spec mcp_server_status(pid()) :: {:ok, %{required(binary()) => map()}}

Get status of all MCP servers.

mcp_status(session)

@spec mcp_status(pid()) :: {:ok, map()} | {:error, term()}

Get native OpenCode MCP server status.

mcp_tool(name, description, input_schema, handler)

@spec mcp_tool(binary(), binary(), map(), (map() ->
                                       {:error, binary()} | {:ok, [map()]})) ::
  mcp_tool_def()

Create an in-process MCP tool definition.

message_to_block(message)

@spec message_to_block(map()) :: content_block()

Convert a single flat message into a content_block.

messages_to_blocks(messages)

@spec messages_to_blocks([map()]) :: [content_block()]

Convert a list of flat messages into content_block format.

normalize_messages(messages)

@spec normalize_messages([map()]) :: [map()]

Normalize a list of messages from any adapter into a uniform flat stream.

Claude produces assistant messages with nested content_blocks. All other adapters (including OpenCode) produce individual typed messages. This function flattens both into a uniform stream where each message has a single, specific type — never nested content_blocks.

Examples

OpencodeEx.normalize_messages(messages)
|> Enum.filter(& &1.type == :text)
|> Enum.map(& &1.content)
|> Enum.join("")

output_style(session)

@spec output_style(pid()) :: {:ok, binary() | nil} | {:error, term()}

Get the output style from the system init data.

prompt_async(session, prompt)

@spec prompt_async(pid(), binary()) :: {:ok, term()} | {:error, term()}

Send a prompt asynchronously using native OpenCode prompt_async.

prompt_async(session, prompt, opts)

@spec prompt_async(pid(), binary(), map()) :: {:ok, term()} | {:error, term()}

provider_auth_methods(session)

@spec provider_auth_methods(pid()) :: {:ok, map()} | {:error, term()}

List native OpenCode provider auth methods.

provider_list(session)

@spec provider_list(pid()) :: {:ok, map()} | {:error, term()}

List native OpenCode providers.

provider_oauth_authorize(session, provider_id, body)

@spec provider_oauth_authorize(pid(), binary(), map()) ::
  {:ok, map()} | {:error, term()}

Start a native OpenCode provider OAuth authorize flow.

provider_oauth_callback(session, provider_id, body)

@spec provider_oauth_callback(pid(), binary(), map()) ::
  {:ok, map()} | {:error, term()}

Complete a native OpenCode provider OAuth callback flow.

query(session, prompt, params \\ %{})

@spec query(pid(), binary(), query_opts()) ::
  {:ok, [message_map()]} | {:error, term()}

Send a query and collect all response messages (blocking).

Returns {:ok, messages} where messages is a list of beam_agent_core message maps. Uses deadline-based timeout.

Options

  • :timeout - total query timeout in ms (default: 120_000)

receive_event(session, ref, timeout \\ 5000)

@spec receive_event(pid(), reference(), timeout()) ::
  {:ok, message_map()} | {:error, term()}

Receive the next normalized event from the native OpenCode /event stream.

reconnect_mcp_server(session, server_name)

@spec reconnect_mcp_server(pid(), binary()) ::
  {:ok, %{required(<<_::48>>) => <<_::88>>}} | {:error, :not_found}

Reconnect a failed MCP server.

revert_session(session, selector)

@spec revert_session(pid(), map()) ::
  {:ok, session_info_map()} | {:error, :invalid_selector | :not_found}

Revert the visible session history to a prior boundary.

rewind_files(session, checkpoint_uuid)

@spec rewind_files(pid(), binary()) ::
  :ok | {:error, :not_found | {:restore_failed, binary(), atom()}}

Revert file changes to a checkpoint via universal checkpointing.

sdk_hook(event, callback)

@spec sdk_hook(atom(), hook_callback()) :: %{event: atom(), callback: hook_callback()}

Create an SDK lifecycle hook.

sdk_hook(event, callback, matcher)

@spec sdk_hook(atom(), hook_callback(), %{tool_name: binary()}) :: %{
  event: atom(),
  callback: hook_callback(),
  matcher: %{tool_name: binary()},
  compiled_re: {:re_pattern, term(), term(), term(), term()}
}

Create an SDK lifecycle hook with a matcher.

send_command(session, command, params \\ %{})

@spec send_command(pid(), binary(), map()) :: {:ok, term()} | {:error, term()}

Send a command to the current session.

send_control(session, method, params \\ %{})

@spec send_control(pid(), binary(), map()) ::
  {:error,
   :not_found
   | {:invalid_param, :max_thinking_tokens}
   | {:missing_param,
      :max_thinking_tokens | :model | :permission_mode | :task_id}
   | {:unknown_method, binary()}}
  | {:ok,
     %{
       max_thinking_tokens: pos_integer(),
       model: term(),
       permission_mode: atom() | binary()
     }}

Send a raw control message via universal control dispatch.

server_health(session)

@spec server_health(pid()) :: {:ok, map()} | {:error, term()}

Check the health of the OpenCode server.

session_info(session)

@spec session_info(pid()) :: {:ok, map()} | {:error, term()}

Query session info (session id, directory, model, transport).

session_init(session, opts)

@spec session_init(pid(), map()) :: {:ok, term()} | {:error, term()}

Initialize the current native OpenCode session.

session_messages(session)

@spec session_messages(pid()) :: {:ok, term()} | {:error, term()}

Fetch native server-side messages for the current session.

session_messages(session, opts)

@spec session_messages(pid(), map()) :: {:ok, term()} | {:error, term()}

Fetch native server-side messages for the current session with options.

set_max_thinking_tokens(session, max_tokens)

@spec set_max_thinking_tokens(pid(), pos_integer()) ::
  {:ok, %{max_thinking_tokens: pos_integer()}}

Set maximum thinking tokens via universal control.

set_mcp_servers(session, servers)

@spec set_mcp_servers(pid(), [%{name: binary(), tools: [map()], version: binary()}]) ::
  {:error, :not_found} | {:ok, %{required(binary()) => binary()}}

Replace MCP server configurations.

set_model(session, model)

@spec set_model(pid(), binary()) :: {:ok, binary()} | {:error, term()}

Change the model at runtime.

set_permission_mode(session, mode)

@spec set_permission_mode(pid(), binary()) :: {:ok, binary()} | {:error, term()}

Change the permission mode at runtime via universal control.

share_session(session)

@spec share_session(pid()) :: {:ok, map()} | {:error, :not_found}

Create or replace share state for the current session.

share_session(session, opts)

@spec share_session(pid(), map()) :: {:ok, map()} | {:error, :not_found}

shell_command(session, command)

@spec shell_command(pid(), binary()) :: {:ok, term()} | {:error, term()}

Run a native OpenCode shell command for the current session.

shell_command(session, command, opts)

@spec shell_command(pid(), binary(), map()) :: {:ok, term()} | {:error, term()}

start_session(opts)

@spec start_session(keyword() | map()) :: {:ok, pid()} | {:error, term()}

Start an OpenCode HTTP session.

stop(session)

@spec stop(pid()) :: :ok

Stop an OpenCode session.

stop_task(session, task_id)

@spec stop_task(pid(), binary()) :: :ok | {:error, :not_found}

Stop a running agent task via universal task tracking.

stream(session, prompt, params \\ %{})

@spec stream(pid(), binary(), map()) :: Enumerable.t()

Returns a Stream that yields {:ok, msg} or {:error, reason} tuples.

Non-raising variant of stream!/3.

stream!(session, prompt, params \\ %{})

@spec stream!(pid(), binary(), map()) :: Enumerable.t()

Returns a Stream that yields messages as they arrive.

Raises on errors. Uses Stream.resource/3 under the hood.

The query is dispatched to the CLI immediately when stream!/3 is called. Message consumption is lazy/pull-based.

Example

session
|> OpencodeEx.stream!("Explain OTP supervision trees")
|> Enum.each(fn msg -> IO.puts(msg.content) end)

submit_feedback(session, feedback)

@spec submit_feedback(pid(), map()) :: :ok

Submit feedback via universal feedback tracking.

summarize_session(session)

@spec summarize_session(pid()) :: {:ok, map()} | {:error, :not_found}

Generate and store a summary for the current session.

summarize_session(session, opts)

@spec summarize_session(pid(), map()) :: {:ok, map()} | {:error, :not_found}

supported_agents(session)

@spec supported_agents(pid()) :: {:ok, list()} | {:error, term()}

List available agents.

supported_commands(session)

@spec supported_commands(pid()) :: {:ok, list()} | {:error, term()}

List available slash commands.

supported_models(session)

@spec supported_models(pid()) :: {:ok, list()} | {:error, term()}

List available models.

thread_archive(session, thread_id)

@spec thread_archive(pid(), binary()) :: {:ok, thread_info()} | {:error, :not_found}

Archive a thread.

thread_fork(session, thread_id)

@spec thread_fork(pid(), binary()) :: {:ok, thread_info()} | {:error, :not_found}

Fork an existing thread.

thread_fork(session, thread_id, opts)

@spec thread_fork(pid(), binary(), thread_opts()) ::
  {:ok, thread_info()} | {:error, :not_found}

thread_list(session)

@spec thread_list(pid()) :: {:ok, [thread_info()]}

List all threads for this session.

thread_read(session, thread_id)

@spec thread_read(pid(), binary()) ::
  {:ok, %{thread: thread_info(), messages: [map()]}} | {:error, :not_found}

Read thread metadata, optionally including visible messages.

thread_read(session, thread_id, opts)

@spec thread_read(pid(), binary(), map()) ::
  {:ok, %{thread: thread_info(), messages: [map()]}} | {:error, :not_found}

thread_resume(session, thread_id)

@spec thread_resume(pid(), binary()) :: {:ok, thread_info()} | {:error, :not_found}

Resume an existing thread.

thread_rollback(session, thread_id, selector)

@spec thread_rollback(pid(), binary(), map()) ::
  {:ok, thread_info()} | {:error, :invalid_selector | :not_found}

Rollback the visible thread history.

thread_start(session, opts \\ %{})

@spec thread_start(pid(), thread_opts()) ::
  {:ok,
   %{
     archived: false,
     created_at: integer(),
     message_count: 0,
     metadata: map(),
     name: binary(),
     session_id: binary(),
     status: :active,
     thread_id: binary(),
     updated_at: integer(),
     visible_message_count: 0,
     parent_thread_id: binary()
   }}

Start a new conversation thread.

thread_unarchive(session, thread_id)

@spec thread_unarchive(pid(), binary()) :: {:ok, thread_info()} | {:error, :not_found}

Unarchive a thread.

todo_summary(todos)

@spec todo_summary([todo_item()]) :: %{
  :total => non_neg_integer(),
  required(atom()) => non_neg_integer()
}

Get a summary of todo counts by status.

toggle_mcp_server(session, server_name, enabled)

@spec toggle_mcp_server(pid(), binary(), boolean()) ::
  {:ok, %{required(<<_::48>>) => <<_::56>>}} | {:error, :not_found}

Enable or disable an MCP server.

tui_append_prompt(session, text)

@spec tui_append_prompt(pid(), binary()) :: {:ok, term()} | {:error, term()}

Append prompt text to the native OpenCode TUI.

tui_open_help(session)

@spec tui_open_help(pid()) :: {:ok, term()} | {:error, term()}

Open the native OpenCode help dialog.

turn_respond(session, request_id, params)

@spec turn_respond(pid(), binary(), map()) ::
  :ok | {:error, :not_found | :already_resolved}

Respond to an agent request via universal turn response.

unrevert_session(session)

@spec unrevert_session(pid()) :: {:ok, session_info_map()} | {:error, :not_found}

Clear any stored session revert state.

unshare_session(session)

@spec unshare_session(pid()) :: :ok | {:error, :not_found}

Revoke share state for the current session.

working_directory(session)

@spec working_directory(pid()) :: {:ok, binary() | nil} | {:error, term()}

Get the working directory from the system init data.