use crate::error::ApiError; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; use codex_protocol::config_types::Verbosity as VerbosityConfig; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::RateLimitSnapshot; use codex_protocol::protocol::TokenUsage; use futures::Stream; use serde::Deserialize; use serde::Serialize; use serde_json::Value; use std::collections::HashMap; use std::pin::Pin; use std::task::Context; use std::task::Poll; use tokio::sync::mpsc; /// Canonical input payload for the compaction endpoint. #[derive(Debug, Clone, Serialize)] pub struct CompactionInput<'a> { pub model: &'a str, pub input: &'a [ResponseItem], pub instructions: &'a str, } /// Canonical input payload for the memory summarize endpoint. #[derive(Debug, Clone, Serialize)] pub struct MemorySummarizeInput { pub model: String, #[serde(rename = "traces")] pub raw_memories: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub reasoning: Option, } #[derive(Debug, Clone, Serialize)] pub struct RawMemory { pub id: String, pub metadata: RawMemoryMetadata, pub items: Vec, } #[derive(Debug, Clone, Serialize)] pub struct RawMemoryMetadata { pub source_path: String, } #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] pub struct MemorySummarizeOutput { #[serde(rename = "trace_summary", alias = "raw_memory")] pub raw_memory: String, pub memory_summary: String, } #[derive(Debug)] pub enum ResponseEvent { Created, OutputItemDone(ResponseItem), OutputItemAdded(ResponseItem), /// Emitted when the server includes `OpenAI-Model` on the stream response. /// This can differ from the requested model when backend safety routing applies. ServerModel(String), /// Emitted when `X-Reasoning-Included: true` is present on the response, /// meaning the server already accounted for past reasoning tokens and the /// client should not re-estimate them. ServerReasoningIncluded(bool), Completed { response_id: String, token_usage: Option, }, OutputTextDelta(String), ReasoningSummaryDelta { delta: String, summary_index: i64, }, ReasoningContentDelta { delta: String, content_index: i64, }, ReasoningSummaryPartAdded { summary_index: i64, }, RateLimits(RateLimitSnapshot), ModelsEtag(String), } #[derive(Debug, Serialize, Clone, PartialEq)] pub struct Reasoning { #[serde(skip_serializing_if = "Option::is_none")] pub effort: Option, #[serde(skip_serializing_if = "Option::is_none")] pub summary: Option, } #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename_all = "snake_case")] pub enum TextFormatType { #[default] JsonSchema, } #[derive(Debug, Serialize, Default, Clone, PartialEq)] pub struct TextFormat { /// Format type used by the OpenAI text controls. pub r#type: TextFormatType, /// When true, the server is expected to strictly validate responses. pub strict: bool, /// JSON schema for the desired output. pub schema: Value, /// Friendly name for the format, used in telemetry/debugging. pub name: String, } /// Controls the `text` field for the Responses API, combining verbosity and /// optional JSON schema output formatting. #[derive(Debug, Serialize, Default, Clone, PartialEq)] pub struct TextControls { #[serde(skip_serializing_if = "Option::is_none")] pub verbosity: Option, #[serde(skip_serializing_if = "Option::is_none")] pub format: Option, } #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename_all = "lowercase")] pub enum OpenAiVerbosity { Low, #[default] Medium, High, } impl From for OpenAiVerbosity { fn from(v: VerbosityConfig) -> Self { match v { VerbosityConfig::Low => OpenAiVerbosity::Low, VerbosityConfig::Medium => OpenAiVerbosity::Medium, VerbosityConfig::High => OpenAiVerbosity::High, } } } #[derive(Debug, Serialize, Clone, PartialEq)] pub struct ResponsesApiRequest { pub model: String, pub instructions: String, pub input: Vec, pub tools: Vec, pub tool_choice: String, pub parallel_tool_calls: bool, pub reasoning: Option, pub store: bool, pub stream: bool, pub include: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub service_tier: Option, #[serde(skip_serializing_if = "Option::is_none")] pub prompt_cache_key: Option, #[serde(skip_serializing_if = "Option::is_none")] pub text: Option, } impl From<&ResponsesApiRequest> for ResponseCreateWsRequest { fn from(request: &ResponsesApiRequest) -> Self { Self { model: request.model.clone(), instructions: request.instructions.clone(), previous_response_id: None, input: request.input.clone(), tools: request.tools.clone(), tool_choice: request.tool_choice.clone(), parallel_tool_calls: request.parallel_tool_calls, reasoning: request.reasoning.clone(), store: request.store, stream: request.stream, include: request.include.clone(), service_tier: request.service_tier.clone(), prompt_cache_key: request.prompt_cache_key.clone(), text: request.text.clone(), generate: None, client_metadata: None, } } } #[derive(Debug, Serialize)] pub struct ResponseCreateWsRequest { pub model: String, pub instructions: String, #[serde(skip_serializing_if = "Option::is_none")] pub previous_response_id: Option, pub input: Vec, pub tools: Vec, pub tool_choice: String, pub parallel_tool_calls: bool, pub reasoning: Option, pub store: bool, pub stream: bool, pub include: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub service_tier: Option, #[serde(skip_serializing_if = "Option::is_none")] pub prompt_cache_key: Option, #[serde(skip_serializing_if = "Option::is_none")] pub text: Option, #[serde(skip_serializing_if = "Option::is_none")] pub generate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub client_metadata: Option>, } #[derive(Debug, Serialize)] #[serde(tag = "type")] #[allow(clippy::large_enum_variant)] pub enum ResponsesWsRequest { #[serde(rename = "response.create")] ResponseCreate(ResponseCreateWsRequest), } pub fn create_text_param_for_request( verbosity: Option, output_schema: &Option, ) -> Option { if verbosity.is_none() && output_schema.is_none() { return None; } Some(TextControls { verbosity: verbosity.map(std::convert::Into::into), format: output_schema.as_ref().map(|schema| TextFormat { r#type: TextFormatType::JsonSchema, strict: true, schema: schema.clone(), name: "codex_output_schema".to_string(), }), }) } pub struct ResponseStream { pub rx_event: mpsc::Receiver>, } impl Stream for ResponseStream { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.rx_event.poll_recv(cx) } }