codex-api/src/endpoint/compact.rs
116 lines
use crate::auth::SharedAuthProvider;use crate::common::CompactionInput;use crate::endpoint::session::EndpointSession;use crate::error::ApiError;use crate::provider::Provider;use codex_client::HttpTransport;use codex_client::RequestTelemetry;use codex_protocol::models::ResponseItem;use http::HeaderMap;use http::Method;use serde::Deserialize;use serde_json::to_value;use std::sync::Arc;use std::sync::OnceLock;use std::time::Duration;const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state";pub struct CompactClient<T: HttpTransport> { session: EndpointSession<T>,}impl<T: HttpTransport> CompactClient<T> { pub fn new(transport: T, provider: Provider, auth: SharedAuthProvider) -> Self { Self { session: EndpointSession::new(transport, provider, auth), } } pub fn with_telemetry(self, request: Option<Arc<dyn RequestTelemetry>>) -> Self { Self { session: self.session.with_request_telemetry(request), } } fn path() -> &'static str { "responses/compact" } pub async fn compact( &self, body: serde_json::Value, extra_headers: HeaderMap, request_timeout: Duration, turn_state: Option<&OnceLock<String>>, ) -> Result<Vec<ResponseItem>, ApiError> { let resp = self .session .execute_with( Method::POST, Self::path(), extra_headers, Some(body), |req| { req.timeout = Some(request_timeout); }, ) .await?; if let Some(turn_state) = turn_state && let Some(header_value) = resp .headers .get(X_CODEX_TURN_STATE_HEADER) .and_then(|value| value.to_str().ok()) { let _ = turn_state.set(header_value.to_string()); } let parsed: CompactHistoryResponse = serde_json::from_slice(&resp.body).map_err(|e| ApiError::Stream(e.to_string()))?; Ok(parsed.output) } pub async fn compact_input( &self, input: &CompactionInput<'_>, extra_headers: HeaderMap, request_timeout: Duration, turn_state: Option<&OnceLock<String>>, ) -> Result<Vec<ResponseItem>, ApiError> { let body = to_value(input) .map_err(|e| ApiError::Stream(format!("failed to encode compaction input: {e}")))?; self.compact(body, extra_headers, request_timeout, turn_state) .await }}#[derive(Debug, Deserialize)]struct CompactHistoryResponse { output: Vec<ResponseItem>,}#[cfg(test)]mod tests { use super::*; use codex_client::Request; use codex_client::Response; use codex_client::StreamResponse; use codex_client::TransportError; #[derive(Clone, Default)] struct DummyTransport; impl HttpTransport for DummyTransport { async fn execute(&self, _req: Request) -> Result<Response, TransportError> { Err(TransportError::Build("execute should not run".to_string())) } async fn stream(&self, _req: Request) -> Result<StreamResponse, TransportError> { Err(TransportError::Build("stream should not run".to_string())) } } #[test] fn path_is_responses_compact() { assert_eq!(CompactClient::<DummyTransport>::path(), "responses/compact"); }}