2026-04-09 00:10:38 +08:00
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
|
2026-07-11 03:42:30 +08:00
|
|
|
use super::{apply_codex_openai_responses_special_body_edits, apply_codex_openai_special_headers};
|
2026-06-14 20:36:57 +08:00
|
|
|
use crate::ai_serving::planner::standard::build_local_openai_responses_request_body;
|
2026-04-09 00:10:38 +08:00
|
|
|
use http::{HeaderMap, HeaderValue};
|
|
|
|
|
use serde_json::json;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn applies_codex_defaults_when_body_rules_do_not_handle_fields() {
|
|
|
|
|
let mut body = json!({
|
2026-07-11 03:42:30 +08:00
|
|
|
"model": "gpt-5.4",
|
2026-04-09 00:10:38 +08:00
|
|
|
"max_output_tokens": 128,
|
|
|
|
|
"temperature": 0.3,
|
|
|
|
|
"top_p": 0.9,
|
|
|
|
|
"metadata": {"client": "desktop"},
|
|
|
|
|
"store": true
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-26 20:32:55 +08:00
|
|
|
apply_codex_openai_responses_special_body_edits(
|
|
|
|
|
&mut body,
|
|
|
|
|
"codex",
|
|
|
|
|
"openai:responses",
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
);
|
2026-04-09 00:10:38 +08:00
|
|
|
|
|
|
|
|
assert!(body.get("max_output_tokens").is_none());
|
|
|
|
|
assert!(body.get("temperature").is_none());
|
|
|
|
|
assert!(body.get("top_p").is_none());
|
|
|
|
|
assert!(body.get("metadata").is_none());
|
|
|
|
|
assert_eq!(body["store"], false);
|
2026-07-11 03:42:30 +08:00
|
|
|
assert!(body.get("instructions").is_none());
|
2026-05-14 14:39:22 +08:00
|
|
|
assert_eq!(body["include"], json!(["reasoning.encrypted_content"]));
|
|
|
|
|
assert_eq!(body["parallel_tool_calls"], true);
|
2026-07-11 03:42:30 +08:00
|
|
|
assert_eq!(body["reasoning"]["effort"], "medium");
|
|
|
|
|
assert!(body["reasoning"].get("summary").is_none());
|
2026-04-09 00:10:38 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 20:36:57 +08:00
|
|
|
#[test]
|
|
|
|
|
fn local_openai_responses_codex_body_wraps_string_input_for_backend() {
|
|
|
|
|
let body = json!({
|
|
|
|
|
"model": "gpt-5",
|
|
|
|
|
"input": "hello"
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let provider_request_body = build_local_openai_responses_request_body(
|
|
|
|
|
&body,
|
|
|
|
|
"gpt-5-upstream",
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
"codex",
|
|
|
|
|
"openai:responses",
|
|
|
|
|
None,
|
|
|
|
|
Some("key-123"),
|
|
|
|
|
&HeaderMap::new(),
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
.expect("codex local openai responses body should build");
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
provider_request_body["input"],
|
|
|
|
|
json!([{
|
|
|
|
|
"type": "message",
|
|
|
|
|
"role": "user",
|
|
|
|
|
"content": [{
|
|
|
|
|
"type": "input_text",
|
|
|
|
|
"text": "hello"
|
|
|
|
|
}]
|
|
|
|
|
}])
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 00:10:38 +08:00
|
|
|
#[test]
|
2026-04-12 01:52:25 +08:00
|
|
|
fn strips_store_for_compact_even_when_body_rules_handle_it() {
|
2026-04-09 00:10:38 +08:00
|
|
|
let body_rules = json!([
|
|
|
|
|
{"action":"set","path":"store","value":true},
|
|
|
|
|
{"action":"set","path":"instructions","value":"Keep custom"},
|
|
|
|
|
{"action":"set","path":"metadata","value":{"client":"desktop","mode":"custom"}},
|
|
|
|
|
{"action":"set","path":"top_p","value":0.5}
|
|
|
|
|
]);
|
|
|
|
|
let mut body = json!({
|
2026-07-11 03:42:30 +08:00
|
|
|
"model": "gpt-5.4",
|
2026-04-09 00:10:38 +08:00
|
|
|
"max_output_tokens": 128,
|
|
|
|
|
"metadata": {"client": "desktop", "mode": "custom"},
|
|
|
|
|
"store": true,
|
|
|
|
|
"instructions": "Keep custom",
|
|
|
|
|
"top_p": 0.5
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-26 20:32:55 +08:00
|
|
|
apply_codex_openai_responses_special_body_edits(
|
2026-04-09 00:10:38 +08:00
|
|
|
&mut body,
|
|
|
|
|
"codex",
|
2026-04-26 20:32:55 +08:00
|
|
|
"openai:responses:compact",
|
2026-04-09 00:10:38 +08:00
|
|
|
Some(&body_rules),
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert!(body.get("max_output_tokens").is_none());
|
2026-04-12 01:52:25 +08:00
|
|
|
assert!(body.get("store").is_none());
|
2026-04-09 00:10:38 +08:00
|
|
|
assert_eq!(body["instructions"], "Keep custom");
|
2026-07-11 03:42:30 +08:00
|
|
|
assert!(body.get("metadata").is_none());
|
|
|
|
|
assert!(body.get("top_p").is_none());
|
|
|
|
|
assert_eq!(body["parallel_tool_calls"], true);
|
|
|
|
|
assert!(body.as_object().is_some_and(|object| {
|
|
|
|
|
object.keys().all(|field| {
|
|
|
|
|
matches!(
|
|
|
|
|
field.as_str(),
|
|
|
|
|
"model"
|
|
|
|
|
| "input"
|
|
|
|
|
| "instructions"
|
|
|
|
|
| "tools"
|
|
|
|
|
| "parallel_tool_calls"
|
|
|
|
|
| "reasoning"
|
|
|
|
|
| "service_tier"
|
|
|
|
|
| "prompt_cache_key"
|
|
|
|
|
| "text"
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}));
|
2026-04-09 00:10:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-07-11 03:42:30 +08:00
|
|
|
fn does_not_synthesize_prompt_cache_key_from_api_key_identity() {
|
2026-04-09 00:10:38 +08:00
|
|
|
let mut body = json!({
|
|
|
|
|
"model": "gpt-5",
|
|
|
|
|
"input": "hello",
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-26 20:32:55 +08:00
|
|
|
apply_codex_openai_responses_special_body_edits(
|
2026-04-09 00:10:38 +08:00
|
|
|
&mut body,
|
|
|
|
|
"codex",
|
2026-04-26 20:32:55 +08:00
|
|
|
"openai:responses",
|
2026-04-09 00:10:38 +08:00
|
|
|
None,
|
|
|
|
|
Some("key-123"),
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-11 03:42:30 +08:00
|
|
|
assert!(body.get("prompt_cache_key").is_none());
|
2026-04-09 00:10:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn keeps_existing_prompt_cache_key_for_codex_requests() {
|
|
|
|
|
let mut body = json!({
|
|
|
|
|
"model": "gpt-5",
|
|
|
|
|
"input": "hello",
|
|
|
|
|
"prompt_cache_key": "existing-key",
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-26 20:32:55 +08:00
|
|
|
apply_codex_openai_responses_special_body_edits(
|
2026-04-09 00:10:38 +08:00
|
|
|
&mut body,
|
|
|
|
|
"codex",
|
2026-04-26 20:32:55 +08:00
|
|
|
"openai:responses",
|
2026-04-09 00:10:38 +08:00
|
|
|
None,
|
|
|
|
|
Some("key-123"),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!(body["prompt_cache_key"], "existing-key");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-07-11 03:42:30 +08:00
|
|
|
fn injects_identity_headers_without_deriving_session_headers_from_body() {
|
2026-04-09 00:10:38 +08:00
|
|
|
let mut headers = BTreeMap::new();
|
|
|
|
|
let body = json!({
|
|
|
|
|
"model": "gpt-5",
|
|
|
|
|
"prompt_cache_key": "172c39e6-c0a0-5a70-8b63-e0f8e0d185a3",
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-11 03:42:30 +08:00
|
|
|
apply_codex_openai_special_headers(
|
2026-04-09 00:10:38 +08:00
|
|
|
&mut headers,
|
|
|
|
|
&body,
|
|
|
|
|
&HeaderMap::new(),
|
|
|
|
|
"codex",
|
2026-04-26 20:32:55 +08:00
|
|
|
"openai:responses",
|
2026-04-09 00:10:38 +08:00
|
|
|
Some("trace-codex-123"),
|
2026-07-11 03:42:30 +08:00
|
|
|
Some(r#"{"account_id":"acc-123","is_fedramp":true}"#),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
headers.get("chatgpt-account-id"),
|
|
|
|
|
Some(&"acc-123".to_string())
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(headers.get("x-client-request-id"), None);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
headers.get("user-agent"),
|
|
|
|
|
Some(&"codex_cli_rs/0.144.1".to_string())
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(headers.get("originator"), Some(&"codex_cli_rs".to_string()));
|
|
|
|
|
assert!(!headers.contains_key("version"));
|
|
|
|
|
assert_eq!(headers.get("x-openai-fedramp"), Some(&"true".to_string()));
|
|
|
|
|
assert_eq!(headers.get("session-id"), None);
|
|
|
|
|
assert_eq!(headers.get("thread-id"), None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn injects_only_codex_client_headers_for_images_requests() {
|
|
|
|
|
let mut headers = BTreeMap::new();
|
|
|
|
|
apply_codex_openai_special_headers(
|
|
|
|
|
&mut headers,
|
|
|
|
|
&json!({
|
|
|
|
|
"model": "gpt-image-2",
|
|
|
|
|
"prompt": "draw a city"
|
|
|
|
|
}),
|
|
|
|
|
&HeaderMap::new(),
|
|
|
|
|
"codex",
|
|
|
|
|
"openai:image",
|
|
|
|
|
Some("trace-codex-image-123"),
|
|
|
|
|
Some(r#"{"account_id":"acc-123","is_fedramp":true}"#),
|
2026-04-09 00:10:38 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
headers.get("chatgpt-account-id"),
|
|
|
|
|
Some(&"acc-123".to_string())
|
|
|
|
|
);
|
2026-04-22 21:09:29 +08:00
|
|
|
assert_eq!(
|
|
|
|
|
headers.get("user-agent"),
|
2026-07-11 03:42:30 +08:00
|
|
|
Some(&"codex_cli_rs/0.144.1".to_string())
|
2026-04-09 00:10:38 +08:00
|
|
|
);
|
2026-07-11 03:42:30 +08:00
|
|
|
assert_eq!(headers.get("originator"), Some(&"codex_cli_rs".to_string()));
|
|
|
|
|
assert!(!headers.contains_key("version"));
|
|
|
|
|
assert_eq!(headers.get("x-openai-fedramp"), Some(&"true".to_string()));
|
|
|
|
|
for name in ["x-client-request-id", "session-id", "thread-id"] {
|
|
|
|
|
assert!(
|
|
|
|
|
!headers.contains_key(name),
|
|
|
|
|
"unexpected Images header: {name}"
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-04-09 00:10:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-07-11 03:42:30 +08:00
|
|
|
fn preserves_client_context_headers_and_enforces_codex_auth_identity_headers() {
|
2026-04-09 00:10:38 +08:00
|
|
|
let mut headers = BTreeMap::new();
|
|
|
|
|
headers.insert(
|
|
|
|
|
"x-client-request-id".to_string(),
|
|
|
|
|
"kept-by-rule-request".to_string(),
|
|
|
|
|
);
|
2026-07-11 03:42:30 +08:00
|
|
|
headers.insert("session-id".to_string(), "kept-by-rule-session".to_string());
|
|
|
|
|
headers.insert("thread-id".to_string(), "kept-by-rule-thread".to_string());
|
|
|
|
|
headers.insert(
|
|
|
|
|
"chatgpt-account-id".to_string(),
|
|
|
|
|
"configured-spoof".to_string(),
|
|
|
|
|
);
|
|
|
|
|
headers.insert(
|
|
|
|
|
"x-openai-fedramp".to_string(),
|
|
|
|
|
"configured-false".to_string(),
|
|
|
|
|
);
|
2026-04-09 00:10:38 +08:00
|
|
|
let body = json!({
|
|
|
|
|
"model": "gpt-5",
|
|
|
|
|
"prompt_cache_key": "172c39e6-c0a0-5a70-8b63-e0f8e0d185a3",
|
|
|
|
|
});
|
|
|
|
|
let mut original_headers = HeaderMap::new();
|
|
|
|
|
original_headers.insert(
|
|
|
|
|
"x-client-request-id",
|
|
|
|
|
HeaderValue::from_static("user-specified-request"),
|
|
|
|
|
);
|
|
|
|
|
original_headers.insert(
|
2026-07-11 03:42:30 +08:00
|
|
|
"session-id",
|
2026-04-09 00:10:38 +08:00
|
|
|
HeaderValue::from_static("user-specified-session"),
|
|
|
|
|
);
|
|
|
|
|
original_headers.insert(
|
2026-07-11 03:42:30 +08:00
|
|
|
"thread-id",
|
|
|
|
|
HeaderValue::from_static("user-specified-thread"),
|
2026-04-09 00:10:38 +08:00
|
|
|
);
|
2026-04-22 21:09:29 +08:00
|
|
|
original_headers.insert(
|
|
|
|
|
"user-agent",
|
|
|
|
|
HeaderValue::from_static("user-specified-agent"),
|
|
|
|
|
);
|
|
|
|
|
original_headers.insert(
|
|
|
|
|
"originator",
|
|
|
|
|
HeaderValue::from_static("user-specified-originator"),
|
|
|
|
|
);
|
2026-07-11 03:42:30 +08:00
|
|
|
original_headers.insert("version", HeaderValue::from_static("user-version"));
|
|
|
|
|
original_headers.insert("x-openai-fedramp", HeaderValue::from_static("user-fedramp"));
|
|
|
|
|
original_headers.insert(
|
|
|
|
|
"chatgpt-account-id",
|
|
|
|
|
HeaderValue::from_static("user-account"),
|
|
|
|
|
);
|
2026-04-09 00:10:38 +08:00
|
|
|
|
2026-07-11 03:42:30 +08:00
|
|
|
apply_codex_openai_special_headers(
|
2026-04-09 00:10:38 +08:00
|
|
|
&mut headers,
|
|
|
|
|
&body,
|
|
|
|
|
&original_headers,
|
|
|
|
|
"codex",
|
2026-04-26 20:32:55 +08:00
|
|
|
"openai:responses",
|
2026-04-09 00:10:38 +08:00
|
|
|
Some("trace-codex-123"),
|
2026-07-11 03:42:30 +08:00
|
|
|
Some(r#"{"account_id":"acc-123","is_fedramp":true}"#),
|
2026-04-09 00:10:38 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
headers.get("x-client-request-id"),
|
|
|
|
|
Some(&"kept-by-rule-request".to_string())
|
|
|
|
|
);
|
2026-04-22 21:09:29 +08:00
|
|
|
assert!(!headers.contains_key("user-agent"));
|
|
|
|
|
assert!(!headers.contains_key("originator"));
|
2026-07-11 03:42:30 +08:00
|
|
|
assert!(!headers.contains_key("version"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
headers.get("chatgpt-account-id"),
|
|
|
|
|
Some(&"acc-123".to_string())
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(headers.get("x-openai-fedramp"), Some(&"true".to_string()));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
headers.get("session-id"),
|
|
|
|
|
Some(&"kept-by-rule-session".to_string())
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
headers.get("thread-id"),
|
|
|
|
|
Some(&"kept-by-rule-thread".to_string())
|
|
|
|
|
);
|
2026-04-09 00:10:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-07-11 03:42:30 +08:00
|
|
|
fn compact_does_not_derive_session_headers_from_body() {
|
2026-04-09 00:10:38 +08:00
|
|
|
let mut headers = BTreeMap::new();
|
|
|
|
|
let body = json!({
|
|
|
|
|
"model": "gpt-5",
|
|
|
|
|
"prompt_cache_key": "172c39e6-c0a0-5a70-8b63-e0f8e0d185a3",
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-11 03:42:30 +08:00
|
|
|
apply_codex_openai_special_headers(
|
2026-04-09 00:10:38 +08:00
|
|
|
&mut headers,
|
|
|
|
|
&body,
|
|
|
|
|
&HeaderMap::new(),
|
|
|
|
|
"codex",
|
2026-04-26 20:32:55 +08:00
|
|
|
"openai:responses:compact",
|
2026-04-09 00:10:38 +08:00
|
|
|
Some("trace-codex-compact-123"),
|
2026-07-11 03:42:30 +08:00
|
|
|
Some(r#"{"account_id":"acc-123","is_fedramp":true}"#),
|
2026-04-09 00:10:38 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
headers.get("chatgpt-account-id"),
|
|
|
|
|
Some(&"acc-123".to_string())
|
|
|
|
|
);
|
2026-07-11 03:42:30 +08:00
|
|
|
assert_eq!(headers.get("x-client-request-id"), None);
|
2026-04-22 21:09:29 +08:00
|
|
|
assert_eq!(
|
|
|
|
|
headers.get("user-agent"),
|
2026-07-11 03:42:30 +08:00
|
|
|
Some(&"codex_cli_rs/0.144.1".to_string())
|
2026-04-22 21:09:29 +08:00
|
|
|
);
|
2026-07-11 03:42:30 +08:00
|
|
|
assert_eq!(headers.get("originator"), Some(&"codex_cli_rs".to_string()));
|
|
|
|
|
assert!(!headers.contains_key("version"));
|
|
|
|
|
assert_eq!(headers.get("x-openai-fedramp"), Some(&"true".to_string()));
|
|
|
|
|
assert_eq!(headers.get("session-id"), None);
|
|
|
|
|
assert_eq!(headers.get("thread-id"), None);
|
2026-04-09 00:10:38 +08:00
|
|
|
}
|