fix: split Codex chat and responses defaults

This commit is contained in:
ZheFox
2026-05-14 14:39:22 +08:00
parent a7e71624e3
commit a0eed2cc51
10 changed files with 185 additions and 69 deletions

View File

@@ -30,7 +30,10 @@ fn applies_codex_defaults_when_body_rules_do_not_handle_fields() {
assert!(body.get("top_p").is_none());
assert!(body.get("metadata").is_none());
assert_eq!(body["store"], false);
assert_eq!(body["instructions"], "You are ChatGPT.");
assert_eq!(body["instructions"], "");
assert_eq!(body["include"], json!(["reasoning.encrypted_content"]));
assert_eq!(body["parallel_tool_calls"], true);
assert!(body.get("reasoning").is_none());
}
#[test]

View File

@@ -293,7 +293,11 @@ mod tests {
assert!(converted.get("metadata").is_none());
assert_eq!(converted["store"], false);
assert_eq!(converted["instructions"], "You are ChatGPT.");
assert_eq!(converted["instructions"], "");
assert_eq!(converted["include"], json!(["reasoning.encrypted_content"]));
assert_eq!(converted["parallel_tool_calls"], true);
assert_eq!(converted["reasoning"]["effort"], "medium");
assert_eq!(converted["reasoning"]["summary"], "auto");
}
#[test]

View File

