codex-codes 0.101.0

A tightly typed Rust interface for the OpenAI Codex CLI JSON protocol
Documentation

codex-codes

Crates.io Documentation CI License Downloads

A typed Rust interface for the OpenAI Codex CLI app-server JSON-RPC protocol.

Part of the rust-code-agent-sdks workspace.

Overview

This crate provides type-safe Rust representations of the Codex CLI's JSON-RPC protocol, used by codex app-server. It includes optional sync and async clients for multi-turn conversations with the Codex agent.

Tested against: Codex CLI 0.104.0

Installation

Default (All Features)

cargo add codex-codes

Requires the Codex CLI (codex binary) to be installed and available in PATH.

Feature Flags

Feature Description WASM-compatible
types Core message types only (minimal dependencies) Yes
sync-client Synchronous client with blocking I/O No
async-client Asynchronous client with tokio runtime No

All features are enabled by default.

Types Only (WASM-compatible)

[dependencies]
codex-codes = { version = "0.100", default-features = false, features = ["types"] }

Sync Client Only

[dependencies]
codex-codes = { version = "0.100", default-features = false, features = ["sync-client"] }

Async Client Only

[dependencies]
codex-codes = { version = "0.100", default-features = false, features = ["async-client"] }

Usage

Async Client (Multi-Turn)

use codex_codes::{AsyncClient, ThreadStartParams, TurnStartParams, UserInput, ServerMessage};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = AsyncClient::start().await?;

    // Start a thread
    let thread = client.thread_start(&ThreadStartParams::default()).await?;

    // Send a turn
    client.turn_start(&TurnStartParams {
        thread_id: thread.thread_id().to_string(),
        input: vec![UserInput::Text { text: "What is 2 + 2?".into() }],
        model: None,
        reasoning_effort: None,
        sandbox_policy: None,
    }).await?;

    // Stream notifications
    while let Some(msg) = client.next_message().await? {
        match msg {
            ServerMessage::Notification { method, params } => {
                println!("{}: {:?}", method, params);
                if method == "turn/completed" { break; }
            }
            ServerMessage::Request { id, method, .. } => {
                // Handle approval requests
                client.respond(id, &serde_json::json!({"decision": "accept"})).await?;
            }
        }
    }

    client.shutdown().await?;
    Ok(())
}

Sync Client (Multi-Turn)

use codex_codes::{SyncClient, ThreadStartParams, TurnStartParams, UserInput, ServerMessage};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = SyncClient::start()?;

    let thread = client.thread_start(&ThreadStartParams::default())?;
    client.turn_start(&TurnStartParams {
        thread_id: thread.thread_id().to_string(),
        input: vec![UserInput::Text { text: "What is 2 + 2?".into() }],
        model: None,
        reasoning_effort: None,
        sandbox_policy: None,
    })?;

    for result in client.events() {
        let msg = result?;
        match &msg {
            ServerMessage::Notification { method, .. } => {
                if method == "turn/completed" { break; }
            }
            _ => {}
        }
    }

    Ok(())
}

Raw Protocol Access

use codex_codes::{ThreadItem, JsonRpcMessage, RequestId};

// Parse exec-format JSONL events
let item_json = r#"{"type":"agent_message","id":"msg_1","text":"Hello!"}"#;
let item: ThreadItem = serde_json::from_str(item_json).unwrap();

// Parse app-server JSON-RPC messages
let rpc_json = r#"{"id":1,"result":{"threadId":"th_abc"}}"#;
let msg: JsonRpcMessage = serde_json::from_str(rpc_json).unwrap();

Protocol

The crate supports two protocol modes:

App-Server JSON-RPC (Primary)

The codex app-server --listen stdio:// process speaks a JSON-RPC 2.0 protocol (without the "jsonrpc":"2.0" field) over newline-delimited stdio.

Lifecycle: initialize -> thread/start -> turn/start -> stream notifications -> turn/completed -> next turn/start

Approval flows: The server sends requests back to the client for command execution and file change approvals.

Exec JSONL (Legacy)

The codex exec --json - one-shot protocol emits ThreadEvent JSONL lines. These types are still available for parsing captures.

Types

JSON-RPC (jsonrpc module)

  • RequestId -- String or integer request identifier
  • JsonRpcRequest, JsonRpcResponse, JsonRpcError, JsonRpcNotification
  • JsonRpcMessage -- Untagged union of all message types

Protocol (protocol module)

  • Thread lifecycle: ThreadStartParams/Response, ThreadArchiveParams/Response
  • Turn lifecycle: TurnStartParams/Response, TurnInterruptParams/Response
  • Notifications: TurnCompletedNotification, AgentMessageDeltaNotification, etc.
  • Approvals: CommandExecutionApprovalParams/Response, FileChangeApprovalParams/Response
  • UserInput, Turn, TurnStatus, ServerMessage

Items (ThreadItem)

Discriminated union of agent action items (shared between exec and app-server):

  • agent_message / agentMessage -- Text output from the model
  • reasoning -- Chain-of-thought reasoning
  • command_execution / commandExecution -- Shell command with output
  • file_change / fileChange -- File modifications
  • mcp_tool_call / mcpToolCall -- MCP tool invocation
  • web_search / webSearch -- Web search query
  • todo_list / todoList -- Task tracking list
  • error -- Error item

Events (ThreadEvent) -- Exec Format

  • thread.started, turn.started, turn.completed, turn.failed
  • item.started, item.updated, item.completed
  • error

Compatibility

Tested against: Codex CLI 0.104.0

The crate version tracks the Codex CLI version. If you're using a different CLI version, please report whether it works at: https://github.com/meawoppl/rust-code-agent-sdks/issues

License

Apache-2.0. See LICENSE.