use crate::error::{Result, SdkError};
use crate::options::{ExecOptions, ResumeOptions};
use crate::transport::ReadOnlyTransport;
use crate::types::Event;
#[derive(Debug, Clone)]
pub struct CodexClient {
pub cli_path: String,
}
impl Default for CodexClient {
fn default() -> Self {
Self::new()
}
}
impl CodexClient {
pub fn new() -> Self {
Self {
cli_path: "codex".to_owned(),
}
}
pub fn with_cli_path(path: impl Into<String>) -> Self {
Self {
cli_path: path.into(),
}
}
pub async fn exec(&self, prompt: &str, opts: ExecOptions) -> Result<Execution> {
let args = opts.to_cli_args();
let transport = ReadOnlyTransport::spawn(
&self.cli_path,
&["exec"],
&args,
Some(prompt),
opts.working_dir.as_deref(),
&opts.env_vars,
)?;
Ok(Execution {
transport,
thread_id: None,
finished: false,
})
}
pub async fn exec_resume(&self, prompt: &str, opts: ResumeOptions) -> Result<Execution> {
let args = opts.to_cli_args();
let transport = ReadOnlyTransport::spawn(
&self.cli_path,
&["exec", "resume"],
&args,
Some(prompt),
opts.working_dir.as_deref(),
&opts.env_vars,
)?;
Ok(Execution {
transport,
thread_id: None,
finished: false,
})
}
}
pub struct Execution {
transport: ReadOnlyTransport,
thread_id: Option<String>,
finished: bool,
}
impl Execution {
pub async fn next_event(&mut self) -> Result<Option<Event>> {
if self.finished {
return Ok(None);
}
loop {
let value = self.transport.recv().await?;
let Some(value) = value else {
self.finished = true;
let (exit_code, stderr) = self.transport.wait_with_stderr().await?;
if exit_code.unwrap_or(0) != 0 {
return Err(SdkError::ProcessDied {
exit_code,
stderr: stderr.unwrap_or_default(),
});
}
return Ok(None);
};
let event: Event = match serde_json::from_value(value.clone()) {
Ok(e) => e,
Err(e) => {
tracing::warn!(
error = %e,
line = %value,
"skipping unrecognized event from codex stdout"
);
continue;
}
};
if let Event::ThreadStarted { ref thread_id } = event {
self.thread_id = Some(thread_id.clone());
}
return Ok(Some(event));
}
}
pub fn thread_id(&self) -> Option<&str> {
self.thread_id.as_deref()
}
pub fn is_finished(&self) -> bool {
self.finished
}
pub fn interrupt(&self) -> Result<()> {
self.transport.interrupt()
}
pub async fn kill(mut self) -> Result<()> {
self.transport.kill().await
}
pub async fn wait(mut self) -> Result<(Option<i32>, Option<String>)> {
self.transport.wait_with_stderr().await
}
}