codex_api/endpoint/
compact.rs1use 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}