diff --git a/codex-rs/api-client/src/chat.rs b/codex-rs/api-client/src/chat.rs index 0efe20a42a..7322081554 100644 --- a/codex-rs/api-client/src/chat.rs +++ b/codex-rs/api-client/src/chat.rs @@ -10,13 +10,10 @@ use futures::TryStreamExt; /// /// - `http_client`: Reqwest client used for HTTP requests. /// - `provider`: Provider configuration (base URL, headers, retries, etc.). -/// - `model`: Model identifier to use. /// - `otel_event_manager`: Telemetry event manager for request/stream instrumentation. -/// - `session_source`: Session metadata, used to set subagent headers when applicable. pub struct ChatCompletionsApiClientConfig { pub http_client: reqwest::Client, pub provider: ModelProviderInfo, - pub model: String, pub otel_event_manager: OtelEventManager, pub extra_headers: Vec<(String, String)>, } @@ -34,7 +31,6 @@ impl ChatCompletionsApiClient { pub async fn stream_payload_wire( &self, payload_json: &serde_json::Value, - _session_source: Option<&codex_protocol::protocol::SessionSource>, ) -> Result { if self.config.provider.wire_api != codex_provider_config::WireApi::Chat { return Err(crate::error::Error::UnsupportedOperation( @@ -42,7 +38,6 @@ impl ChatCompletionsApiClient { )); } - let auth = crate::client::http::resolve_auth(&None).await; let extra_headers: Vec<(&str, String)> = self .config .extra_headers @@ -52,7 +47,7 @@ impl ChatCompletionsApiClient { let mut req_builder = crate::client::http::build_request( &self.config.http_client, &self.config.provider, - &auth, + &None, &extra_headers, ) .await?; diff --git a/codex-rs/api-client/src/client/fixtures.rs b/codex-rs/api-client/src/client/fixtures.rs index a0e4c28488..06192cdfc9 100644 --- a/codex-rs/api-client/src/client/fixtures.rs +++ b/codex-rs/api-client/src/client/fixtures.rs @@ -1,4 +1,3 @@ -use std::io::BufRead; use std::path::Path; use codex_otel::otel_event_manager::OtelEventManager; @@ -17,18 +16,20 @@ pub async fn stream_from_fixture_wire( ) -> Result { let (tx_event, rx_event) = mpsc::channel::>(1600); let display_path = path.as_ref().display().to_string(); - let file = std::fs::File::open(path.as_ref()) - .map_err(|err| Error::Other(format!("failed to open fixture {display_path}: {err}")))?; - let lines = std::io::BufReader::new(file).lines(); - - let mut content = String::new(); - for line in lines { - let line = line - .map_err(|err| Error::Other(format!("failed to read fixture {display_path}: {err}")))?; - content.push_str(&line); - content.push('\n'); - content.push('\n'); - } + let content = std::fs::read_to_string(path.as_ref()).map_err(|err| { + Error::Other(format!( + "failed to read fixture text from {display_path}: {err}" + )) + })?; + let content = content + .lines() + .map(|line| { + let mut line_with_spacing = line.to_string(); + line_with_spacing.push('\n'); + line_with_spacing.push('\n'); + line_with_spacing + }) + .collect::(); let rdr = std::io::Cursor::new(content); let stream = ReaderStream::new(rdr).map_err(|err| Error::Other(err.to_string())); diff --git a/codex-rs/api-client/src/client/rate_limits.rs b/codex-rs/api-client/src/client/rate_limits.rs index f739d28f9f..d4ecec2223 100644 --- a/codex-rs/api-client/src/client/rate_limits.rs +++ b/codex-rs/api-client/src/client/rate_limits.rs @@ -17,6 +17,10 @@ pub fn parse_rate_limit_snapshot(headers: &HeaderMap) -> Option, } +#[derive(Default, Deserialize)] +struct WireUsage { + #[serde(default)] + input_tokens: i64, + #[serde(default)] + cached_input_tokens: Option, + #[serde(default)] + output_tokens: i64, + #[serde(default)] + reasoning_output_tokens: Option, + #[serde(default)] + total_tokens: i64, + #[serde(default)] + input_tokens_details: Option, + #[serde(default)] + output_tokens_details: Option, +} + +#[derive(Default, Deserialize)] +struct WireInputTokensDetails { + #[serde(default)] + cached_tokens: Option, +} + +#[derive(Default, Deserialize)] +struct WireOutputTokensDetails { + #[serde(default)] + reasoning_tokens: Option, +} + pub struct WireResponsesSseDecoder; #[async_trait] @@ -122,45 +152,29 @@ impl WireResponseDecoder for WireResponsesSseDecoder { } fn parse_wire_usage(resp: &Value) -> Option { - let usage = resp.get("usage").cloned()?; - let input_tokens = usage - .get("input_tokens") - .and_then(serde_json::Value::as_i64) - .unwrap_or(0); + let usage: WireUsage = serde_json::from_value(resp.get("usage")?.clone()).ok()?; let cached_input_tokens = usage - .get("cached_input_tokens") - .and_then(serde_json::Value::as_i64) + .cached_input_tokens .or_else(|| { usage - .get("input_tokens_details") - .and_then(|d| d.get("cached_tokens")) - .and_then(serde_json::Value::as_i64) + .input_tokens_details + .and_then(|details| details.cached_tokens) }) .unwrap_or(0); - let output_tokens = usage - .get("output_tokens") - .and_then(serde_json::Value::as_i64) - .unwrap_or(0); let reasoning_output_tokens = usage - .get("reasoning_output_tokens") - .and_then(serde_json::Value::as_i64) + .reasoning_output_tokens .or_else(|| { usage - .get("output_tokens_details") - .and_then(|d| d.get("reasoning_tokens")) - .and_then(serde_json::Value::as_i64) + .output_tokens_details + .and_then(|details| details.reasoning_tokens) }) .unwrap_or(0); - let total_tokens = usage - .get("total_tokens") - .and_then(serde_json::Value::as_i64) - .unwrap_or(0); Some(WireTokenUsage { - input_tokens, + input_tokens: usage.input_tokens, cached_input_tokens, - output_tokens, + output_tokens: usage.output_tokens, reasoning_output_tokens, - total_tokens, + total_tokens: usage.total_tokens, }) } diff --git a/codex-rs/api-client/src/responses.rs b/codex-rs/api-client/src/responses.rs index 84b2354268..d577893d1b 100644 --- a/codex-rs/api-client/src/responses.rs +++ b/codex-rs/api-client/src/responses.rs @@ -20,14 +20,12 @@ use codex_provider_config::ModelProviderInfo; /// /// - `http_client`: Reqwest client used for HTTP requests. /// - `provider`: Provider configuration (base URL, headers, retries, etc.). -/// - `model`: Model identifier to use. /// - `conversation_id`: Used to set conversation/session headers and cache keys. /// - `auth_provider`: Optional provider of auth context (e.g., ChatGPT login token). /// - `otel_event_manager`: Telemetry event manager for request/stream instrumentation. pub struct ResponsesApiClientConfig { pub http_client: reqwest::Client, pub provider: ModelProviderInfo, - pub model: String, pub conversation_id: ConversationId, pub auth_provider: Option>, pub otel_event_manager: OtelEventManager, @@ -46,11 +44,7 @@ impl ResponsesApiClient { } impl ResponsesApiClient { - pub async fn stream_payload_wire( - &self, - payload_json: &Value, - _session_source: Option<&codex_protocol::protocol::SessionSource>, - ) -> Result { + pub async fn stream_payload_wire(&self, payload_json: &Value) -> Result { if self.config.provider.wire_api != codex_provider_config::WireApi::Responses { return Err(Error::UnsupportedOperation( "ResponsesApiClient requires a Responses provider".to_string(), diff --git a/codex-rs/api-client/src/routed_client.rs b/codex-rs/api-client/src/routed_client.rs index c96cec7d08..cd370d15fa 100644 --- a/codex-rs/api-client/src/routed_client.rs +++ b/codex-rs/api-client/src/routed_client.rs @@ -19,7 +19,6 @@ use codex_provider_config::ModelProviderInfo; pub struct RoutedApiClientConfig { pub http_client: reqwest::Client, pub provider: ModelProviderInfo, - pub model: String, pub conversation_id: ConversationId, pub auth_provider: Option>, pub otel_event_manager: OtelEventManager, @@ -46,7 +45,6 @@ impl RoutedApiClient { let cfg = ResponsesApiClientConfig { http_client: self.config.http_client.clone(), provider: self.config.provider.clone(), - model: self.config.model.clone(), conversation_id: self.config.conversation_id, auth_provider: self.config.auth_provider.clone(), otel_event_manager: self.config.otel_event_manager.clone(), @@ -61,18 +59,17 @@ impl RoutedApiClient { .await; } let client = ResponsesApiClient::new(cfg)?; - client.stream_payload_wire(payload_json, None).await + client.stream_payload_wire(payload_json).await } WireApi::Chat => { let cfg = ChatCompletionsApiClientConfig { http_client: self.config.http_client.clone(), provider: self.config.provider.clone(), - model: self.config.model.clone(), otel_event_manager: self.config.otel_event_manager.clone(), extra_headers: self.config.extra_headers.clone(), }; let client = ChatCompletionsApiClient::new(cfg)?; - client.stream_payload_wire(payload_json, None).await + client.stream_payload_wire(payload_json).await } } } diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 99a9ccda5a..204d1bd017 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -192,7 +192,6 @@ impl ModelClient { let config = RoutedApiClientConfig { http_client, provider: self.provider.clone(), - model: self.config.model.clone(), conversation_id: self.conversation_id, auth_provider, otel_event_manager: self.otel_event_manager.clone(),