Skip to main content

codex_api/endpoint/
compact.rs

1use crate::auth::SharedAuthProvider;
2use crate::common::CompactionInput;
3use crate::endpoint::session::EndpointSession;
4use crate::error::ApiError;
5use crate::provider::Provider;
6use codex_client::HttpTransport;
7use codex_client::RequestTelemetry;
8use codex_protocol::models::ResponseItem;
9use http::HeaderMap;
10use http::Method;
11use serde::Deserialize;
12use std::sync::Arc;
13use std::sync::OnceLock;
14use std::time::Duration;
15
16const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state";
17
18pub struct CompactClient<T: HttpTransport> {
19    session: EndpointSession<T>,
20}
21
22impl<T: HttpTransport> CompactClient<T> {
23    pub fn new(transport: T, provider: Provider, auth: SharedAuthProvider) -> Self {
24        Self {
25            session: EndpointSession::new(transport, provider, auth),
26        }
27    }
28
29    pub fn with_telemetry(self, request: Option<Arc<dyn RequestTelemetry>>) -> Self {
30        Self {
31            session: self.session.with_request_telemetry(request),
32        }
33    }
34
35    fn path() -> &'static str {
36        "responses/compact"
37    }
38
39    pub async fn compact(
40        &self,
41        body: serde_json::Value,
42        extra_headers: HeaderMap,
43        request_timeout: Duration,
44        turn_state: Option<&OnceLock<String>>,
45    ) -> Result<Vec<ResponseItem>, ApiError> {
46        let resp = self
47            .session
48            .execute_with(
49                Method::POST,
50                Self::path(),
51                extra_headers,
52                Some(body),
53                |req| {
54                    req.timeout = Some(request_timeout);
55                },
56            )
57            .await?;
58        if let Some(turn_state) = turn_state
59            && let Some(header_value) = resp
60                .headers
61                .get(X_CODEX_TURN_STATE_HEADER)
62                .and_then(|value| value.to_str().ok())
63        {
64            let _ = turn_state.set(header_value.to_string());
65        }
66        let parsed: CompactHistoryResponse =
67            serde_json::from_slice(&resp.body).map_err(|e| ApiError::Stream(e.to_string()))?;
68        Ok(parsed.output)
69    }
70
71    pub async fn compact_input(
72        &self,
73        input: &CompactionInput<'_>,
74        extra_headers: HeaderMap,
75        request_timeout: Duration,
76        turn_state: Option<&OnceLock<String>>,
77    ) -> Result<Vec<ResponseItem>, ApiError> {
78        let body = serde_json::to_value(input)
79            .map_err(|e| ApiError::Stream(format!("failed to encode compaction input: {e}")))?;
80        self.compact(body, extra_headers, request_timeout, turn_state)
81            .await
82    }
83}
84
85#[derive(Debug, Deserialize)]
86struct CompactHistoryResponse {
87    output: Vec<ResponseItem>,
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use codex_client::Request;
94    use codex_client::Response;
95    use codex_client::StreamResponse;
96    use codex_client::TransportError;
97
98    #[derive(Clone, Default)]
99    struct DummyTransport;
100
101    impl HttpTransport for DummyTransport {
102        async fn execute(&self, _req: Request) -> Result<Response, TransportError> {
103            Err(TransportError::Build("execute should not run".to_string()))
104        }
105
106        async fn stream(&self, _req: Request) -> Result<StreamResponse, TransportError> {
107            Err(TransportError::Build("stream should not run".to_string()))
108        }
109    }
110
111    #[test]
112    fn path_is_responses_compact() {
113        assert_eq!(CompactClient::<DummyTransport>::path(), "responses/compact");
114    }
115}