mirror of
https://github.com/openai/codex.git
synced 2026-11-15 21:50:46 +00:00
Use responses request helpers for compact requests
Make /responses/compact reuse the shared responses request construction and serialization path, then adapt only the fields rejected by the compact endpoint. Preserve compact-specific headers and add remote compact parity coverage across auth modes. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -20,21 +20,6 @@ use tokio::sync::mpsc;
|
||||
pub const WS_REQUEST_HEADER_TRACEPARENT_CLIENT_METADATA_KEY: &str = "ws_request_header_traceparent";
|
||||
pub const WS_REQUEST_HEADER_TRACESTATE_CLIENT_METADATA_KEY: &str = "ws_request_header_tracestate";
|
||||
|
||||
/// Canonical input payload for the compaction endpoint.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct CompactionInput<'a> {
|
||||
pub model: &'a str,
|
||||
pub input: &'a [ResponseItem],
|
||||
#[serde(skip_serializing_if = "str::is_empty")]
|
||||
pub instructions: &'a str,
|
||||
pub tools: Vec<Value>,
|
||||
pub parallel_tool_calls: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reasoning: Option<Reasoning>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text: Option<TextControls>,
|
||||
}
|
||||
|
||||
/// Canonical input payload for the memory summarize endpoint.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct MemorySummarizeInput {
|
||||
@@ -162,6 +147,11 @@ impl From<VerbosityConfig> for OpenAiVerbosity {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared request body for `/responses`-style calls.
|
||||
///
|
||||
/// Normal sampling sends every populated field. `/responses/compact` starts from this same shape
|
||||
/// and then clears the fields that endpoint does not accept, so new request-body fields naturally
|
||||
/// stay visible to both paths unless compact explicitly opts out.
|
||||
#[derive(Debug, Serialize, Clone, PartialEq)]
|
||||
pub struct ResponsesApiRequest {
|
||||
pub model: String,
|
||||
@@ -169,12 +159,14 @@ pub struct ResponsesApiRequest {
|
||||
pub instructions: String,
|
||||
pub input: Vec<ResponseItem>,
|
||||
pub tools: Vec<serde_json::Value>,
|
||||
pub tool_choice: String,
|
||||
pub parallel_tool_calls: bool,
|
||||
pub reasoning: Option<Reasoning>,
|
||||
pub store: bool,
|
||||
pub stream: bool,
|
||||
pub include: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub store: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stream: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub include: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub service_tier: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -193,7 +185,6 @@ impl From<&ResponsesApiRequest> for ResponseCreateWsRequest {
|
||||
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,
|
||||
@@ -217,12 +208,14 @@ pub struct ResponseCreateWsRequest {
|
||||
pub previous_response_id: Option<String>,
|
||||
pub input: Vec<ResponseItem>,
|
||||
pub tools: Vec<Value>,
|
||||
pub tool_choice: String,
|
||||
pub parallel_tool_calls: bool,
|
||||
pub reasoning: Option<Reasoning>,
|
||||
pub store: bool,
|
||||
pub stream: bool,
|
||||
pub include: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub store: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stream: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub include: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub service_tier: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use crate::auth::SharedAuthProvider;
|
||||
use crate::common::CompactionInput;
|
||||
use crate::common::ResponsesApiRequest;
|
||||
use crate::endpoint::ResponsesOptions;
|
||||
use crate::endpoint::session::EndpointSession;
|
||||
use crate::error::ApiError;
|
||||
use crate::provider::Provider;
|
||||
use crate::requests::responses::build_responses_request_body;
|
||||
use crate::requests::responses::build_responses_request_headers;
|
||||
use codex_client::HttpTransport;
|
||||
use codex_client::RequestTelemetry;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use http::HeaderMap;
|
||||
use http::Method;
|
||||
use serde::Deserialize;
|
||||
use serde_json::to_value;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct CompactClient<T: HttpTransport> {
|
||||
@@ -47,14 +49,21 @@ impl<T: HttpTransport> CompactClient<T> {
|
||||
Ok(parsed.output)
|
||||
}
|
||||
|
||||
pub async fn compact_input(
|
||||
pub async fn compact_request(
|
||||
&self,
|
||||
input: &CompactionInput<'_>,
|
||||
extra_headers: HeaderMap,
|
||||
request: ResponsesApiRequest,
|
||||
options: ResponsesOptions,
|
||||
) -> Result<Vec<ResponseItem>, ApiError> {
|
||||
let body = to_value(input)
|
||||
.map_err(|e| ApiError::Stream(format!("failed to encode compaction input: {e}")))?;
|
||||
self.compact(body, extra_headers).await
|
||||
let ResponsesOptions {
|
||||
conversation_id,
|
||||
session_source,
|
||||
extra_headers,
|
||||
..
|
||||
} = options;
|
||||
let body = build_responses_request_body(&request, self.session.provider())?;
|
||||
let headers =
|
||||
build_responses_request_headers(extra_headers, conversation_id, session_source);
|
||||
self.compact(body, headers).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,8 @@ use crate::endpoint::session::EndpointSession;
|
||||
use crate::error::ApiError;
|
||||
use crate::provider::Provider;
|
||||
use crate::requests::Compression;
|
||||
use crate::requests::attach_item_ids;
|
||||
use crate::requests::headers::build_conversation_headers;
|
||||
use crate::requests::headers::insert_header;
|
||||
use crate::requests::headers::subagent_header;
|
||||
use crate::requests::responses::build_responses_request_body;
|
||||
use crate::requests::responses::build_responses_request_headers;
|
||||
use crate::sse::spawn_response_stream;
|
||||
use crate::telemetry::SseTelemetry;
|
||||
use codex_client::HttpTransport;
|
||||
@@ -28,7 +26,7 @@ pub struct ResponsesClient<T: HttpTransport> {
|
||||
sse_telemetry: Option<Arc<dyn SseTelemetry>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ResponsesOptions {
|
||||
pub conversation_id: Option<String>,
|
||||
pub session_source: Option<SessionSource>,
|
||||
@@ -79,20 +77,9 @@ impl<T: HttpTransport> ResponsesClient<T> {
|
||||
turn_state,
|
||||
} = options;
|
||||
|
||||
let mut body = serde_json::to_value(&request)
|
||||
.map_err(|e| ApiError::Stream(format!("failed to encode responses request: {e}")))?;
|
||||
if request.store && self.session.provider().is_azure_responses_endpoint() {
|
||||
attach_item_ids(&mut body, &request.input);
|
||||
}
|
||||
|
||||
let mut headers = extra_headers;
|
||||
if let Some(ref conv_id) = conversation_id {
|
||||
insert_header(&mut headers, "x-client-request-id", conv_id);
|
||||
}
|
||||
headers.extend(build_conversation_headers(conversation_id));
|
||||
if let Some(subagent) = subagent_header(&session_source) {
|
||||
insert_header(&mut headers, "x-openai-subagent", &subagent);
|
||||
}
|
||||
let body = build_responses_request_body(&request, self.session.provider())?;
|
||||
let headers =
|
||||
build_responses_request_headers(extra_headers, conversation_id, session_source);
|
||||
|
||||
self.stream(body, headers, compression, turn_state).await
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ pub use crate::auth::AuthHeaderTelemetry;
|
||||
pub use crate::auth::AuthProvider;
|
||||
pub use crate::auth::SharedAuthProvider;
|
||||
pub use crate::auth::auth_header_telemetry;
|
||||
pub use crate::common::CompactionInput;
|
||||
pub use crate::common::MemorySummarizeInput;
|
||||
pub use crate::common::MemorySummarizeOutput;
|
||||
pub use crate::common::OpenAiVerbosity;
|
||||
|
||||
@@ -2,4 +2,3 @@ pub(crate) mod headers;
|
||||
pub(crate) mod responses;
|
||||
|
||||
pub use responses::Compression;
|
||||
pub(crate) use responses::attach_item_ids;
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
use crate::common::ResponsesApiRequest;
|
||||
use crate::error::ApiError;
|
||||
use crate::provider::Provider;
|
||||
use crate::requests::headers::build_conversation_headers;
|
||||
use crate::requests::headers::insert_header;
|
||||
use crate::requests::headers::subagent_header;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use http::HeaderMap;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
@@ -8,6 +16,33 @@ pub enum Compression {
|
||||
Zstd,
|
||||
}
|
||||
|
||||
pub(crate) fn build_responses_request_body(
|
||||
request: &ResponsesApiRequest,
|
||||
provider: &Provider,
|
||||
) -> Result<Value, ApiError> {
|
||||
let mut body = serde_json::to_value(request)
|
||||
.map_err(|e| ApiError::Stream(format!("failed to encode responses request: {e}")))?;
|
||||
if request.store == Some(true) && provider.is_azure_responses_endpoint() {
|
||||
attach_item_ids(&mut body, &request.input);
|
||||
}
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
pub(crate) fn build_responses_request_headers(
|
||||
mut extra_headers: HeaderMap,
|
||||
conversation_id: Option<String>,
|
||||
session_source: Option<SessionSource>,
|
||||
) -> HeaderMap {
|
||||
if let Some(ref conv_id) = conversation_id {
|
||||
insert_header(&mut extra_headers, "x-client-request-id", conv_id);
|
||||
}
|
||||
extra_headers.extend(build_conversation_headers(conversation_id));
|
||||
if let Some(subagent) = subagent_header(&session_source) {
|
||||
insert_header(&mut extra_headers, "x-openai-subagent", &subagent);
|
||||
}
|
||||
extra_headers
|
||||
}
|
||||
|
||||
pub(crate) fn attach_item_ids(payload_json: &mut Value, original_items: &[ResponseItem]) {
|
||||
let Some(input_value) = payload_json.get_mut("input") else {
|
||||
return;
|
||||
|
||||
@@ -43,7 +43,7 @@ struct RecordingState {
|
||||
}
|
||||
|
||||
impl RecordingState {
|
||||
fn record(&self, req: Request) {
|
||||
fn record_stream(&self, req: Request) {
|
||||
let mut guard = self
|
||||
.stream_requests
|
||||
.lock()
|
||||
@@ -78,7 +78,7 @@ impl HttpTransport for RecordingTransport {
|
||||
}
|
||||
|
||||
async fn stream(&self, req: Request) -> Result<StreamResponse, TransportError> {
|
||||
self.state.record(req);
|
||||
self.state.record_stream(req);
|
||||
|
||||
let stream = futures::stream::iter(Vec::<Result<Bytes, TransportError>>::new());
|
||||
Ok(StreamResponse {
|
||||
@@ -325,12 +325,11 @@ async fn streaming_client_retries_on_transport_error() -> Result<()> {
|
||||
instructions: "Say hi".into(),
|
||||
input: Vec::new(),
|
||||
tools: Vec::new(),
|
||||
tool_choice: "auto".into(),
|
||||
parallel_tool_calls: false,
|
||||
reasoning: None,
|
||||
store: false,
|
||||
stream: true,
|
||||
include: Vec::new(),
|
||||
store: Some(false),
|
||||
stream: Some(true),
|
||||
include: Some(Vec::new()),
|
||||
service_tier: None,
|
||||
prompt_cache_key: None,
|
||||
text: None,
|
||||
@@ -426,12 +425,11 @@ async fn azure_default_store_attaches_ids_and_headers() -> Result<()> {
|
||||
phase: None,
|
||||
}],
|
||||
tools: Vec::new(),
|
||||
tool_choice: "auto".into(),
|
||||
parallel_tool_calls: false,
|
||||
reasoning: None,
|
||||
store: true,
|
||||
stream: true,
|
||||
include: Vec::new(),
|
||||
store: Some(true),
|
||||
stream: Some(true),
|
||||
include: Some(Vec::new()),
|
||||
service_tier: None,
|
||||
prompt_cache_key: None,
|
||||
text: None,
|
||||
|
||||
Reference in New Issue
Block a user