# codex-codes
[](https://crates.io/crates/codex-codes)
[](https://docs.rs/codex-codes)
[](https://github.com/meawoppl/rust-code-agent-sdks/actions/workflows/ci.yml)
[](../LICENSE)
[](https://crates.io/crates/codex-codes)
A typed Rust interface for the [OpenAI Codex CLI](https://github.com/openai/codex) app-server JSON-RPC protocol.
Part of the [rust-code-agent-sdks](https://github.com/meawoppl/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)
```bash
cargo add codex-codes
```
Requires the [Codex CLI](https://github.com/openai/codex) (`codex` binary) to be installed and available in PATH.
### Feature Flags
| `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)
```toml
[dependencies]
codex-codes = { version = "0.100", default-features = false, features = ["types"] }
```
#### Sync Client Only
```toml
[dependencies]
codex-codes = { version = "0.100", default-features = false, features = ["sync-client"] }
```
#### Async Client Only
```toml
[dependencies]
codex-codes = { version = "0.100", default-features = false, features = ["async-client"] }
```
## Usage
### Async Client (Multi-Turn)
```rust
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)
```rust
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
```rust
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](../LICENSE).