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}