Codex Handbook
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");    }}