Skip to main content

codex_cli_sdk/
lib.rs

1//! # codex-cli-sdk
2//!
3//! Rust SDK for the [OpenAI Codex CLI](https://github.com/openai/codex).
4//!
5//! This crate provides a programmatic interface to the Codex CLI by spawning
6//! it as a subprocess and communicating via JSONL. It mirrors the API of the
7//! official [TypeScript SDK](https://www.npmjs.com/package/@openai/codex-sdk).
8//!
9//! ## Quick Start
10//!
11//! ```rust,no_run
12//! use codex_cli_sdk::{Codex, CodexConfig, ThreadOptions};
13//!
14//! #[tokio::main]
15//! async fn main() -> codex_cli_sdk::Result<()> {
16//!     let codex = Codex::new(CodexConfig::default())?;
17//!     let mut thread = codex.start_thread(ThreadOptions::default());
18//!     let turn = thread.run("List the files in this directory", Default::default()).await?;
19//!     println!("{}", turn.final_response);
20//!     Ok(())
21//! }
22//! ```
23//!
24//! ## Streaming
25//!
26//! ```rust,no_run
27//! use codex_cli_sdk::{Codex, CodexConfig, ThreadOptions, ThreadEvent, ThreadItem};
28//! use tokio_stream::StreamExt;
29//!
30//! #[tokio::main]
31//! async fn main() -> codex_cli_sdk::Result<()> {
32//!     let codex = Codex::new(CodexConfig::default())?;
33//!     let mut thread = codex.start_thread(ThreadOptions::default());
34//!     let mut stream = thread.run_streamed("Explain this codebase", Default::default()).await?;
35//!
36//!     while let Some(event) = stream.next().await {
37//!         match event? {
38//!             ThreadEvent::ItemUpdated { item: ThreadItem::AgentMessage { text, .. } } => {
39//!                 print!("{}", text);
40//!             }
41//!             ThreadEvent::ItemStarted { item: ThreadItem::CommandExecution { command, .. } } => {
42//!                 println!("\n> Running: {}", command);
43//!             }
44//!             ThreadEvent::TurnCompleted { usage } => {
45//!                 println!("\n--- done ({} tokens) ---", usage.output_tokens);
46//!             }
47//!             _ => {}
48//!         }
49//!     }
50//!     Ok(())
51//! }
52//! ```
53//!
54//! ## Resuming Threads
55//!
56//! ```rust,no_run
57//! use codex_cli_sdk::{Codex, CodexConfig, ThreadOptions};
58//!
59//! #[tokio::main]
60//! async fn main() -> codex_cli_sdk::Result<()> {
61//!     let codex = Codex::new(CodexConfig::default())?;
62//!
63//!     // First turn
64//!     let mut thread = codex.start_thread(ThreadOptions::default());
65//!     let turn = thread.run("Create a hello.py file", Default::default()).await?;
66//!     let thread_id = thread.id().unwrap();
67//!
68//!     // Resume later
69//!     let mut thread = codex.resume_thread(&thread_id, ThreadOptions::default());
70//!     let turn = thread.run("Now add error handling to hello.py", Default::default()).await?;
71//!     Ok(())
72//! }
73//! ```
74//!
75//! ## Approval Handling
76//!
77//! ```rust,no_run
78//! use codex_cli_sdk::{Codex, CodexConfig, ThreadOptions};
79//! use codex_cli_sdk::permissions::{ApprovalCallback, ApprovalDecision};
80//! use std::sync::Arc;
81//!
82//! #[tokio::main]
83//! async fn main() -> codex_cli_sdk::Result<()> {
84//!     let codex = Codex::new(CodexConfig::default())?;
85//!     let options = ThreadOptions::builder()
86//!         .approval(codex_cli_sdk::config::ApprovalPolicy::OnRequest)
87//!         .build();
88//!
89//!     let callback: ApprovalCallback = Arc::new(|ctx| {
90//!         Box::pin(async move {
91//!             println!("Agent wants to run: {}", ctx.request.command);
92//!             ApprovalDecision::Approved.into()
93//!         })
94//!     });
95//!
96//!     let mut thread = codex.start_thread(options)
97//!         .with_approval_callback(callback);
98//!     let turn = thread.run("Set up a new Rust project", Default::default()).await?;
99//!     Ok(())
100//! }
101//! ```
102
103pub mod callback;
104pub mod client;
105pub mod config;
106pub(crate) mod discovery;
107pub mod errors;
108pub mod hooks;
109pub mod mcp;
110pub mod permissions;
111pub(crate) mod transport;
112pub mod types;
113
114#[cfg(any(test, feature = "testing"))]
115pub mod testing;
116
117// ── Re-exports ─────────────────────────────────────────────────
118
119pub use callback::{EventCallback, apply_callback};
120pub use client::{Codex, Thread};
121
122pub use config::{
123    ApprovalPolicy, CodexConfig, ReasoningEffort, SandboxPolicy, ThreadOptions, TurnOptions,
124    WebSearchMode,
125};
126pub use errors::{Error, Result};
127pub use hooks::{
128    HookCallback, HookContext, HookDecision, HookEvent, HookInput, HookMatcher, HookOutput,
129    HookTimeoutBehavior,
130};
131pub use mcp::{McpServerConfig, McpServers};
132pub use permissions::{
133    ApprovalCallback, ApprovalContext, ApprovalDecision, ApprovalOutcome, PatchApprovalCallback,
134    PatchApprovalContext,
135};
136#[cfg(any(test, feature = "testing"))]
137pub use transport::Transport;
138pub use types::events::{
139    ApprovalRequestEvent, PatchApprovalRequestEvent, StreamedTurn, ThreadError, ThreadEvent, Turn,
140    Usage,
141};
142pub use types::input::{Input, UserInput};
143pub use types::items::ThreadItem;
144
145// ── Top-level convenience functions ────────────────────────────
146
147/// One-shot query: start a thread, run the prompt, return all events as a `Turn`.
148pub async fn query(
149    prompt: impl Into<String>,
150    config: CodexConfig,
151    options: ThreadOptions,
152) -> Result<Turn> {
153    let codex = Codex::new(config)?;
154    let mut thread = codex.start_thread(options);
155    let input = Input::Text(prompt.into());
156    thread.run(input, Default::default()).await
157}
158
159/// Streaming query: start a thread, run the prompt, return event stream.
160pub async fn query_stream(
161    prompt: impl Into<String>,
162    config: CodexConfig,
163    options: ThreadOptions,
164) -> Result<StreamedTurn> {
165    let codex = Codex::new(config)?;
166    let mut thread = codex.start_thread(options);
167    let input = Input::Text(prompt.into());
168    thread.run_streamed(input, Default::default()).await
169}
170
171/// Collect all events from a `StreamedTurn` into a `Vec<ThreadEvent>`.
172pub async fn collect_stream(mut stream: StreamedTurn) -> Result<Vec<ThreadEvent>> {
173    use tokio_stream::StreamExt;
174    let mut events = Vec::new();
175    while let Some(event) = stream.next().await {
176        events.push(event?);
177    }
178    Ok(events)
179}