Skip to content

Commit e933537

Browse files
authored
feat: add phase 1 mem client (#10629)
Adding a client on top of openai/openai#672176
1 parent 71e63f8 commit e933537

File tree

8 files changed

+498
-4
lines changed

8 files changed

+498
-4
lines changed

codex-rs/codex-api/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,13 @@ The public interface of this crate is intentionally small and uniform:
2929
- Output: `Vec<ResponseItem>`.
3030
- `CompactClient::compact_input(&CompactionInput, extra_headers)` wraps the JSON encoding and retry/telemetry wiring.
3131

32+
- **Memory trace summarize endpoint**
33+
- Input: `MemoryTraceSummarizeInput` (re-exported as `codex_api::MemoryTraceSummarizeInput`):
34+
- `model: String`.
35+
- `traces: Vec<MemoryTrace>`.
36+
- `MemoryTrace` includes `id`, `metadata.source_path`, and normalized `items`.
37+
- `reasoning: Option<Reasoning>`.
38+
- Output: `Vec<MemoryTraceSummaryOutput>`.
39+
- `MemoriesClient::trace_summarize_input(&MemoryTraceSummarizeInput, extra_headers)` wraps JSON encoding and retry/telemetry wiring.
40+
3241
All HTTP details (URLs, headers, retry/backoff policies, SSE framing) are encapsulated in `codex-api` and `codex-client`. Callers construct prompts/inputs using protocol types and work with typed streams of `ResponseEvent` or compacted `ResponseItem` values.

codex-rs/codex-api/src/common.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
66
use codex_protocol::protocol::RateLimitSnapshot;
77
use codex_protocol::protocol::TokenUsage;
88
use futures::Stream;
9+
use serde::Deserialize;
910
use serde::Serialize;
1011
use serde_json::Value;
1112
use std::pin::Pin;
@@ -37,6 +38,33 @@ pub struct CompactionInput<'a> {
3738
pub instructions: &'a str,
3839
}
3940

