Merge branch 'pr-453'

This commit is contained in:
fawney19
2026-05-15 02:25:39 +08:00
11 changed files with 213 additions and 78 deletions

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,

View File

@@ -375,9 +375,9 @@ INSERT INTO api_keys (
feature_settings,
is_active,
expires_at,
auto_delete_on_expiry,
is_locked,
is_standalone,
auto_delete_on_expiry,
total_requests,
total_tokens,
total_cost_usd,
@@ -398,10 +398,10 @@ VALUES (
$11,
NULL,
$12,
FALSE,
FALSE,
$13,
$14,
FALSE,
FALSE,
$15,
$16,
$17,
@@ -449,9 +449,9 @@ INSERT INTO api_keys (
feature_settings,
is_active,
expires_at,
auto_delete_on_expiry,
is_locked,
is_standalone,
auto_delete_on_expiry,
total_requests,
total_tokens,
total_cost_usd,
@@ -472,10 +472,10 @@ VALUES (
$11,
NULL,
$12,
FALSE,
TRUE,
$13,
$14,
FALSE,
TRUE,
$15,
$16,
$17,
@@ -551,7 +551,7 @@ SET
allowed_providers = CASE WHEN $7 THEN $8::json ELSE allowed_providers END,
allowed_api_formats = CASE WHEN $9 THEN $10::json ELSE allowed_api_formats END,
allowed_models = CASE WHEN $11 THEN $12::json ELSE allowed_models END,
expires_at = CASE WHEN $13 THEN $14 ELSE expires_at END,
expires_at = CASE WHEN $13 THEN $14::timestamptz ELSE expires_at END,
auto_delete_on_expiry = CASE WHEN $15 THEN $16 ELSE auto_delete_on_expiry END,
updated_at = NOW()
WHERE id = $1
@@ -1535,9 +1535,28 @@ fn map_auth_api_key_export_row(
#[cfg(test)]
mod tests {
use super::{SqlxAuthApiKeySnapshotReadRepository, UPDATE_STANDALONE_API_KEY_BASIC_SQL};
use super::{
CREATE_STANDALONE_API_KEY_SQL, CREATE_USER_API_KEY_SQL,
SqlxAuthApiKeySnapshotReadRepository, UPDATE_STANDALONE_API_KEY_BASIC_SQL,
};
use crate::driver::postgres::{PostgresPoolConfig, PostgresPoolFactory};
#[test]
fn create_api_key_sql_orders_expiry_before_standalone_flags() {
assert!(CREATE_USER_API_KEY_SQL.contains(
"expires_at,\n auto_delete_on_expiry,\n is_locked,\n is_standalone,"
));
assert!(CREATE_USER_API_KEY_SQL.contains(
"$12,\n $13,\n FALSE,\n FALSE,\n $14,"
));
assert!(CREATE_STANDALONE_API_KEY_SQL.contains(
"expires_at,\n auto_delete_on_expiry,\n is_locked,\n is_standalone,"
));
assert!(CREATE_STANDALONE_API_KEY_SQL.contains(
"$12,\n $13,\n FALSE,\n TRUE,\n $14,"
));
}
#[test]
fn update_standalone_api_key_basic_sql_casts_json_case_values() {
assert!(UPDATE_STANDALONE_API_KEY_BASIC_SQL
@@ -1552,7 +1571,7 @@ mod tests {
assert!(UPDATE_STANDALONE_API_KEY_BASIC_SQL
.contains("rate_limit = CASE WHEN $3 THEN $4 ELSE rate_limit END"));
assert!(UPDATE_STANDALONE_API_KEY_BASIC_SQL
.contains("expires_at = CASE WHEN $13 THEN $14 ELSE expires_at END"));
.contains("expires_at = CASE WHEN $13 THEN $14::timestamptz ELSE expires_at END"));
assert!(UPDATE_STANDALONE_API_KEY_BASIC_SQL.contains(
"auto_delete_on_expiry = CASE WHEN $15 THEN $16 ELSE auto_delete_on_expiry END"
));