Packages
codex_app_server
0.1.0
Elixir client for the OpenAI Codex app-server JSON-RPC protocol over stdio.
Current section
Files
Jump to
Current section
Files
lib/codex_app_server.ex
defmodule CodexAppServer do
@moduledoc """
Elixir client for the [OpenAI Codex app-server](https://developers.openai.com/codex/app-server/)
JSON-RPC 2.0 protocol over stdio.
## Quick start
{:ok, result} = CodexAppServer.run("/path/to/workspace", "Fix the auth bug",
title: "ABC-123: Fix auth",
approval_policy: "never",
sandbox: "workspace-write",
sandbox_policy: %{"type" => "workspaceWrite"}
)
## Multi-turn sessions
{:ok, session} = CodexAppServer.start_session("/path/to/workspace",
approval_policy: "never",
sandbox: "workspace-write"
)
{:ok, turn1} = CodexAppServer.run_turn(session, "Fix the bug",
title: "ABC-123: Fix auth",
sandbox_policy: %{"type" => "workspaceWrite"}
)
{:ok, turn2} = CodexAppServer.run_turn(session, "Now add tests",
title: "ABC-123: Fix auth",
sandbox_policy: %{"type" => "workspaceWrite"}
)
CodexAppServer.stop_session(session)
## Dynamic tools
Register client-side tools that Codex can invoke during a turn:
tools = [
%{
"name" => "my_api",
"description" => "Query my API",
"inputSchema" => %{"type" => "object", "properties" => %{"query" => %{"type" => "string"}}}
}
]
CodexAppServer.run(workspace, prompt,
tools: tools,
tool_executor: fn "my_api", args -> %{"success" => true, "contentItems" => [...]} end
)
"""
alias CodexAppServer.Session
@type run_opt ::
{:command, String.t()}
| {:title, String.t()}
| {:approval_policy, String.t() | map()}
| {:sandbox, String.t()}
| {:sandbox_policy, map()}
| {:tools, [map()]}
| {:tool_executor, (String.t(), map() -> map())}
| {:on_message, (map() -> any())}
| {:turn_timeout_ms, pos_integer()}
| {:read_timeout_ms, pos_integer()}
| {:client_info, map()}
@type session_opt ::
{:command, String.t()}
| {:approval_policy, String.t() | map()}
| {:sandbox, String.t()}
| {:tools, [map()]}
| {:read_timeout_ms, pos_integer()}
| {:client_info, map()}
@type turn_opt ::
{:title, String.t()}
| {:approval_policy, String.t() | map()}
| {:sandbox_policy, map()}
| {:tools, [map()]}
| {:tool_executor, (String.t(), map() -> map())}
| {:on_message, (map() -> any())}
| {:turn_timeout_ms, pos_integer()}
@doc """
Run a single Codex turn in one call. Starts a session, runs the turn, and stops the session.
Returns `{:ok, result}` on success or `{:error, reason}` on failure.
"""
@spec run(Path.t(), String.t(), [run_opt()]) :: {:ok, map()} | {:error, term()}
def run(workspace, prompt, opts \\ []) do
{session_opts, turn_opts} = split_opts(opts)
with {:ok, session} <- start_session(workspace, session_opts) do
try do
run_turn(session, prompt, turn_opts)
after
stop_session(session)
end
end
end
@doc """
Start a long-lived Codex app-server session for multi-turn usage.
The session spawns the Codex subprocess and completes the `initialize` / `thread/start`
handshake. Use `run_turn/3` to send prompts and `stop_session/1` when done.
"""
@spec start_session(Path.t(), [session_opt()]) :: {:ok, Session.t()} | {:error, term()}
def start_session(workspace, opts \\ []) do
Session.start(workspace, opts)
end
@doc """
Run a turn on an existing session. Blocks until the turn completes, fails, or times out.
"""
@spec run_turn(Session.t(), String.t(), [turn_opt()]) :: {:ok, map()} | {:error, term()}
def run_turn(session, prompt, opts \\ []) do
Session.run_turn(session, prompt, opts)
end
@doc """
Stop a session and terminate the Codex subprocess.
"""
@spec stop_session(Session.t()) :: :ok
def stop_session(session) do
Session.stop(session)
end
defp split_opts(opts) do
session_keys = [:command, :approval_policy, :sandbox, :tools, :read_timeout_ms, :client_info]
session_opts = Keyword.take(opts, session_keys)
turn_opts = Keyword.drop(opts, [:command, :sandbox, :read_timeout_ms, :client_info])
{session_opts, turn_opts}
end
end