41+
/// Canonical input payload for the memory trace summarize endpoint.
42+
#[derive(Debug, Clone, Serialize)]
43+
pub struct MemoryTraceSummarizeInput {
44+
pub model: String,
45+
pub traces: Vec<MemoryTrace>,
46+
#[serde(skip_serializing_if = "Option::is_none")]
47+
pub reasoning: Option<Reasoning>,
48+
}
49+
50+
#[derive(Debug, Clone, Serialize)]
51+
pub struct MemoryTrace {
52+
pub id: String,
53+
pub metadata: MemoryTraceMetadata,
54+
pub items: Vec<Value>,
55+
}
56+
57+
#[derive(Debug, Clone, Serialize)]
58+
pub struct MemoryTraceMetadata {
59+
pub source_path: String,
60+
}
61+
62+
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
63+
pub struct MemoryTraceSummaryOutput {
64+
pub trace_summary: String,
65+
pub memory_summary: String,
66+
}
67+
4068
#[derive(Debug)]
4169
pub enum ResponseEvent {
4270
Created,
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use crate::auth::AuthProvider;
2+
use crate::common::MemoryTraceSummarizeInput;
3+
use crate::common::MemoryTraceSummaryOutput;
4+
use crate::endpoint::session::EndpointSession;
5+
use crate::error::ApiError;
6+
use crate::provider::Provider;
7+
use codex_client::HttpTransport;
8+
use codex_client::RequestTelemetry;
9+
use http::HeaderMap;
10+
use http::Method;
11+
use serde::Deserialize;
12+
use serde_json::to_value;
13+
use std::sync::Arc;
14+
15+
pub struct MemoriesClient<T: HttpTransport, A: AuthProvider> {
16+
session: EndpointSession<T, A>,
17+
}
18+
19+
impl<T: HttpTransport, A: AuthProvider> MemoriesClient<T, A> {
20+
pub fn new(transport: T, provider: Provider, auth: A) -> Self {
21+
Self {
22+
session: EndpointSession::new(transport, provider, auth),
23+
}
24+
}
25+
26+
pub fn with_telemetry(self, request: Option<Arc<dyn RequestTelemetry>>) -> Self {
27+
Self {
28+
session: self.session.with_request_telemetry(request),
29+
}
30+
}
31+
32+
fn path() -> &'static str {
33+
"memories/trace_summarize"
34+
}
35+
36+
pub async fn trace_summarize(
37+
&self,
38+
body: serde_json::Value,
39+
extra_headers: HeaderMap,
40+
) -> Result<Vec<MemoryTraceSummaryOutput>, ApiError> {
41+
let resp = self
42+
.session
43+
.execute(Method::POST, Self::path(), extra_headers, Some(body))
44+
.await?;
45+
let parsed: TraceSummarizeResponse =
46+
serde_json::from_slice(&resp.body).map_err(|e| ApiError::Stream(e.to_string()))?;
47+
Ok(parsed.output)
48+
}
49+
50+
pub async fn trace_summarize_input(
51+
&self,
52+
input: &MemoryTraceSummarizeInput,
53+
extra_headers: HeaderMap,
54+
) -> Result<Vec<MemoryTraceSummaryOutput>, ApiError> {
55+
let body = to_value(input).map_err(|e| {
56+
ApiError::Stream(format!(
57+
"failed to encode memory trace summarize input: {e}"
58+
))
59+
})?;
60+
self.trace_summarize(body, extra_headers).await
61+
}
62+
}
63+
64+
#[derive(Debug, Deserialize)]
65+
struct TraceSummarizeResponse {
66+
output: Vec<MemoryTraceSummaryOutput>,
67+
}
68+
69+
#[cfg(test)]
70+
mod tests {
71+
use super::*;
72+
use async_trait::async_trait;
73+
use codex_client::Request;
74+
use codex_client::Response;
75+
use codex_client::StreamResponse;
76+
use codex_client::TransportError;
77+
78+
#[derive(Clone, Default)]
79+
struct DummyTransport;
80+
81+
#[async_trait]
82+
impl HttpTransport for DummyTransport {
83+
async fn execute(&self, _req: Request) -> Result<Response, TransportError> {
84+
Err(TransportError::Build("execute should not run".to_string()))
85+
}
86+
87+
async fn stream(&self, _req: Request) -> Result<StreamResponse, TransportError> {
88+
Err(TransportError::Build("stream should not run".to_string()))
89+
}
90+
}
91+
92+
#[derive(Clone, Default)]
93+
struct DummyAuth;
94+
95+
impl AuthProvider for DummyAuth {
96+
fn bearer_token(&self) -> Option<String> {
97+
None
98+
}
99+
}
100+
101+
#[test]
102+
fn path_is_memories_trace_summarize() {
103+
assert_eq!(
104+
MemoriesClient::<DummyTransport, DummyAuth>::path(),
105+
"memories/trace_summarize"
106+
);
107+
}
108+
}

codex-rs/codex-api/src/endpoint/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod aggregate;
22
pub mod compact;
3+
pub mod memories;
34
pub mod models;
45
pub mod responses;
56
pub mod responses_websocket;

codex-rs/codex-api/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ pub use codex_client::TransportError;
1515

1616
pub use crate::auth::AuthProvider;
1717
pub use crate::common::CompactionInput;
18+
pub use crate::common::MemoryTrace;
19+
pub use crate::common::MemoryTraceMetadata;
20+
pub use crate::common::MemoryTraceSummarizeInput;
21+
pub use crate::common::MemoryTraceSummaryOutput;
1822
pub use crate::common::Prompt;
1923
pub use crate::common::ResponseAppendWsRequest;
2024
pub use crate::common::ResponseCreateWsRequest;
@@ -24,6 +28,7 @@ pub use crate::common::ResponsesApiRequest;
2428
pub use crate::common::create_text_param_for_request;
2529
pub use crate::endpoint::aggregate::AggregateStreamExt;
2630
pub use crate::endpoint::compact::CompactClient;
31+
pub use crate::endpoint::memories::MemoriesClient;
2732
pub use crate::endpoint::models::ModelsClient;
2833
pub use crate::endpoint::responses::ResponsesClient;
2934
pub use crate::endpoint::responses::ResponsesOptions;