@@ -2,7 +2,7 @@ use serde_json::Value;
use crate::ai_serving::transport::apply_standard_provider_request_body_rules_with_request_headers;
use crate::ai_serving::{
apply_codex_openai_responses_special_body_edits,
apply_codex_openai_responses_chat_body_edits,
apply_openai_responses_compact_special_body_edits,
build_cross_format_openai_chat_request_body_with_model_directives as surface_build_cross_format_openai_chat_request_body,
build_local_openai_chat_request_body_with_model_directives as surface_build_local_openai_chat_request_body,
@@ -75,7 +75,7 @@ pub(crate) fn build_cross_format_openai_chat_request_body(
body_json,
request_headers,
)?;
apply_codex_openai_responses_special_body_edits(
apply_codex_openai_responses_chat_body_edits(
&mut provider_request_body,
provider_type,
provider_api_format,

View File

@@ -149,7 +149,8 @@ fn local_openai_responses_wrapper_preserves_body_order_after_edits() {
"prompt_cache_key",
]
);
assert_eq!(provider_request_body["parallel_tool_calls"], true);
assert_eq!(provider_request_body["parallel_tool_calls"], json!(true));
assert_eq!(provider_request_body["instructions"], json!(""));
}
#[test]

View File

@@ -3,6 +3,7 @@ pub(crate) use aether_ai_formats::api::{
aggregate_openai_chat_stream_sync_response, aggregate_openai_responses_stream_sync_response,
aggregate_standard_chat_stream_sync_response, aggregate_standard_cli_stream_sync_response,
api_format_alias_matches, api_format_storage_aliases,
apply_codex_openai_responses_chat_body_edits,
apply_codex_openai_responses_special_body_edits, apply_codex_openai_responses_special_headers,
apply_model_directive_mapping_patch, apply_model_directive_overrides_from_model,
apply_model_directive_overrides_from_request,

View File

@@ -155,6 +155,7 @@ pub async fn serve_execution_runtime_tcp(
Ok(())
}
#[cfg(unix)]
pub async fn serve_execution_runtime_unix(
socket_path: &Path,
max_in_flight_requests: Option<usize>,
@@ -179,6 +180,19 @@ pub async fn serve_execution_runtime_unix(
Ok(())
}
#[cfg(not(unix))]
pub async fn serve_execution_runtime_unix(
_socket_path: &Path,
_max_in_flight_requests: Option<usize>,
_distributed_request_gate: Option<RuntimeSemaphore>,
) -> Result<(), Box<dyn std::error::Error>> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"Unix sockets are not supported on this platform",
)
.into())
}
async fn health(State(state): State<ExecutionRuntimeAppState>) -> impl IntoResponse {
let request_concurrency = state.request_concurrency_snapshot().map(|snapshot| {
json!({

View File

@@ -3094,7 +3094,7 @@ async fn gateway_handles_openai_responses_test_model_locally() {
.json_body
.as_ref()
.and_then(|body| body.get("instructions")),
Some(&json!("You are ChatGPT."))
Some(&json!(""))
);
assert_eq!(
plan.body

View File

@@ -141,6 +141,7 @@ pub use crate::formats::{
},
openai::responses::{
codex::{
apply_codex_openai_responses_chat_body_edits,
apply_codex_openai_responses_special_body_edits,
apply_codex_openai_responses_special_headers,
apply_openai_responses_compact_special_body_edits, CODEX_OPENAI_IMAGE_DEFAULT_MODEL,

View File

@@ -8,7 +8,10 @@ use sha2::Sha256;
use uuid::Uuid;
const CODEX_PROMPT_CACHE_NAMESPACE_VERSION: &str = "v3";
const CODEX_DEFAULT_INSTRUCTIONS: &str = "You are ChatGPT.";
const CODEX_DEFAULT_INSTRUCTIONS: &str = "";
const CODEX_DEFAULT_REASONING_EFFORT: &str = "medium";
const CODEX_DEFAULT_REASONING_SUMMARY: &str = "auto";
const CODEX_REASONING_ENCRYPTED_CONTENT_INCLUDE: &str = "reasoning.encrypted_content";
const CODEX_DEFAULT_USER_AGENT: &str =
"codex-tui/0.122.0 (Mac OS 15.2.0; arm64) vscode/2.6.11 (codex-tui; 0.122.0)";
const CODEX_DEFAULT_ORIGINATOR: &str = "codex-tui";
@@ -139,50 +142,6 @@ fn inject_codex_default_variation_prompt(body_object: &mut serde_json::Map<Strin
);
}
fn ensure_codex_reasoning_summary(body_object: &mut serde_json::Map<String, Value>) {
let reasoning = body_object
.entry("reasoning".to_string())
.or_insert_with(|| json!({}));
if !reasoning.is_object() {
*reasoning = json!({});
}
let Some(reasoning_object) = reasoning.as_object_mut() else {
return;
};
reasoning_object
.entry("effort".to_string())
.or_insert_with(|| json!("medium"));
reasoning_object
.entry("summary".to_string())
.or_insert_with(|| json!("auto"));
}
fn ensure_codex_reasoning_include(body_object: &mut serde_json::Map<String, Value>) {
const REASONING_ENCRYPTED_CONTENT: &str = "reasoning.encrypted_content";
match body_object.get_mut("include") {
Some(Value::Array(include)) => {
let has_reasoning_encrypted_content = include
.iter()
.any(|value| value.as_str() == Some(REASONING_ENCRYPTED_CONTENT));
if !has_reasoning_encrypted_content {
include.push(json!(REASONING_ENCRYPTED_CONTENT));
}
}
Some(_) | None => {
body_object.insert("include".to_string(), json!([REASONING_ENCRYPTED_CONTENT]));
}
}
}
fn ensure_codex_responses_defaults(body_object: &mut serde_json::Map<String, Value>) {
ensure_codex_reasoning_summary(body_object);
ensure_codex_reasoning_include(body_object);
body_object
.entry("parallel_tool_calls".to_string())
.or_insert_with(|| json!(true));
}
fn build_stable_codex_prompt_cache_key(user_api_key_id: &str) -> Option<String> {
let normalized = user_api_key_id.trim();
if normalized.is_empty() {
@@ -330,6 +289,54 @@ pub fn apply_openai_responses_compact_special_body_edits(
body_object.remove("stream");
}
fn ensure_codex_responses_passthrough_fields(
body_object: &mut serde_json::Map<String, Value>,
provider_api_format: &str,
body_rules: Option<&Value>,
) {
if is_openai_responses_compact_request(provider_api_format)
|| is_openai_image_request(provider_api_format)
{
return;
}
if !body_rules_handle_path(body_rules, "parallel_tool_calls") {
body_object.insert("parallel_tool_calls".to_string(), json!(true));
}
if !body_rules_handle_path(body_rules, "include") {
body_object.insert(
"include".to_string(),
json!([CODEX_REASONING_ENCRYPTED_CONTENT_INCLUDE]),
);
}
}
fn ensure_codex_chat_reasoning_defaults(
body_object: &mut serde_json::Map<String, Value>,
provider_api_format: &str,
body_rules: Option<&Value>,
) {
if is_openai_responses_compact_request(provider_api_format)
|| is_openai_image_request(provider_api_format)
{
return;
}
if body_rules_handle_path(body_rules, "reasoning") {
return;
}
let reasoning = body_object
.entry("reasoning".to_string())
.or_insert_with(|| json!({}));
let Some(reasoning_object) = reasoning.as_object_mut() else {
return;
};
reasoning_object
.entry("effort".to_string())
.or_insert_with(|| json!(CODEX_DEFAULT_REASONING_EFFORT));
reasoning_object
.entry("summary".to_string())
.or_insert_with(|| json!(CODEX_DEFAULT_REASONING_SUMMARY));
}
pub fn apply_codex_openai_responses_special_body_edits(
provider_request_body: &mut Value,
provider_type: &str,
@@ -362,7 +369,7 @@ pub fn apply_codex_openai_responses_special_body_edits(
} else if !body_rules_handle_path(body_rules, "store") {
body_object.insert("store".to_string(), json!(false));
}
ensure_codex_responses_defaults(body_object);
ensure_codex_responses_passthrough_fields(body_object, provider_api_format, body_rules);
if !body_rules_handle_path(body_rules, "instructions")
&& !body_object.contains_key("instructions")
{
@@ -370,6 +377,10 @@ pub fn apply_codex_openai_responses_special_body_edits(
"instructions".to_string(),
json!(CODEX_DEFAULT_INSTRUCTIONS),
);
} else if body_object.contains_key("instructions")
&& body_object.get("instructions").map_or(false, |v| v.is_null())
{
body_object.insert("instructions".to_string(), json!(""));
}
if is_openai_image_request(provider_api_format) {
body_object.insert(
@@ -389,6 +400,30 @@ pub fn apply_codex_openai_responses_special_body_edits(
);
}
pub fn apply_codex_openai_responses_chat_body_edits(
provider_request_body: &mut Value,
provider_type: &str,
provider_api_format: &str,
body_rules: Option<&Value>,
user_api_key_id: Option<&str>,
) {
apply_codex_openai_responses_special_body_edits(
provider_request_body,
provider_type,
provider_api_format,
body_rules,
user_api_key_id,
);
if !is_codex_openai_responses_request(provider_type, provider_api_format) {
return;
}
let Some(body_object) = provider_request_body.as_object_mut() else {
return;
};
ensure_codex_chat_reasoning_defaults(body_object, provider_api_format, body_rules);
}
pub fn apply_codex_openai_responses_special_headers(
provider_request_headers: &mut BTreeMap<String, String>,
provider_request_body: &Value,
@@ -464,13 +499,14 @@ pub fn apply_codex_openai_responses_special_headers(
#[cfg(test)]
mod tests {
use super::{
apply_codex_openai_responses_chat_body_edits,
apply_codex_openai_responses_special_body_edits, CODEX_OPENAI_IMAGE_INTERNAL_MODEL,
};
use serde_json::json;
#[test]
fn codex_responses_body_edits_request_reasoning_summary_stream() {
let mut provider_request_body = json!({
fn codex_responses_body_edits_inject_passthrough_fields_without_reasoning_summary() {
let mut provider_request_body = json!( {
"input": [{
"role": "user",
"content": "hello"
@@ -487,21 +523,18 @@ mod tests {
None,
);
assert_eq!(
provider_request_body["reasoning"]["effort"],
json!("medium")
);
assert_eq!(provider_request_body["reasoning"]["summary"], json!("auto"));
assert!(provider_request_body.get("reasoning").is_none());
assert_eq!(
provider_request_body["include"],
json!(["reasoning.encrypted_content"])
);
assert_eq!(provider_request_body["parallel_tool_calls"], json!(true));
assert_eq!(provider_request_body["instructions"], json!(""));
}
#[test]
fn codex_responses_body_edits_preserve_existing_reasoning_and_include() {
let mut provider_request_body = json!({
fn codex_responses_body_edits_preserve_existing_reasoning_but_use_codex_include() {
let mut provider_request_body = json!( {
"input": [],
"model": "gpt-5.4",
"include": ["file_search_call.results"],
@@ -524,9 +557,53 @@ mod tests {
);
assert_eq!(
provider_request_body["include"],
json!(["file_search_call.results", "reasoning.encrypted_content"])
json!(["reasoning.encrypted_content"])
);
assert_eq!(provider_request_body["parallel_tool_calls"], json!(false));
assert_eq!(provider_request_body["parallel_tool_calls"], json!(true));
}
#[test]
fn codex_chat_body_edits_inject_reasoning_summary_defaults() {
let mut provider_request_body = json!({
"input": [],
"model": "gpt-5.4"
});
apply_codex_openai_responses_chat_body_edits(
&mut provider_request_body,
"codex",
"openai:responses",
None,
None,
);
assert_eq!(provider_request_body["reasoning"]["effort"], json!("medium"));
assert_eq!(provider_request_body["reasoning"]["summary"], json!("auto"));
assert_eq!(
provider_request_body["include"],
json!(["reasoning.encrypted_content"])
);
assert_eq!(provider_request_body["parallel_tool_calls"], json!(true));
}
#[test]
fn codex_chat_body_edits_preserve_existing_reasoning_effort() {
let mut provider_request_body = json!({
"input": [],
"model": "gpt-5.4",
"reasoning": {"effort": "low"}
});
apply_codex_openai_responses_chat_body_edits(
&mut provider_request_body,
"codex",
"openai:responses",
None,
None,
);
assert_eq!(provider_request_body["reasoning"]["effort"], json!("low"));
assert_eq!(provider_request_body["reasoning"]["summary"], json!("auto"));
}
#[test]

View File

@@ -14,6 +14,7 @@ use serde_json::Value;
use crate::formats::shared::model_directives::apply_model_directive_overrides_from_request;
use crate::formats::openai::responses::codex::{
apply_codex_openai_responses_chat_body_edits,
apply_codex_openai_responses_special_body_edits,
apply_openai_responses_compact_special_body_edits,
};
@@ -117,13 +118,27 @@ pub fn build_standard_request_body_with_model_directives_and_request_headers(
) {
return None;
}
apply_codex_openai_responses_special_body_edits(
&mut provider_request_body,
provider_type,
provider_api_format,
body_rules,
user_api_key_id,
let client_is_openai_responses_family = matches!(
aether_ai_formats::normalize_api_format_alias(client_api_format).as_str(),
"openai:responses" | "openai:responses:compact"
);
if client_is_openai_responses_family {
apply_codex_openai_responses_special_body_edits(
&mut provider_request_body,
provider_type,
provider_api_format,
body_rules,
user_api_key_id,
);
} else {
apply_codex_openai_responses_chat_body_edits(
&mut provider_request_body,
provider_type,
provider_api_format,
body_rules,
user_api_key_id,
);
}
apply_openai_responses_compact_special_body_edits(
&mut provider_request_body,
provider_api_format,