{"id":32167285,"url":"https://github.com/nshkrdotcom/codex_sdk","last_synced_at":"2026-02-18T21:02:42.417Z","repository":{"id":318723959,"uuid":"1074639979","full_name":"nshkrdotcom/codex_sdk","owner":"nshkrdotcom","description":"OpenAI Codex SDK written in Elixir","archived":false,"fork":false,"pushed_at":"2026-02-07T05:14:00.000Z","size":1332,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-02-07T13:37:09.296Z","etag":null,"topics":["ai","ai-integration","api-client","beam","code-generation","code-synthesis","codex","elixir","erlang-vm","functional-programming","llm","llm-client","nshkr-ai-sdk","openai","otp","sdk"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nshkrdotcom.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-10-12T07:26:57.000Z","updated_at":"2026-02-07T05:13:47.000Z","dependencies_parsed_at":"2025-10-19T23:20:30.478Z","dependency_job_id":null,"html_url":"https://github.com/nshkrdotcom/codex_sdk","commit_stats":null,"previous_names":["nshkrdotcom/codex_sdk"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/nshkrdotcom/codex_sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fcodex_sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fcodex_sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fcodex_sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fcodex_sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nshkrdotcom","download_url":"https://codeload.github.com/nshkrdotcom/codex_sdk/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fcodex_sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29596125,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T20:59:56.587Z","status":"ssl_error","status_checked_at":"2026-02-18T20:58:41.434Z","response_time":162,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-integration","api-client","beam","code-generation","code-synthesis","codex","elixir","erlang-vm","functional-programming","llm","llm-client","nshkr-ai-sdk","openai","otp","sdk"],"created_at":"2025-10-21T15:26:40.708Z","updated_at":"2026-02-18T21:02:42.410Z","avatar_url":"https://github.com/nshkrdotcom.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/codex_sdk.svg\" alt=\"Codex SDK Logo\" width=\"200\" height=\"200\"\u003e\n\u003c/p\u003e\n\n# Codex SDK for Elixir\n\n[![CI](https://github.com/nshkrdotcom/codex_sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/nshkrdotcom/codex_sdk/actions/workflows/ci.yml)\n[![Elixir](https://img.shields.io/badge/elixir-1.18.3-purple.svg)](https://elixir-lang.org)\n[![OTP](https://img.shields.io/badge/otp-27.3.3-blue.svg)](https://www.erlang.org)\n[![Hex.pm](https://img.shields.io/hexpm/v/codex_sdk.svg)](https://hex.pm/packages/codex_sdk)\n[![Documentation](https://img.shields.io/badge/docs-hexdocs-purple.svg)](https://hexdocs.pm/codex_sdk)\n[![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/nshkrdotcom/codex_sdk/blob/main/LICENSE)\n\nAn idiomatic Elixir SDK for embedding OpenAI's Codex agent in your workflows and applications. This SDK wraps the `codex-rs` executable, providing a complete, production-ready interface with streaming support and comprehensive event handling.\n\n## Features\n\n- **End-to-End Codex Lifecycle**: Spawn, resume, and manage full Codex threads with rich turn instrumentation.\n- **Multi-Transport Support**: Default exec JSONL (`codex exec --json`) plus stateful app-server JSON-RPC over stdio (`codex app-server`) with multi-modal `UserInput` blocks.\n- **Upstream Compatibility**: Mirrors Codex CLI flags (profile/OSS/full-auto/color, config overrides, review/resume) and handles app-server protocol drift (e.g. MCP list method rename fallbacks).\n- **Streaming \u0026 Structured Output**: Real-time events, per-thread output schemas, reasoning summary/content preservation, and typed app-server deltas.\n- **File \u0026 Attachment Pipeline**: Secure temp file registry and change events.\n- **Approval Hooks \u0026 Sandbox Policies**: Dynamic or static approval flows with registry-backed persistence.\n- **Collaboration \u0026 Personality Controls**: Collaboration modes, personality overrides, and web search mode toggles.\n- **Tooling \u0026 MCP Integration**: Built-in registry for Codex tool manifests, MCP client helpers, and elicitation handling.\n- **Observability-Ready**: Telemetry spans, OTLP exporters gated by environment flags, usage stats, and rate limit snapshots.\n- **Realtime API Support**: Full integration with OpenAI Realtime API for bidirectional voice interactions with WebSocket streaming.\n- **Voice Pipeline**: Non-realtime STT -\u003e Workflow -\u003e TTS pipeline with streaming audio support and multi-turn conversations.\n\n## Installation\n\nAdd `codex_sdk` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:codex_sdk, \"~\u003e 0.10.1\"}\n  ]\nend\n```\n\n## Prerequisites\n\nYou must have the `codex` CLI installed. Install it via npm or Homebrew:\n\n```bash\n# Using npm\nnpm install -g @openai/codex\n\n# Using Homebrew\nbrew install codex\n```\n\nThe SDK does not vendor `codex-rs`; it shells out to the `codex` executable on your system. Path\nresolution follows this order:\n\n1. `codex_path_override` supplied in `Codex.Options.new/1`\n2. `CODEX_PATH` environment variable\n3. `System.find_executable(\"codex\")`\n\nMake sure the binary at the resolved location is executable and kept up to date.\n\nFor authentication, sign in with your ChatGPT account (this stores credentials for the CLI):\n\n```bash\ncodex\n# Select \"Sign in with ChatGPT\"\n```\n\nAlternatively, set `CODEX_API_KEY` before starting your BEAM node. The SDK prefers `CODEX_API_KEY`,\nthen `auth.json` `OPENAI_API_KEY`, and otherwise falls back to your CLI login tokens stored under\n`CODEX_HOME` (default `~/.codex/auth.json`, with legacy credential file support). If neither an API\nkey nor an authenticated CLI session is available, Codex executions will fail with upstream\nauthentication errors—the SDK does not perform additional login flows.\n\nIf `cli_auth_credentials_store = \"keyring\"` is set in config and keyring support is unavailable,\nthe SDK logs a warning and skips file-based tokens (remote model fetch falls back to bundled models).\nWhen `cli_auth_credentials_store = \"auto\"` and keyring is unavailable, the SDK falls back to file-based auth.\n\nWhen an API key is supplied, the SDK forwards it as both `CODEX_API_KEY` and `OPENAI_API_KEY`\nto the codex subprocess to align with provider expectations.\n\nBase URL precedence is: explicit `:base_url` in `Codex.Options.new/1`, then `OPENAI_BASE_URL`,\nthen the OpenAI default (`https://api.openai.com/v1`).\n\nDefault model: `gpt-5.3-codex` (unless overridden by `CODEX_MODEL`, `OPENAI_DEFAULT_MODEL`, or `CODEX_MODEL_DEFAULT`).\n\nRemote models are gated behind `features.remote_models = true` in the effective Codex config (system `/etc/codex/config.toml`, user `$CODEX_HOME/config.toml`, and `.codex/config.toml` layers between `cwd` and the project root; root markers default to `.git` and are configurable via `project_root_markers`). When enabled, the SDK merges the remote `/models` list (or bundled `models.json`) with local presets and keeps `gpt-5.3-codex` available.\n\nSee the [OpenAI Codex documentation](https://github.com/openai/codex) for more authentication options.\n\n## Quick Start\n\n### Basic Usage\n\n```elixir\n# Start a new conversation\n{:ok, thread} = Codex.start_thread()\n\n# Run a turn and get results\n{:ok, result} = Codex.Thread.run(thread, \"Explain the purpose of GenServers in Elixir\")\n\n# Access the final response\nIO.puts(result.final_response)\n\n# Inspect all items (messages, reasoning, commands, file changes, etc.)\nIO.inspect(result.items)\n\n# Continue the conversation\n{:ok, next_result} = Codex.Thread.run(thread, \"Give me an example\")\n```\n\n### App-server Transport (Optional)\n\nThe SDK defaults to exec JSONL for backwards compatibility. To use the stateful app-server transport:\n\n```elixir\n{:ok, codex_opts} = Codex.Options.new(%{api_key: System.fetch_env!(\"CODEX_API_KEY\")})\n{:ok, conn} = Codex.AppServer.connect(codex_opts)\n\n{:ok, thread} =\n  Codex.start_thread(codex_opts, %{\n    transport: {:app_server, conn},\n    working_directory: \"/project\"\n  })\n\n{:ok, result} = Codex.Thread.run(thread, \"List the available skills for this repo\")\n\n{:ok, %{\"data\" =\u003e skills}} = Codex.AppServer.skills_list(conn, cwds: [\"/project\"])\n```\n\nMulti-modal input is supported on app-server transport:\n\n```elixir\ninput = [\n  %{type: :text, text: \"Explain this screenshot\"},\n  %{type: :local_image, path: \"/tmp/screenshot.png\"}\n]\n\n{:ok, result} = Codex.Thread.run(thread, input)\n```\n\nNote: exec JSONL transport still accepts text input only; list inputs return `{:error, {:unsupported_input, :exec}}`.\n\nApp-server-only APIs include:\n\n- `Codex.AppServer.thread_list/2`, `thread_archive/2`, `thread_read/3`, `thread_fork/3`, `thread_rollback/3`, `thread_loaded_list/2`\n- `Codex.AppServer.model_list/2`, `config_read/2`, `config_write/4`, `config_batch_write/3`, `config_requirements/1`\n- `Codex.AppServer.skills_config_write/3`, `collaboration_mode_list/1`, `apps_list/2`\n- `Codex.AppServer.turn_interrupt/3`\n- `Codex.AppServer.fuzzy_file_search/3` (legacy v1 helper used by `@` file search)\n- `Codex.AppServer.command_write_stdin/4` (interactive command stdin)\n- `Codex.AppServer.Account.*` and `Codex.AppServer.Mcp.*` endpoints (including MCP reload)\n- Approvals via `Codex.AppServer.subscribe/2` + `Codex.AppServer.respond/3`\n\nNote: app-server v2 does not support sending `UserInput::Skill` directly; use `skills/list` and inject skill content as text if you need emulation.\nLegacy app-server v1 conversation flows are available via `Codex.AppServer.V1`.\n\n### Streaming Responses\n\nFor real-time processing of events as they occur:\n\n```elixir\n{:ok, thread} = Codex.start_thread()\n\n{:ok, stream} = Codex.Thread.run_streamed(\n  thread,\n  \"Analyze this codebase and suggest improvements\"\n)\n\n# Process events as they arrive\nfor event \u003c- stream do\n  case event do\n    %Codex.Events.ItemStarted{item: item} -\u003e\n      IO.puts(\"New item: #{item.type}\")\n\n    %Codex.Events.ItemCompleted{item: %{type: \"agent_message\", text: text}} -\u003e\n      IO.puts(\"Response: #{text}\")\n\n    %Codex.Events.TurnCompleted{usage: usage} -\u003e\n      IO.puts(\"Tokens used: #{usage.input_tokens + usage.output_tokens}\")\n\n    _ -\u003e\n      :ok\n  end\nend\n```\n\n### Structured Output\n\nRequest JSON responses conforming to a specific schema:\n\n```elixir\nschema = %{\n  \"type\" =\u003e \"object\",\n  \"properties\" =\u003e %{\n    \"summary\" =\u003e %{\"type\" =\u003e \"string\"},\n    \"issues\" =\u003e %{\n      \"type\" =\u003e \"array\",\n      \"items\" =\u003e %{\n        \"type\" =\u003e \"object\",\n        \"properties\" =\u003e %{\n          \"severity\" =\u003e %{\"type\" =\u003e \"string\", \"enum\" =\u003e [\"low\", \"medium\", \"high\"]},\n          \"description\" =\u003e %{\"type\" =\u003e \"string\"},\n          \"file\" =\u003e %{\"type\" =\u003e \"string\"}\n        },\n        \"required\" =\u003e [\"severity\", \"description\"]\n      }\n    }\n  },\n  \"required\" =\u003e [\"summary\", \"issues\"]\n}\n\n{:ok, thread} = Codex.start_thread()\n\n{:ok, result} = Codex.Thread.run(\n  thread,\n  \"Analyze the code quality of this project\",\n  output_schema: schema\n)\n\n# Parse the JSON response\n{:ok, data} = Jason.decode(result.final_response)\nIO.inspect(data[\"issues\"])\n```\n\n### Runnable Examples\n\nThe repository ships with standalone scripts under `examples/` that you can execute via `mix run`. Live scripts (prefixed `live_`) hit the real Codex CLI using your existing CLI login—no extra API key wiring needed. To run everything sequentially:\n\n```bash\n./examples/run_all.sh\n```\n\nOr run individual scripts:\n\n```bash\n# Basic blocking turn and item traversal\nmix run examples/basic_usage.exs\n\n# Streaming patterns (real-time, progressive, stateful)\nmix run examples/streaming.exs progressive\n\n# Live model defaults + compaction/usage handling (CLI login or CODEX_API_KEY)\nmix run examples/live_usage_and_compaction.exs \"summarize recent changes\"\n\n# Live exec controls (env injection, cancellation token, timeout)\nmix run examples/live_exec_controls.exs \"list files and print CODEX_DEMO_ENV\"\n\n# Structured output decoding and struct mapping\nmix run examples/structured_output.exs struct\n\n# Conversation/resume workflow helpers\nmix run examples/conversation_and_resume.exs save-resume\n\n# Concurrency + collaboration demos\nmix run examples/concurrency_and_collaboration.exs parallel lib/codex/thread.ex lib/codex/exec.ex\n\n# Auto-run tool bridging (forwards outputs/failures to codex exec)\nmix run examples/tool_bridging_auto_run.exs\n\n# Live two-turn session using CLI login or CODEX_API_KEY\nmix run examples/live_session_walkthrough.exs \"your prompt here\"\n\n# Live tooling stream: shows shell + MCP events and falls back to last agent message\nmix run examples/live_tooling_stream.exs \"optional prompt\"\n\n# Live telemetry stream: prints thread/turn ids, source metadata, usage deltas, diffs, and compaction (low reasoning, fast prompt)\nmix run examples/live_telemetry_stream.exs\n\n# Live CLI demo (requires authenticated codex CLI or CODEX_API_KEY)\nmix run examples/live_cli_demo.exs \"What is the capital of France?\"\n```\n\n\n### Realtime Voice Interactions\n\nFor bidirectional voice interactions using the OpenAI Realtime API:\n- Auth precedence for realtime/voice API keys is:\n  `CODEX_API_KEY` -\u003e `auth.json` `OPENAI_API_KEY` -\u003e `OPENAI_API_KEY`.\n\n```elixir\nalias Codex.Realtime\n\n# Create a realtime agent\nagent = Realtime.agent(\n  name: \"VoiceAssistant\",\n  instructions: \"You are a helpful voice assistant. Keep responses brief.\"\n)\n\n# Configure session options\nconfig = %Codex.Realtime.Config.RunConfig{\n  model_settings: %Codex.Realtime.Config.SessionModelSettings{\n    voice: \"alloy\",\n    turn_detection: %Codex.Realtime.Config.TurnDetectionConfig{\n      type: :semantic_vad,\n      eagerness: :medium\n    }\n  }\n}\n\n# Start a realtime session\n{:ok, session} = Realtime.run(agent, config: config)\n\n# Subscribe to events\nRealtime.subscribe(session, self())\n\n# Send audio and receive responses (commit on final chunk)\nRealtime.send_audio(session, audio_data, commit: true)\n```\n\n`Realtime.Session` also traps linked WebSocket exits and keeps processing other session\nmessages while tool calls are running.\n\n### Voice Pipeline (Non-Realtime)\n\nFor STT -\u003e Workflow -\u003e TTS processing:\n\n```elixir\nalias Codex.Voice.{Pipeline, SimpleWorkflow, Config}\n\n# Create a simple workflow\nworkflow = SimpleWorkflow.new(\n  fn text -\u003e [\"You said: #{text}. How can I help?\"] end,\n  greeting: \"Hello! I'm ready to listen.\"\n)\n\n# Configure the pipeline\nconfig = %Config{\n  workflow_name: \"VoiceDemo\",\n  tts_settings: %Config.TTSSettings{voice: :nova}\n}\n\n# Create and run the pipeline\n{:ok, pipeline} = Pipeline.start_link(workflow: workflow, config: config)\n{:ok, result} = Pipeline.run(pipeline, audio_input)\n\n# Process streamed audio output\nfor event \u003c- result do\n  case event do\n    %Codex.Voice.Events.VoiceStreamEventAudio{data: data} -\u003e\n      # Handle audio chunk\n      play_audio(data)\n    _ -\u003e :ok\n  end\nend\n```\n\nSee `examples/realtime_*.exs` and `examples/voice_*.exs` for comprehensive demos.\n\n### Resuming Threads\n\nThreads are persisted in `~/.codex/sessions`. Resume previous conversations:\n\n```elixir\nthread_id = \"thread_abc123\"\n{:ok, thread} = Codex.resume_thread(thread_id)\n\n{:ok, result} = Codex.Thread.run(thread, \"Continue from where we left off\")\n```\n\nResume the most recent session (equivalent to `codex exec resume --last`):\n\n```elixir\n{:ok, thread} = Codex.resume_thread(:last)\n{:ok, result} = Codex.Thread.run(thread, \"Continue from where we left off\")\n```\n\n### Session Helpers\n\nThe CLI writes session logs under `~/.codex/sessions`. The SDK can list them and\napply or undo diffs locally:\n\n```elixir\n{:ok, sessions} = Codex.Sessions.list_sessions()\n\n{:ok, result} = Codex.Sessions.apply(diff, cwd: \"/path/to/repo\")\n{:ok, _undo} = Codex.Sessions.undo(ghost_snapshot, cwd: \"/path/to/repo\")\n```\n\n### Configuration Options\n\n```elixir\n# Codex-level options\n{:ok, codex_options} =\n  Codex.Options.new(\n    api_key: System.fetch_env!(\"CODEX_API_KEY\"),\n    codex_path_override: \"/custom/path/to/codex\",\n    telemetry_prefix: [:codex, :sdk],\n    model: \"o1\",\n    reasoning_effort: :high,  # :none | :minimal | :low | :medium | :high | :xhigh\n    model_personality: :friendly,\n    review_model: \"gpt-5.3-codex\",\n    tool_output_token_limit: 512,\n    history: %{persistence: \"local\", max_bytes: 1_000_000},\n    config: %{\"model_reasoning_summary\" =\u003e \"concise\"}  # global --config baseline\n  )\n\n# Thread-level options\n{:ok, thread_options} =\n  Codex.Thread.Options.new(\n    metadata: %{project: \"codex_sdk\"},\n    labels: %{environment: \"dev\"},\n    auto_run: true,\n    sandbox: :strict,\n    approval_timeout_ms: 45_000,\n    web_search_mode: :cached,  # :disabled | :cached | :live (explicit :disabled forces disable override)\n    personality: :pragmatic,   # :friendly | :pragmatic | :none (works consistently on exec/app-server)\n    collaboration_mode: :plan  # :plan | :pair_programming | :code | :default | :execute | :custom (app-server)\n  )\n\n{:ok, thread} = Codex.start_thread(codex_options, thread_options)\n\n# Run-level options (validated by Codex.RunConfig.new/1)\nrun_options = %{\n  run_config: %{\n    auto_previous_response_id: true\n  }\n}\n\n{:ok, result} = Codex.Thread.run(thread, \"Your prompt\", run_options)\nIO.inspect(result.last_response_id)\n# Note: last_response_id remains nil until codex exec emits response_id fields.\n\n# Turn-level options\nturn_options = %{output_schema: my_json_schema, personality: :friendly}\n\n{:ok, result} = Codex.Thread.run(thread, \"Your prompt\", turn_options)\n\n# Exec controls: inject env, set cancellation token/timeout/idle timeout (forwarded to codex exec)\nturn_options = %{\n  env: %{\"CODEX_DEMO_ENV\" =\u003e \"from-sdk\"},\n  cancellation_token: \"demo-token-123\",\n  timeout_ms: 120_000,\n  stream_idle_timeout_ms: 300_000\n}\n\n# The SDK also sets CODEX_INTERNAL_ORIGINATOR_OVERRIDE=codex_sdk_elixir\n# unless you provide your own value in `env`.\n\n{:ok, stream} =\n  Codex.Thread.run_streamed(thread, \"List three files and echo $CODEX_DEMO_ENV\", turn_options)\n\n# Opt-in retry and rate limit handling\n{:ok, thread_opts} =\n  Codex.Thread.Options.new(\n    retry: true,\n    retry_opts: [max_attempts: 3],\n    rate_limit: true,\n    rate_limit_opts: [max_attempts: 3]\n  )\n```\n\n### Config Overrides\n\nOptions-level, thread-level, and turn-level config overrides are forwarded as\n`--config key=value` flags to the Codex CLI (exec transport). For app-server transport,\ntyped derived settings plus options-level config overrides are merged into the structured\nconfig payload when unset. Four layers of precedence apply for exec — later wins:\n\n1. **Options-level global** — `Codex.Options.new(config: ...)`\n2. **Derived** — automatically generated from typed `Codex.Options` and `Codex.Thread.Options` fields\n3. **Thread-level** — `Codex.Thread.Options.config_overrides`\n4. **Turn-level** — `config_overrides` in turn opts passed to `Thread.run/3`\n\nNested maps are auto-flattened to dotted-path keys:\n\n```elixir\n# These two are equivalent:\nconfig_overrides: %{\"features\" =\u003e %{\"web_search_request\" =\u003e true}}\nconfig_overrides: [{\"features.web_search_request\", true}]\n```\n\nOverride values are validated at runtime and must be TOML-compatible primitives:\nstrings, booleans, integers/floats, arrays, and nested maps. Unsupported values\n(`nil`, tuples, PIDs, functions, etc.) return an error before the CLI is invoked.\n\nWhen you explicitly disable web search (`web_search_enabled: false` or\n`web_search_mode: :disabled`), the SDK emits `web_search=\"disabled\"` so that\nthread-level intent overrides existing CLI config. If you leave defaults\nuntouched, no disable override is injected.\n\n### Approval Hooks\n\nCodex ships with approval policies and hooks so you can review potentially destructive actions\nbefore the agent executes them. Policies are provided per-thread:\n\n```elixir\npolicy = Codex.Approvals.StaticPolicy.deny(reason: \"manual review required\")\n\n{:ok, thread_opts} =\n  Codex.Thread.Options.new(\n    sandbox: :strict,\n    approval_policy: policy,\n    approval_timeout_ms: 60_000\n  )\n\n{:ok, thread} = Codex.start_thread(%Codex.Options{}, thread_opts)\n```\n\nTo integrate with external workflow tools, implement the `Codex.Approvals.Hook` behaviour and\nset it as the `approval_hook`:\n\n```elixir\ndefmodule MyApp.ApprovalHook do\n  @behaviour Codex.Approvals.Hook\n\n  def review_tool(event, context, _opts) do\n    # Route to Slack/Jira/etc. and await a decision\n    if MyApp.RiskEngine.requires_manual_review?(event, context) do\n      {:deny, \"pending review\"}\n    else\n      :allow\n    end\n  end\nend\n\n{:ok, thread_opts} = Codex.Thread.Options.new(approval_hook: MyApp.ApprovalHook)\n{:ok, thread} = Codex.start_thread(%Codex.Options{}, thread_opts)\n```\n\nHooks can be synchronous or async (see `Codex.Approvals.Hook` for callback semantics), and all\ndecisions emit telemetry so you can audit approvals externally.\n\nCodex respects upstream safe-command markers: tool events flagged with `requires_approval: false`\nbypass approval gating automatically, keeping low-risk workspace actions fast while still blocking\nrequests that require review.\n\nFor app-server file-change approvals, hooks can return `{:allow, grant_root: \"/path\"}` to accept\nthe proposed root for the current session.\n\nTool-call events can also arrive pre-approved via `approved_by_policy` (or `approved`) from the\nCLI; the SDK mirrors that bypass and skips hooks while still emitting telemetry. Sandbox warnings\nare normalized so Windows paths dedupe cleanly (e.g., `C:/Temp` and `C:\\\\Temp` coalesce). See\n`examples/sandbox_warnings_and_approval_bypass.exs` for a runnable walkthrough.\n\n### File Attachments \u0026 Registries\n\nStage attachments once and reuse them across turns or threads with the built-in registry:\n\n```elixir\n{:ok, attachment} = Codex.Files.stage(\"reports/summary.md\", ttl_ms: :infinity)\n\nthread_opts =\n  %Codex.Thread.Options{}\n  |\u003e Codex.Files.attach(attachment)\n\n{:ok, thread} = Codex.start_thread(%Codex.Options{}, thread_opts)\n```\n\nQuery `Codex.Files.metrics/0` for staging stats and force cleanup with `Codex.Files.force_cleanup/0`.\n`Codex.Files.force_cleanup/0`, `Codex.Files.reset!/0`, and `Codex.Files.metrics/0` return\n`{:error, reason}` if the registry is unavailable.\nUse `Codex.Files.list_staged_result/0` for explicit `{:ok, list} | {:error, reason}` responses;\n`Codex.Files.list_staged/0` remains available as a compatibility helper that falls back to `[]` on\nstartup errors.\nStaged files are runtime-scoped; the registry clears the staging directory on startup, so re-stage\nattachments after restarts.\n\n### MCP Tool Discovery\n\nThe SDK provides MCP client helpers for discovering and invoking tools from MCP servers:\n\n```elixir\n# Connect to a stdio MCP server\n{:ok, transport} =\n  Codex.MCP.Transport.Stdio.start_link(\n    command: \"npx\",\n    args: [\"-y\", \"mcp-server\"]\n  )\n\n{:ok, client} =\n  Codex.MCP.Client.initialize(\n    {Codex.MCP.Transport.Stdio, transport},\n    client: \"codex-elixir\",\n    version: \"0.1.0\",\n    server_name: \"my_server\"\n  )\n\n# List tools with filtering\n{:ok, tools, client} = Codex.MCP.Client.list_tools(client,\n  allow: [\"read_file\", \"write_file\"],\n  deny: [\"dangerous_tool\"]\n)\n\n# List tools with qualified names (mcp__server__tool format)\n{:ok, tools, client} = Codex.MCP.Client.list_tools(client, qualify?: true)\n\n# Each tool includes:\n# - \"name\" - original tool name\n# - \"qualified_name\" - fully qualified name (e.g., \"mcp__my_server__read_file\")\n# - \"server_name\" - server identifier\n```\n\n`Codex.MCP.Transport.StreamableHTTP` provides JSON-RPC over HTTP with bearer/OAuth\nauth support for remote MCP servers.\nTransport failures are normalized to `{:error, reason}` tuples.\n\nTool name qualification follows the OpenAI convention (`^[a-zA-Z0-9_-]+$`). Names exceeding\n64 characters are truncated with a SHA1 hash suffix for disambiguation:\n\n```elixir\nCodex.MCP.Client.qualify_tool_name(\"server1\", \"tool_a\")\n#=\u003e \"mcp__server1__tool_a\"\n\n# Long names are truncated with SHA1 suffix\nCodex.MCP.Client.qualify_tool_name(\"srv\", String.duplicate(\"a\", 80))\n#=\u003e 64-character string with SHA1 hash suffix\n```\n\nResults are cached by default; bypass with `cache?: false`. See `Codex.MCP.Client` for\nfull documentation and `examples/live_mcp_and_sessions.exs` for a runnable demo.\n\n### Shell Hosted Tool\n\nThe SDK provides a fully-featured shell command execution tool with approval integration,\ntimeout handling, and output truncation:\n\n```elixir\nalias Codex.Tools\nalias Codex.Tools.ShellTool\n\n# Register with default settings (60s timeout, 10KB max output)\n{:ok, _} = Tools.register(ShellTool)\n\n# Execute a simple command\n{:ok, result} = Tools.invoke(\"shell\", %{\"command\" =\u003e [\"ls\", \"-la\"]}, %{})\n# =\u003e %{\"output\" =\u003e \"...\", \"exit_code\" =\u003e 0, \"success\" =\u003e true}\n\n# With working directory\n{:ok, result} = Tools.invoke(\"shell\", %{\"command\" =\u003e [\"pwd\"], \"workdir\" =\u003e \"/tmp\"}, %{})\n\n# With custom timeout and output limits\n{:ok, _} = Tools.register(ShellTool,\n  timeout_ms: 30_000,\n  max_output_bytes: 5000\n)\n\n# With approval callback for sensitive commands\napproval = fn cmd, _ctx -\u003e\n  if String.contains?(cmd, \"rm\"), do: {:deny, \"rm not allowed\"}, else: :ok\nend\n\n{:ok, _} = Tools.register(ShellTool, approval: approval)\n{:error, {:approval_denied, \"rm not allowed\"}} =\n  Tools.invoke(\"shell\", %{\"command\" =\u003e [\"rm\", \"file\"]}, %{})\n```\n\nFor custom execution, provide a custom executor:\n\n```elixir\ncustom_executor = fn %{\"command\" =\u003e cmd}, _ctx, _meta -\u003e\n  formatted = if is_list(cmd), do: Enum.join(cmd, \" \"), else: cmd\n  {:ok, %{\"output\" =\u003e \"custom: #{formatted}\", \"exit_code\" =\u003e 0}}\nend\n\n{:ok, _} = Tools.register(ShellTool, executor: custom_executor)\n```\n\nFor string shell scripts, use the `shell_command` tool:\n\n```elixir\nalias Codex.Tools.ShellCommandTool\n\n{:ok, _} = Tools.register(ShellCommandTool)\n{:ok, result} = Tools.invoke(\"shell_command\", %{\"command\" =\u003e \"ls -la\", \"workdir\" =\u003e \"/tmp\"}, %{})\n```\n\nAdditional hosted tools include `write_stdin` (unified exec sessions via app-server) and\n`view_image` (local image attachments gated by `features.view_image_tool` or\n`Thread.Options.view_image_tool_enabled`).\n\nSee `examples/shell_tool.exs` for a complete demonstration.\n\n### FileSearch Hosted Tool\n\nThe SDK provides a local filesystem search tool with glob pattern matching and\ncontent search capabilities:\n\n```elixir\nalias Codex.Tools\nalias Codex.Tools.FileSearchTool\n\n# Register with default settings\n{:ok, _} = Tools.register(FileSearchTool)\n\n# Find all Elixir files recursively\n{:ok, result} = Tools.invoke(\"file_search\", %{\"pattern\" =\u003e \"lib/**/*.ex\"}, %{})\n# =\u003e %{\"count\" =\u003e 42, \"files\" =\u003e [%{\"path\" =\u003e \"lib/foo.ex\"}, ...]}\n\n# Search file content with regex\n{:ok, result} = Tools.invoke(\"file_search\", %{\n  \"pattern\" =\u003e \"**/*.ex\",\n  \"content\" =\u003e \"defmodule\"\n}, %{})\n# =\u003e %{\"count\" =\u003e 10, \"files\" =\u003e [%{\"path\" =\u003e \"lib/foo.ex\", \"matches\" =\u003e [...]}]}\n\n# Case-insensitive content search\n{:ok, result} = Tools.invoke(\"file_search\", %{\n  \"pattern\" =\u003e \"**/*.ex\",\n  \"content\" =\u003e \"ERROR\",\n  \"case_sensitive\" =\u003e false\n}, %{})\n\n# Limit results\n{:ok, result} = Tools.invoke(\"file_search\", %{\n  \"pattern\" =\u003e \"**/*\",\n  \"max_results\" =\u003e 20\n}, %{})\n\n# Custom base path\n{:ok, _} = Tools.register(FileSearchTool, base_path: \"/project\")\n```\n\nSupported glob patterns:\n- `*.ex` - All `.ex` files in base directory\n- `**/*.ex` - All `.ex` files recursively\n- `lib/**/*.{ex,exs}` - All Elixir files under lib/\n\nSee `examples/file_search_tool.exs` for more examples.\n\n### MCP Tool Invocation\n\nInvoke tools on MCP servers with built-in retry logic, approval callbacks, and telemetry:\n\n```elixir\n# Basic invocation with default retries (3) and exponential backoff\n{:ok, result} = Codex.MCP.Client.call_tool(client, \"echo\", %{\"text\" =\u003e \"hello\"})\n\n# Custom retry and timeout settings\n{:ok, result} = Codex.MCP.Client.call_tool(client, \"fetch\", %{\"url\" =\u003e url},\n  retries: 5,\n  timeout_ms: 30_000,\n  backoff: fn attempt -\u003e Process.sleep(attempt * 200) end\n)\n\n# With approval callback (for sensitive operations)\n{:ok, result} = Codex.MCP.Client.call_tool(client, \"write_file\", args,\n  approval: fn tool, args, context -\u003e\n    if authorized?(context.user, tool), do: :ok, else: {:deny, \"unauthorized\"}\n  end,\n  context: %{user: current_user}\n)\n```\n\nTelemetry events are emitted for observability:\n- `[:codex, :mcp, :tool_call, :start]` - When a call begins\n- `[:codex, :mcp, :tool_call, :success]` - On successful completion\n- `[:codex, :mcp, :tool_call, :failure]` - On failure after retries exhausted\n\n### Custom Prompts and Skills\n\nList and expand custom prompts from `$CODEX_HOME/prompts`, and load skills when\n`features.skills` is enabled:\n\n```elixir\n{:ok, prompts} = Codex.Prompts.list()\n{:ok, expanded} = Codex.Prompts.expand(Enum.at(prompts, 0), \"FILE=lib/app.ex\")\n\n{:ok, conn} = Codex.AppServer.connect(codex_opts)\n{:ok, %{\"data\" =\u003e skills}} = Codex.Skills.list(conn, skills_enabled: true)\n{:ok, content} = Codex.Skills.load(hd(hd(skills)[\"skills\"]), skills_enabled: true)\n```\n\n### Retry Logic\n\nThe SDK provides comprehensive retry utilities via `Codex.Retry` for handling transient failures:\n\n```elixir\nalias Codex.Retry\n\n# Basic retry with defaults (4 attempts, exponential backoff, 200ms base delay)\n{:ok, result} = Retry.with_retry(fn -\u003e make_api_call() end)\n\n# Custom configuration\n{:ok, result} = Retry.with_retry(\n  fn -\u003e risky_operation() end,\n  max_attempts: 5,\n  base_delay_ms: 100,\n  max_delay_ms: 5_000,\n  strategy: :exponential,\n  jitter: true,\n  on_retry: fn attempt, error -\u003e\n    Logger.warning(\"Retry #{attempt}: #{inspect(error)}\")\n  end\n)\n\n# Different backoff strategies\nRetry.with_retry(fun, strategy: :linear)      # 100, 200, 300, 400ms...\nRetry.with_retry(fun, strategy: :constant)    # 100, 100, 100, 100ms...\nRetry.with_retry(fun, strategy: :exponential) # 100, 200, 400, 800ms... (default)\n\n# Custom backoff function\nRetry.with_retry(fun, strategy: fn attempt -\u003e attempt * 50 end)\n\n# Custom retry predicate\nRetry.with_retry(fun, retry_if: fn\n  :my_transient_error -\u003e true\n  _ -\u003e false\nend)\n\n# Stream retry (retries entire stream creation on failure)\nstream = Retry.with_stream_retry(fn -\u003e make_streaming_request() end)\nEnum.each(stream, \u0026process_item/1)\n```\n\nDefault retryable errors include: `:timeout`, `:econnrefused`, `:econnreset`, `:closed`,\n`:nxdomain`, 5xx HTTP errors, 429 rate limits, stream errors, and `Codex.TransportError`\nwith `retryable?: true`. See `examples/retry_example.exs` for more patterns.\n\n### Telemetry \u0026 OTLP Exporting\n\nOpenTelemetry exporting is disabled by default. To ship traces/metrics to a collector, set\n`CODEX_OTLP_ENABLE=1` along with the endpoint (and optional headers) before starting your\napplication:\n\n```bash\nexport CODEX_OTLP_ENABLE=1\nexport CODEX_OTLP_ENDPOINT=\"https://otel.example.com:4318\"\nexport CODEX_OTLP_HEADERS=\"authorization=Bearer abc123\"\n\nmix run examples/basic_usage.exs\n```\n\nWhen the flag is not set (default), the SDK runs without booting the OTLP exporter—avoiding\n`tls_certificate_check` warnings on systems without the helper installed.\n\nThe Codex CLI (`codex-rs`) has its own OpenTelemetry **log** exporter, configured separately via\n`$CODEX_HOME/config.toml` (default `~/.codex/config.toml`) under `[otel]`. This is independent of\nthe Elixir SDK exporter above.\n\n```toml\n[otel]\nenvironment = \"staging\"\nexporter = \"otlp-grpc\"\nlog_user_prompt = false\n\n[otel.exporter.\"otlp-grpc\"]\nendpoint = \"https://otel.example.com:4317\"\n```\n\nSee `codex/docs/config.md` for the full upstream reference. To point Codex at an isolated config\ndirectory from the SDK, pass `env: %{\"CODEX_HOME\" =\u003e \"/path/to/codex_home\"}` in turn options.\n\n## Architecture\n\nThe SDK follows a layered architecture built on OTP principles:\n\n- **`Codex`**: Main entry point for starting and resuming threads\n- **`Codex.Thread`**: Manages individual conversation threads and turn execution\n- **`Codex.Exec`**: GenServer that manages the `codex-rs` OS process via Port\n- **`Codex.Events`**: Comprehensive event type definitions\n- **`Codex.Items`**: Thread item structs (messages, commands, file changes, etc.)\n- **`Codex.Options`**: Configuration structs for all levels\n- **`Codex.Config.Overrides`**: Config override serialization, nested map flattening, and TOML value validation\n- **`Codex.Runtime.Erlexec`**: Unified erlexec startup shared across subprocess modules\n- **`Codex.Runtime.Env`**: Subprocess environment construction (sets `CODEX_INTERNAL_ORIGINATOR_OVERRIDE`)\n- **`Codex.Config.BaseURL`**: Base URL resolution with option → env → default precedence\n- **`Codex.Config.OptionNormalizers`**: Shared reasoning summary, verbosity, and history validation\n- **`Codex.Realtime`**: Bidirectional voice via OpenAI Realtime API (WebSocket)\n- **`Codex.Voice`**: Non-realtime STT → Workflow → TTS pipeline\n- **`Codex.OutputSchemaFile`**: Helper for managing JSON schema temporary files\n\n### Process Model\n\n```\n┌─────────────┐\n│   Client    │\n└──────┬──────┘\n       │\n       ▼\n┌─────────────────┐\n│ Codex.Thread    │  (manages turn state)\n└────────┬────────┘\n         │\n         ▼\n┌──────────────────┐\n│  Codex.Exec      │  (GenServer - manages codex-rs process)\n└────────┬─────────┘\n         │\n         ▼\n┌──────────────────┐\n│   Port (stdin/   │  (IPC with codex-rs via JSONL)\n│    stdout)       │\n└────────┬─────────┘\n         │\n         ▼\n┌──────────────────┐\n│   codex-rs       │  (OpenAI's Codex CLI)\n└──────────────────┘\n```\n\n## Event Types\n\nThe SDK provides structured events for all Codex operations:\n\n### Thread Events\n\n- `ThreadStarted` - New thread initialized with thread_id\n- `TurnStarted` - Agent begins processing a prompt\n- `TurnCompleted` - Turn finished with usage statistics\n- `TurnFailed` - Turn encountered an error\n\n### Session and Control Events\n\n- `SessionConfigured` - Session bootstrap details and initial messages\n- `ContextCompacted` - Compaction summary after auto-compaction\n- `ThreadRolledBack` - Thread rollback summary\n- `RequestUserInput` - Tool-driven user input request\n- `ElicitationRequest` - MCP elicitation request\n- `UndoStarted` / `UndoCompleted` - Undo lifecycle events\n- `EnteredReviewMode` / `ExitedReviewMode` - Review mode lifecycle updates\n- `ConfigWarning` - Config warnings emitted by the server\n\n### Item Events\n\n- `ItemStarted` - New item added to thread\n- `ItemUpdated` - Item state changed\n- `ItemCompleted` - Item reached terminal state\n\n### Item Types\n\n- **`AgentMessage`** - Text or JSON response from the agent\n- **`Reasoning`** - Agent's reasoning summary\n- **`CommandExecution`** - Shell command execution with output\n- **`FileChange`** - File modifications (add, update, delete)\n- **`McpToolCall`** - Model Context Protocol tool invocations\n- **`WebSearch`** - Web search queries and results\n- **`TodoList`** - Agent's running task list\n- **`Error`** - Non-fatal error items\n\n## Examples\n\nSee the `examples/` directory for comprehensive demonstrations. A quick index:\n\n- **`basic_usage.exs`** - First turn, follow-ups, and result inspection\n- **`streaming.exs`** - Real-time turn streaming (progressive and stateful modes)\n- **`structured_output.exs`** - JSON schema enforcement and decoding helpers\n- **`conversation_and_resume.exs`** - Persisting, resuming, and replaying conversations\n- **`concurrency_and_collaboration.exs`** - Multi-turn concurrency patterns\n- **`approval_hook_example.exs`** - Custom approval hook wiring and telemetry inspection\n- **`sandbox_warnings_and_approval_bypass.exs`** - Normalized sandbox warnings and policy-approved bypass demo\n- **`tool_bridging_auto_run.exs`** - Auto-run tool bridging with retries and failure reporting\n- **`live_cli_demo.exs`** - Live CLI walkthrough (uses CLI auth)\n- **`live_collaboration_modes.exs`** - Collaboration mode presets and a live turn\n- **`live_personality.exs`** - Personality overrides (friendly, pragmatic, none)\n- **`live_config_overrides.exs`** - Nested config override auto-flattening (thread and turn level)\n- **`live_options_config_overrides.exs`** - Options-level global config overrides, precedence, and validation\n- **`live_thread_management.exs`** - Thread read/fork/rollback/loaded list workflows\n- **`live_web_search_modes.exs`** - Web search mode toggles with event reporting\n- **`live_rate_limits.exs`** - Rate limit snapshot reporting from token usage events\n- **`live_session_walkthrough.exs`**, **`live_exec_controls.exs`**, **`live_tooling_stream.exs`**, **`live_telemetry_stream.exs`**, **`live_usage_and_compaction.exs`** - Additional live examples that stream, track usage, and show approvals/tooling flows\n- **`live_realtime_voice.exs`** - Full realtime voice interaction demo with event handling\n- **`realtime_basic.exs`**, **`realtime_tools.exs`**, **`realtime_handoffs.exs`** - Realtime API examples for sessions, tools, and agent handoffs\n- **`voice_pipeline.exs`**, **`voice_multi_turn.exs`**, **`voice_with_agent.exs`** - Voice pipeline examples for STT/TTS workflows\n\nRun examples with:\n\n```bash\nmix run examples/basic_usage.exs\n\n# Live CLI example (requires authenticated codex CLI)\nmix run examples/live_cli_demo.exs \"What is the capital of France?\"\n\n# Run all live examples in sequence\n./examples/run_all.sh\n```\n\n\n## Documentation\n\n- **API Reference**: Generated docs available via `mix docs` or on [HexDocs](https://hexdocs.pm/codex_sdk)\n- **Changelog**: [CHANGELOG.md](CHANGELOG.md) summarises release history\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Acknowledgments\n\n- OpenAI team for the Codex CLI and agent technology\n- Elixir community for excellent OTP tooling and libraries\n- [Gemini Ex](https://github.com/nshkrdotcom/gemini_ex) for SDK inspiration\n\n## Related Projects\n\n- **[OpenAI Codex](https://github.com/openai/codex)** - The official Codex CLI\n- **[Codex TypeScript SDK](https://github.com/openai/codex/tree/main/sdk/typescript)** - Official TypeScript SDK\n\n---\n\n\u003cp align=\"center\"\u003eMade with ❤️ and Elixir\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnshkrdotcom%2Fcodex_sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnshkrdotcom%2Fcodex_sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnshkrdotcom%2Fcodex_sdk/lists"}