1use crate::error::ApiError;
2use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
3use codex_protocol::config_types::Verbosity as VerbosityConfig;
4use codex_protocol::models::ResponseItem;
5use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
6use codex_protocol::protocol::ModelVerification;
7use codex_protocol::protocol::RateLimitSnapshot;
8use codex_protocol::protocol::TokenUsage;
9use codex_protocol::protocol::TurnModerationMetadataEvent;
10use codex_protocol::protocol::W3cTraceContext;
11use futures::Stream;
12use serde::Deserialize;
13use serde::Serialize;
14use serde_json::Value;
15use std::collections::HashMap;
16use std::pin::Pin;
17use std::task::Context;
18use std::task::Poll;
19use tokio::sync::mpsc;
20
21pub const WS_REQUEST_HEADER_TRACEPARENT_CLIENT_METADATA_KEY: &str = "ws_request_header_traceparent";
22pub const WS_REQUEST_HEADER_TRACESTATE_CLIENT_METADATA_KEY: &str = "ws_request_header_tracestate";
23
24#[derive(Debug, Clone, Serialize)]
26pub struct CompactionInput<'a> {
27 pub model: &'a str,
28 pub input: &'a [ResponseItem],
29 #[serde(skip_serializing_if = "str::is_empty")]
30 pub instructions: &'a str,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub tools: Option<Vec<Value>>,
33 pub parallel_tool_calls: bool,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub reasoning: Option<Reasoning>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub service_tier: Option<&'a str>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub prompt_cache_key: Option<&'a str>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub text: Option<TextControls>,
42}
43
44#[derive(Debug, Clone, Serialize)]
46pub struct MemorySummarizeInput {
47 pub model: String,
48 #[serde(rename = "traces")]
49 pub raw_memories: Vec<RawMemory>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub reasoning: Option<Reasoning>,
52}
53
54#[derive(Debug, Clone, Serialize)]
55pub struct RawMemory {
56 pub id: String,
57 pub metadata: RawMemoryMetadata,
58 pub items: Vec<Value>,
59}
60
61#[derive(Debug, Clone, Serialize)]
62pub struct RawMemoryMetadata {
63 pub source_path: String,
64}
65
66#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
67pub struct MemorySummarizeOutput {
68 #[serde(rename = "trace_summary", alias = "raw_memory")]
69 pub raw_memory: String,
70 pub memory_summary: String,
71}
72
73#[derive(Debug)]
74pub enum ResponseEvent {
75 Created,
76 SafetyBuffering(SafetyBuffering),
77 OutputItemDone(ResponseItem),
78 OutputItemAdded(ResponseItem),
79 ServerModel(String),
82 ModelVerifications(Vec<ModelVerification>),
84 TurnModerationMetadata(TurnModerationMetadataEvent),
86 ServerReasoningIncluded(bool),
90 Completed {
91 response_id: String,
92 token_usage: Option<TokenUsage>,
93 end_turn: Option<bool>,
96 },
97 OutputTextDelta(String),
98 ToolCallInputDelta {
99 item_id: String,
100 call_id: Option<String>,
101 delta: String,
102 },
103 ReasoningSummaryDelta {
104 delta: String,
105 summary_index: i64,
106 },
107 ReasoningSummaryDone {
108 item_id: String,
109 text: String,
110 summary_index: i64,
111 },
112 ReasoningContentDelta {
113 delta: String,
114 content_index: i64,
115 },
116 ReasoningSummaryPartAdded {
117 summary_index: i64,
118 },
119 RateLimits(RateLimitSnapshot),
120 ModelsEtag(String),
121}
122
123#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
124pub struct SafetyBuffering {
125 pub use_cases: Vec<String>,
126 pub reasons: Vec<String>,
127 #[serde(skip)]
128 pub show_buffering_ui: bool,
129 #[serde(rename = "retry_model")]
130 pub faster_model: Option<String>,
131}
132
133#[derive(Debug, Clone, Default, PartialEq, Eq)]
134pub(crate) struct SafetyBufferingTreatment {
135 pub faster_model: Option<String>,
136}
137
138#[derive(Debug, Serialize, Clone, PartialEq)]
139#[serde(rename_all = "snake_case")]
140pub enum ReasoningContext {
141 Auto,
142 CurrentTurn,
143 AllTurns,
144}
145
146#[derive(Debug, Serialize, Clone, PartialEq)]
147pub struct Reasoning {
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub effort: Option<ReasoningEffortConfig>,
150 #[serde(skip_serializing_if = "Option::is_none")]
151 pub summary: Option<ReasoningSummaryConfig>,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub context: Option<ReasoningContext>,
154}
155
156#[derive(Debug, Serialize, Clone, PartialEq)]
157#[serde(rename_all = "snake_case")]
158pub enum ReasoningSummaryDelivery {
159 SequentialCutoff,
160}
161
162#[derive(Debug, Serialize, Clone, PartialEq)]
163pub struct StreamOptions {
164 pub reasoning_summary_delivery: ReasoningSummaryDelivery,
165}
166
167#[derive(Debug, Serialize, Default, Clone, PartialEq)]
168#[serde(rename_all = "snake_case")]
169pub enum TextFormatType {
170 #[default]
171 JsonSchema,
172}
173
174#[derive(Debug, Serialize, Default, Clone, PartialEq)]
175pub struct TextFormat {
176 pub r#type: TextFormatType,
178 pub strict: bool,
180 pub schema: Value,
182 pub name: String,
184}
185
186#[derive(Debug, Serialize, Default, Clone, PartialEq)]
189pub struct TextControls {
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub verbosity: Option<OpenAiVerbosity>,
192 #[serde(skip_serializing_if = "Option::is_none")]
193 pub format: Option<TextFormat>,
194}
195
196#[derive(Debug, Serialize, Default, Clone, PartialEq)]
197#[serde(rename_all = "lowercase")]
198pub enum OpenAiVerbosity {
199 Low,
200 #[default]
201 Medium,
202 High,
203}
204
205impl From<VerbosityConfig> for OpenAiVerbosity {
206 fn from(v: VerbosityConfig) -> Self {
207 match v {
208 VerbosityConfig::Low => OpenAiVerbosity::Low,
209 VerbosityConfig::Medium => OpenAiVerbosity::Medium,
210 VerbosityConfig::High => OpenAiVerbosity::High,
211 }
212 }
213}
214
215#[derive(Debug, Serialize, Clone, PartialEq)]
216pub struct ResponsesApiRequest {
217 pub model: String,
218 #[serde(skip_serializing_if = "String::is_empty")]
219 pub instructions: String,
220 pub input: Vec<ResponseItem>,
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub tools: Option<Vec<serde_json::Value>>,
223 pub tool_choice: String,
224 pub parallel_tool_calls: bool,
225 pub reasoning: Option<Reasoning>,
226 pub store: bool,
227 pub stream: bool,
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub stream_options: Option<StreamOptions>,
230 pub include: Vec<String>,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub service_tier: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub prompt_cache_key: Option<String>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub text: Option<TextControls>,
237 #[serde(skip_serializing_if = "Option::is_none")]
238 pub client_metadata: Option<HashMap<String, String>>,
239}
240
241impl<'a> From<&'a ResponsesApiRequest> for ResponseCreateWsRequest<'a> {
242 fn from(request: &'a ResponsesApiRequest) -> Self {
243 Self {
244 model: &request.model,
245 instructions: &request.instructions,
246 previous_response_id: None,
247 input: &request.input,
248 tools: request.tools.as_deref(),
249 tool_choice: &request.tool_choice,
250 parallel_tool_calls: request.parallel_tool_calls,
251 reasoning: request.reasoning.as_ref(),
252 store: request.store,
253 stream: request.stream,
254 stream_options: request.stream_options.as_ref(),
255 include: &request.include,
256 service_tier: request.service_tier.as_deref(),
257 prompt_cache_key: request.prompt_cache_key.as_deref(),
258 text: request.text.as_ref(),
259 generate: None,
260 client_metadata: request.client_metadata.clone(),
261 }
262 }
263}
264
265#[derive(Debug, Serialize)]
266pub struct ResponseCreateWsRequest<'a> {
267 pub model: &'a str,
268 #[serde(skip_serializing_if = "str::is_empty")]
269 pub instructions: &'a str,
270 #[serde(skip_serializing_if = "Option::is_none")]
271 pub previous_response_id: Option<String>,
272 pub input: &'a [ResponseItem],
273 #[serde(skip_serializing_if = "Option::is_none")]
274 pub tools: Option<&'a [Value]>,
275 pub tool_choice: &'a str,
276 pub parallel_tool_calls: bool,
277 pub reasoning: Option<&'a Reasoning>,
278 pub store: bool,
279 pub stream: bool,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 pub stream_options: Option<&'a StreamOptions>,
282 pub include: &'a [String],
283 #[serde(skip_serializing_if = "Option::is_none")]
284 pub service_tier: Option<&'a str>,
285 #[serde(skip_serializing_if = "Option::is_none")]
286 pub prompt_cache_key: Option<&'a str>,
287 #[serde(skip_serializing_if = "Option::is_none")]
288 pub text: Option<&'a TextControls>,
289 #[serde(skip_serializing_if = "Option::is_none")]
290 pub generate: Option<bool>,
291 #[serde(skip_serializing_if = "Option::is_none")]
292 pub client_metadata: Option<HashMap<String, String>>,
293}
294
295pub fn response_create_client_metadata(
296 client_metadata: Option<HashMap<String, String>>,
297 trace: Option<&W3cTraceContext>,
298) -> Option<HashMap<String, String>> {
299 let mut client_metadata = client_metadata.unwrap_or_default();
300
301 if let Some(traceparent) = trace.and_then(|trace| trace.traceparent.as_deref()) {
302 client_metadata.insert(
303 WS_REQUEST_HEADER_TRACEPARENT_CLIENT_METADATA_KEY.to_string(),
304 traceparent.to_string(),
305 );
306 }
307 if let Some(tracestate) = trace.and_then(|trace| trace.tracestate.as_deref()) {
308 client_metadata.insert(
309 WS_REQUEST_HEADER_TRACESTATE_CLIENT_METADATA_KEY.to_string(),
310 tracestate.to_string(),
311 );
312 }
313
314 (!client_metadata.is_empty()).then_some(client_metadata)
315}
316
317#[derive(Debug, Serialize)]
318#[serde(tag = "type")]
319#[allow(clippy::large_enum_variant)]
320pub enum ResponsesWsRequest<'a> {
321 #[serde(rename = "response.create")]
322 ResponseCreate(ResponseCreateWsRequest<'a>),
323}
324
325pub fn create_text_param_for_request(
326 verbosity: Option<VerbosityConfig>,
327 output_schema: &Option<Value>,
328 output_schema_strict: bool,
329) -> Option<TextControls> {
330 if verbosity.is_none() && output_schema.is_none() {
331 return None;
332 }
333
334 Some(TextControls {
335 verbosity: verbosity.map(std::convert::Into::into),
336 format: output_schema.as_ref().map(|schema| TextFormat {
337 r#type: TextFormatType::JsonSchema,
338 strict: output_schema_strict,
339 schema: schema.clone(),
340 name: "codex_output_schema".to_string(),
341 }),
342 })
343}
344
345pub struct ResponseStream {
346 pub rx_event: mpsc::Receiver<Result<ResponseEvent, ApiError>>,
347 pub upstream_request_id: Option<String>,
349}
350
351impl Stream for ResponseStream {
352 type Item = Result<ResponseEvent, ApiError>;
353
354 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
355 self.rx_event.poll_recv(cx)
356 }
357}