codex-rs/core/src/client.rs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ use crate::api_bridge::map_api_error;
77
use crate::auth::UnauthorizedRecovery;
88
use codex_api::CompactClient as ApiCompactClient;
99
use codex_api::CompactionInput as ApiCompactionInput;
10+
use codex_api::MemoriesClient as ApiMemoriesClient;
11+
use codex_api::MemoryTrace as ApiMemoryTrace;
12+
use codex_api::MemoryTraceSummarizeInput as ApiMemoryTraceSummarizeInput;
13+
use codex_api::MemoryTraceSummaryOutput as ApiMemoryTraceSummaryOutput;
1014
use codex_api::Prompt as ApiPrompt;
1115
use codex_api::RequestTelemetry;
1216
use codex_api::ReqwestTransport;
@@ -183,6 +187,55 @@ impl ModelClient {
183187
instructions: &instructions,
184188
};
185189

190+
let extra_headers = self.build_subagent_headers();
191+
client
192+
.compact_input(&payload, extra_headers)
193+
.await
194+
.map_err(map_api_error)
195+
}
196+
197+
/// Builds memory summaries for each provided normalized trace.
198+
///
199+
/// This is a unary call (no streaming) to `/v1/memories/trace_summarize`.
200+
pub async fn summarize_memory_traces(
201+
&self,
202+
traces: Vec<ApiMemoryTrace>,
203+
) -> Result<Vec<ApiMemoryTraceSummaryOutput>> {
204+
if traces.is_empty() {
205+
return Ok(Vec::new());
206+
}
207+
208+
let auth_manager = self.state.auth_manager.clone();
209+
let auth = match auth_manager.as_ref() {
210+
Some(manager) => manager.auth().await,
211+
None => None,
212+
};
213+
let api_provider = self
214+
.state
215+
.provider
216+
.to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?;
217+
let api_auth = auth_provider_from_auth(auth, &self.state.provider)?;
218+
let transport = ReqwestTransport::new(build_reqwest_client());
219+
let request_telemetry = self.build_request_telemetry();
220+
let client = ApiMemoriesClient::new(transport, api_provider, api_auth)
221+
.with_telemetry(Some(request_telemetry));
222+
223+
let payload = ApiMemoryTraceSummarizeInput {
224+
model: self.state.model_info.slug.clone(),
225+
traces,
226+
reasoning: self.state.effort.map(|effort| Reasoning {
227+
effort: Some(effort),
228+
summary: None,
229+
}),
230+
};
231+
232+
client
233+
.trace_summarize_input(&payload, self.build_subagent_headers())
234+
.await
235+
.map_err(map_api_error)
236+
}
237+
238+
fn build_subagent_headers(&self) -> ApiHeaderMap {
186239
let mut extra_headers = ApiHeaderMap::new();
187240
if let SessionSource::SubAgent(sub) = &self.state.session_source {
188241
let subagent = match sub {
@@ -195,10 +248,7 @@ impl ModelClient {
195248
extra_headers.insert("x-openai-subagent", val);
196249
}
197250
}
198-
client
199-
.compact_input(&payload, extra_headers)
200-
.await
201-
.map_err(map_api_error)
251+
extra_headers
202252
}
203253
}
204254

codex-rs/core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,5 @@ pub use codex_protocol::models::ResponseItem;
161161
pub use compact::content_items_to_text;
162162
pub use event_mapping::parse_turn_item;
163163
pub mod compact;
164+
pub mod memory_trace;
164165
pub mod otel_init;

0 commit comments

Comments
 (0)