Skip to main content

codex_codes/
lib.rs

1//! A typed Rust interface for the [OpenAI Codex CLI](https://github.com/openai/codex) protocol.
2//!
3//! This crate provides type-safe bindings for communicating with the Codex CLI's
4//! app-server via its JSON-RPC protocol. It handles message framing, request/response
5//! correlation, approval flows, and streaming notifications for multi-turn agent
6//! conversations.
7//!
8//! # Quick Start
9//!
10//! ```bash
11//! cargo add codex-codes
12//! ```
13//!
14//! ## Using the Async Client (Recommended)
15//!
16//! ```ignore
17//! use codex_codes::{AsyncClient, ThreadStartParams, TurnStartParams, UserInput, ServerMessage};
18//!
19//! #[tokio::main]
20//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
21//!     // Start the app-server (spawns `codex app-server --listen stdio://`)
22//!     let mut client = AsyncClient::start().await?;
23//!
24//!     // Create a thread (a conversation session)
25//!     let thread = client.thread_start(&ThreadStartParams::default()).await?;
26//!
27//!     // Send a turn (a user message that triggers an agent response)
28//!     client.turn_start(&TurnStartParams {
29//!         thread_id: thread.thread_id().to_string(),
30//!         input: vec![UserInput::Text { text: "What is 2 + 2?".into() }],
31//!         model: None,
32//!         reasoning_effort: None,
33//!         sandbox_policy: None,
34//!     }).await?;
35//!
36//!     // Stream notifications until the turn completes
37//!     while let Some(msg) = client.next_message().await? {
38//!         match msg {
39//!             ServerMessage::Notification { method, params } => {
40//!                 if method == "turn/completed" { break; }
41//!             }
42//!             ServerMessage::Request { id, .. } => {
43//!                 // Approval request — auto-accept for this example
44//!                 client.respond(id, &serde_json::json!({"decision": "accept"})).await?;
45//!             }
46//!         }
47//!     }
48//!
49//!     client.shutdown().await?;
50//!     Ok(())
51//! }
52//! ```
53//!
54//! ## Using the Sync Client
55//!
56//! ```ignore
57//! use codex_codes::{SyncClient, ThreadStartParams, TurnStartParams, UserInput, ServerMessage};
58//!
59//! fn main() -> Result<(), Box<dyn std::error::Error>> {
60//!     let mut client = SyncClient::start()?;
61//!     let thread = client.thread_start(&ThreadStartParams::default())?;
62//!
63//!     client.turn_start(&TurnStartParams {
64//!         thread_id: thread.thread_id().to_string(),
65//!         input: vec![UserInput::Text { text: "What is 2 + 2?".into() }],
66//!         model: None,
67//!         reasoning_effort: None,
68//!         sandbox_policy: None,
69//!     })?;
70//!
71//!     for result in client.events() {
72//!         match result? {
73//!             ServerMessage::Notification { method, .. } => {
74//!                 if method == "turn/completed" { break; }
75//!             }
76//!             _ => {}
77//!         }
78//!     }
79//!     Ok(())
80//! }
81//! ```
82//!
83//! # Architecture
84//!
85//! The crate is organized into several key modules:
86//!
87//! - [`client_async`] / [`client_sync`] — High-level clients that manage the
88//!   app-server process, request/response correlation, and message buffering
89//! - [`protocol`] — App-server v2 request params, response types, and notification
90//!   bodies (thread/turn lifecycle, approvals, deltas)
91//! - [`jsonrpc`] — Low-level JSON-RPC message types (request, response, error,
92//!   notification) matching the app-server's wire format
93//! - [`cli`] — Builder for spawning `codex app-server --listen stdio://`
94//! - [`error`] — Error types and result aliases
95//! - [`version`] — Version compatibility checking against the installed CLI
96//!
97//! # Protocol Overview
98//!
99//! The Codex app-server communicates via newline-delimited JSON-RPC 2.0 over stdio
100//! (without the standard `"jsonrpc":"2.0"` field). The conversation lifecycle is:
101//!
102//! 1. **Initialize** — `initialize` + `initialized` handshake (handled automatically by `start()`)
103//! 2. **Start a thread** — `thread/start` creates a conversation session
104//! 3. **Start a turn** — `turn/start` sends user input, triggering agent work
105//! 4. **Stream notifications** — The server emits `item/agentMessage/delta`,
106//!    `item/commandExecution/outputDelta`, etc. as the agent works
107//! 5. **Handle approvals** — The server may send requests like
108//!    `item/commandExecution/requestApproval` that require a response
109//! 6. **Turn completes** — `turn/completed` signals the agent is done
110//! 7. **Repeat** — Send another `turn/start` for follow-up questions
111//!
112//! # Feature Flags
113//!
114//! | Feature | Description | WASM-compatible |
115//! |---------|-------------|-----------------|
116//! | `types` | Core message types and protocol structs only | Yes |
117//! | `sync-client` | Synchronous client with blocking I/O | No |
118//! | `async-client` | Asynchronous client using tokio | No |
119//!
120//! All features are enabled by default. For WASM or type-sharing use cases:
121//!
122//! ```toml
123//! [dependencies]
124//! codex-codes = { version = "0.128", default-features = false, features = ["types"] }
125//! ```
126//!
127//! # Version Compatibility
128//!
129//! The Codex CLI protocol is evolving. This crate automatically checks your
130//! installed CLI version and warns if it's newer than tested. Current tested
131//! version: **0.130.0**
132//!
133//! Report compatibility issues at: <https://github.com/meawoppl/rust-code-agent-sdks/issues>
134//!
135//! # Examples
136//!
137//! See the `examples/` directory for complete working examples:
138//! - `async_client.rs` — Single-turn async query with streaming deltas
139//! - `sync_client.rs` — Single-turn synchronous query
140//! - `basic_repl.rs` — Interactive REPL with multi-turn conversation and approval handling
141//!
142//! # Parsing Raw Protocol Messages
143//!
144//! ```
145//! use codex_codes::{ThreadEvent, ThreadItem, JsonRpcMessage};
146//!
147//! // Parse exec-format JSONL events
148//! let json = r#"{"type":"thread.started","thread_id":"th_abc"}"#;
149//! let event: ThreadEvent = serde_json::from_str(json).unwrap();
150//!
151//! // Parse app-server JSON-RPC messages
152//! let rpc = r#"{"id":1,"result":{"threadId":"th_abc"}}"#;
153//! let msg: JsonRpcMessage = serde_json::from_str(rpc).unwrap();
154//! ```
155
156mod io;
157
158pub mod error;
159pub mod jsonrpc;
160pub mod messages;
161pub mod protocol;
162pub mod protocol_generated;
163
164#[cfg(any(feature = "sync-client", feature = "async-client"))]
165pub mod cli;
166
167#[cfg(any(feature = "sync-client", feature = "async-client"))]
168pub mod version;
169
170#[cfg(any(feature = "sync-client", feature = "async-client"))]
171mod stderr_drain;
172
173#[cfg(feature = "sync-client")]
174pub mod client_sync;
175
176#[cfg(feature = "async-client")]
177pub mod client_async;
178
179// Exec-level event types (JSONL protocol)
180pub use io::events::{
181    ItemCompletedEvent, ItemStartedEvent, ItemUpdatedEvent, ThreadError, ThreadErrorEvent,
182    ThreadEvent, ThreadStartedEvent, TurnCompletedEvent, TurnFailedEvent, TurnStartedEvent, Usage,
183};
184
185// Thread item types (shared between exec and app-server)
186pub use io::items::{
187    AgentMessageItem, CommandExecutionItem, CommandExecutionStatus, ErrorItem, FileChangeItem,
188    FileUpdateChange, McpToolCallError, McpToolCallItem, McpToolCallResult, McpToolCallStatus,
189    PatchApplyStatus, PatchChangeKind, ReasoningItem, ThreadItem, TodoItem, TodoListItem,
190    WebSearchItem,
191};
192
193// Configuration types
194pub use io::options::{
195    ApprovalMode, ModelReasoningEffort, SandboxMode, ThreadOptions, WebSearchMode,
196};
197
198// Error types (always available)
199pub use error::{Error, ParseError, Result};
200
201// JSON-RPC types (always available)
202pub use jsonrpc::{
203    JsonRpcError, JsonRpcErrorData, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest,
204    JsonRpcResponse, RequestId,
205};
206
207// App-server protocol types (always available)
208pub use protocol::{
209    AccountLoginCompletedNotification, AccountRateLimitsUpdatedNotification,
210    AccountUpdatedNotification, AgentMessageDeltaNotification, AppListUpdatedNotification,
211    ClientInfo, CmdOutputDeltaNotification, CommandApprovalDecision,
212    CommandExecOutputDeltaNotification, CommandExecutionApprovalParams,
213    CommandExecutionApprovalResponse, ConfigWarningNotification, ContextCompactedNotification,
214    DeprecationNoticeNotification, ErrorNotification,
215    ExternalAgentConfigImportCompletedNotification, FileChangeApprovalDecision,
216    FileChangeApprovalParams, FileChangeApprovalResponse, FileChangeOutputDeltaNotification,
217    FileChangePatchUpdatedNotification, FsChangedNotification,
218    FuzzyFileSearchSessionCompletedNotification, FuzzyFileSearchSessionUpdatedNotification,
219    GuardianWarningNotification, HookCompletedNotification, HookStartedNotification,
220    InitializeCapabilities, InitializeParams, InitializeResponse, ItemCompletedNotification,
221    ItemGuardianApprovalReviewCompletedNotification, ItemGuardianApprovalReviewStartedNotification,
222    ItemStartedNotification, McpServerOauthLoginCompletedNotification,
223    McpServerStartupStatusUpdatedNotification, McpToolCallProgressNotification,
224    ModelReroutedNotification, ModelVerificationNotification, PlanDeltaNotification,
225    ProcessExitedNotification, ProcessOutputDeltaNotification, RateLimitWindow, RateLimits,
226    ReasoningDeltaNotification, ReasoningSummaryPartAddedNotification,
227    ReasoningTextDeltaNotification, RemoteControlStatusChangedNotification,
228    ServerRequestResolvedNotification, SkillsChangedNotification, TerminalInteractionNotification,
229    ThreadArchiveParams, ThreadArchiveResponse, ThreadArchivedNotification,
230    ThreadClosedNotification, ThreadGoalClearedNotification, ThreadGoalUpdatedNotification,
231    ThreadInfo, ThreadNameUpdatedNotification, ThreadRealtimeClosedNotification,
232    ThreadRealtimeErrorNotification, ThreadRealtimeItemAddedNotification,
233    ThreadRealtimeOutputAudioDeltaNotification, ThreadRealtimeSdpNotification,
234    ThreadRealtimeStartedNotification, ThreadRealtimeTranscriptDeltaNotification,
235    ThreadRealtimeTranscriptDoneNotification, ThreadStartParams, ThreadStartResponse,
236    ThreadStartedNotification, ThreadStatus, ThreadStatusChangedNotification,
237    ThreadTokenUsageUpdatedNotification, ThreadUnarchivedNotification, TokenCounts, TokenUsage,
238    Turn, TurnCompletedNotification, TurnDiffUpdatedNotification, TurnError, TurnInterruptParams,
239    TurnInterruptResponse, TurnPlanStep, TurnPlanStepStatus, TurnPlanUpdatedNotification,
240    TurnStartParams, TurnStartResponse, TurnStartedNotification, TurnStatus, UserInput,
241    WarningNotification, WindowsSandboxSetupCompletedNotification,
242    WindowsWorldWritableWarningNotification,
243};
244
245// Also re-export the `UserMessageItem` and its content block so callers can
246// pattern-match on the new ThreadItem variant added in 0.102.
247pub use io::items::{UserMessageContent, UserMessageItem};
248
249// Typed message dispatch (notifications + server-to-client requests)
250pub use messages::{Notification, ServerMessage, ServerRequest};
251
252// CLI builder (feature-gated)
253#[cfg(any(feature = "sync-client", feature = "async-client"))]
254pub use cli::AppServerBuilder;
255
256// Sync client
257#[cfg(feature = "sync-client")]
258pub use client_sync::{EventIterator, SyncClient};
259
260// Async client
261#[cfg(feature = "async-client")]
262pub use client_async::{AsyncClient, EventStream};