Merge pull request #682 from MMEXA/codex/codex-prompt-cache-identity-20260717

fix(codex): 统一通用缓存键与原生会话身份
This commit is contained in:
fawney19
2026-07-18 00:33:20 +08:00
committed by GitHub
7 changed files with 665 additions and 16 deletions
@@ -133,6 +133,17 @@ pub(crate) fn apply_provider_request_routing_policy_to_decision(
);
let Some(context) = input.routing_context.as_ref() else {
// Cache identity headers are projected only at the terminal boundary. Any non-empty
// session headers already present here are explicit client or header-rule inputs and stay
// authoritative.
if let Some(provider_request_body) = decision.provider_request_body.as_ref() {
crate::ai_serving::apply_codex_openai_responses_identity_headers(
&mut decision.provider_request_headers,
provider_request_body,
provider_type.as_str(),
provider_api_format.as_str(),
);
}
return Ok(());
};
let provider_body_rules = decision
@@ -182,6 +193,14 @@ pub(crate) fn apply_provider_request_routing_policy_to_decision(
})?;
ensure_report_context_routing_trace(input, decision, &policy);
if policy.mutation_plan.is_empty() {
if let Some(provider_request_body) = decision.provider_request_body.as_ref() {
crate::ai_serving::apply_codex_openai_responses_identity_headers(
&mut decision.provider_request_headers,
provider_request_body,
provider_type.as_str(),
provider_api_format.as_str(),
);
}
return Ok(());
}
if original_provider_request_body.is_none() && !policy.mutation_plan.body_patch.is_empty() {
@@ -255,6 +274,12 @@ pub(crate) fn apply_provider_request_routing_policy_to_decision(
input.requested_model.as_str(),
)
});
crate::ai_serving::apply_codex_openai_responses_identity_headers(
&mut provider_request_headers,
&provider_request_body,
provider_type.as_str(),
provider_api_format.as_str(),
);
crate::ai_serving::apply_codex_openai_responses_lite_header_for_request_body_with_capabilities(
&mut provider_request_headers,
Some(&provider_request_body),
@@ -1279,6 +1304,107 @@ mod tests {
);
}
#[test]
fn codex_prompt_cache_identity_headers_are_terminal_after_routing_mutations() {
let mut input = sample_decision_input();
input
.routing_context
.as_mut()
.expect("routing context")
.client_api_format = "openai:responses".to_string();
set_provider_request_rules(
&mut input,
&["gpt-5"],
json!([{
"type": "patch_headers",
"patch": [
{"op": "remove", "name": "session-id"},
{"op": "remove", "name": "thread-id"}
]
}]),
);
let identity = "172c39e6-c0a0-5a70-8b63-e0f8e0d185a3";
let mut decision = sample_decision();
decision.provider_type = Some("codex".to_string());
decision.provider_api_format = Some("openai:responses".to_string());
decision.client_api_format = Some("openai:responses".to_string());
decision.provider_request_body = Some(json!({
"model": "gpt-5",
"input": [],
"prompt_cache_key": identity,
"client_metadata": {
"session_id": identity,
"thread_id": identity
}
}));
assert!(!decision.provider_request_headers.contains_key("session-id"));
assert!(!decision.provider_request_headers.contains_key("thread-id"));
apply_provider_request_routing_policy_to_decision(&input, &mut decision, None)
.expect("terminal Codex identity contract should be restored");
assert_eq!(
decision
.provider_request_headers
.get("session-id")
.map(String::as_str),
Some(identity)
);
assert_eq!(
decision
.provider_request_headers
.get("thread-id")
.map(String::as_str),
Some(identity)
);
}
#[test]
fn codex_prompt_cache_identity_headers_fail_closed_after_body_identity_removal() {
let mut input = sample_decision_input();
input
.routing_context
.as_mut()
.expect("routing context")
.client_api_format = "openai:responses".to_string();
set_provider_request_rules(
&mut input,
&["gpt-5"],
json!([{
"type": "json_patch_body",
"patch": [
{"op": "remove", "path": "/prompt_cache_key"},
{"op": "remove", "path": "/client_metadata"}
]
}]),
);
let identity = "172c39e6-c0a0-5a70-8b63-e0f8e0d185a3";
let mut decision = sample_decision();
decision.provider_type = Some("codex".to_string());
decision.provider_api_format = Some("openai:responses".to_string());
decision.client_api_format = Some("openai:responses".to_string());
decision.provider_request_body = Some(json!({
"model": "gpt-5",
"input": [],
"prompt_cache_key": identity,
"client_metadata": {
"session_id": identity,
"thread_id": identity
}
}));
assert!(!decision.provider_request_headers.contains_key("session-id"));
assert!(!decision.provider_request_headers.contains_key("thread-id"));
apply_provider_request_routing_policy_to_decision(&input, &mut decision, None)
.expect("terminal Codex identity contract should fail closed");
let body = decision.provider_request_body.as_ref().expect("body");
assert!(body.get("prompt_cache_key").is_none());
assert!(body.get("client_metadata").is_none());
assert!(!decision.provider_request_headers.contains_key("session-id"));
assert!(!decision.provider_request_headers.contains_key("thread-id"));
}
#[test]
fn codex_compact_contract_is_terminal_after_routing_mutations() {
let mut input = sample_decision_input();
@@ -3,7 +3,8 @@
mod tests;
pub(crate) use crate::ai_serving::{
apply_codex_openai_responses_special_body_edits, apply_codex_openai_special_headers,
apply_codex_openai_responses_identity_headers, apply_codex_openai_responses_special_body_edits,
apply_codex_openai_special_headers,
};
pub(crate) fn codex_model_capabilities_for_transport(
@@ -1,10 +1,12 @@
use std::collections::BTreeMap;
use super::{
apply_codex_openai_responses_special_body_edits, apply_codex_openai_special_headers,
codex_model_capabilities,
apply_codex_openai_responses_identity_headers, apply_codex_openai_responses_special_body_edits,
apply_codex_openai_special_headers, codex_model_capabilities,
};
use crate::ai_serving::planner::standard::{
build_cross_format_openai_responses_request_body, build_local_openai_responses_request_body,
};
use crate::ai_serving::planner::standard::build_local_openai_responses_request_body;
use http::{HeaderMap, HeaderValue};
use serde_json::json;
@@ -180,11 +182,11 @@ fn does_not_synthesize_prompt_cache_key_from_api_key_identity() {
}
#[test]
fn keeps_existing_prompt_cache_key_for_codex_requests() {
fn adapts_generic_prompt_cache_key_to_codex_native_identity() {
let mut body = json!({
"model": "gpt-5",
"input": "hello",
"prompt_cache_key": "existing-key",
"prompt_cache_key": "ltm-pc-v2-5557e02f5c9b447a97673ba330dbe77a",
});
apply_codex_openai_responses_special_body_edits(
@@ -195,11 +197,291 @@ fn keeps_existing_prompt_cache_key_for_codex_requests() {
Some("key-123"),
);
assert_eq!(body["prompt_cache_key"], "existing-key");
let expected_identity = "d9c5d122-7c1c-5fb1-ba9d-656062eda44e";
assert_eq!(body["prompt_cache_key"], expected_identity);
assert_eq!(body["client_metadata"]["session_id"], expected_identity);
assert_eq!(body["client_metadata"]["thread_id"], expected_identity);
}
#[test]
fn injects_identity_headers_without_deriving_session_headers_from_body() {
fn preserves_native_codex_cache_identity_and_metadata() {
let mut body = json!({
"model": "gpt-5",
"input": "hello",
"prompt_cache_key": "guardian:parent-thread",
"client_metadata": {
"session_id": "native-session",
"thread_id": "native-thread",
"turn_id": "native-turn"
}
});
let expected = body.clone();
apply_codex_openai_responses_special_body_edits(
&mut body,
"codex",
"openai:responses",
None,
Some("key-123"),
);
assert_eq!(body["prompt_cache_key"], expected["prompt_cache_key"]);
assert_eq!(body["client_metadata"], expected["client_metadata"]);
}
#[test]
fn preserves_uuid_prompt_cache_key_while_completing_codex_identity() {
let identity = "172c39e6-c0a0-5a70-8b63-e0f8e0d185a3";
let mut body = json!({
"model": "gpt-5.6-luna",
"input": "hello",
"prompt_cache_key": identity
});
apply_codex_openai_responses_special_body_edits(
&mut body,
"codex",
"openai:responses",
None,
None,
);
assert_eq!(body["prompt_cache_key"], identity);
assert_eq!(body["client_metadata"]["session_id"], identity);
assert_eq!(body["client_metadata"]["thread_id"], identity);
}
#[test]
fn keeps_codex_prompt_cache_domains_distinct() {
let mut first = json!({
"model": "gpt-5.6-luna",
"input": "hello",
"prompt_cache_key": "tenant-a"
});
let mut second = json!({
"model": "gpt-5.6-luna",
"input": "hello",
"prompt_cache_key": "tenant-b"
});
for body in [&mut first, &mut second] {
apply_codex_openai_responses_special_body_edits(
body,
"codex",
"openai:responses",
None,
None,
);
}
assert_ne!(first["prompt_cache_key"], second["prompt_cache_key"]);
assert_eq!(
first["prompt_cache_key"],
first["client_metadata"]["session_id"]
);
assert_eq!(
second["prompt_cache_key"],
second["client_metadata"]["session_id"]
);
}
#[test]
fn completes_partial_and_null_codex_client_metadata() {
let mut partial = json!({
"model": "gpt-5.6-luna",
"input": "hello",
"prompt_cache_key": "generic-affinity",
"client_metadata": {
"thread_id": "native-thread",
"caller": "sdk"
}
});
let mut null_metadata = json!({
"model": "gpt-5.6-luna",
"input": "hello",
"prompt_cache_key": "generic-affinity",
"client_metadata": null
});
let mut null_session = json!({
"model": "gpt-5.6-luna",
"input": "hello",
"prompt_cache_key": "generic-affinity",
"client_metadata": {
"session_id": null,
"thread_id": null,
"caller": "sdk"
}
});
for body in [&mut partial, &mut null_metadata, &mut null_session] {
apply_codex_openai_responses_special_body_edits(
body,
"codex",
"openai:responses",
None,
None,
);
}
assert_eq!(partial["client_metadata"]["thread_id"], "native-thread");
assert_eq!(partial["client_metadata"]["caller"], "sdk");
assert_eq!(
partial["client_metadata"]["session_id"],
partial["prompt_cache_key"]
);
assert_eq!(
null_metadata["client_metadata"]["session_id"],
null_metadata["prompt_cache_key"]
);
assert_eq!(
null_metadata["client_metadata"]["thread_id"],
null_metadata["prompt_cache_key"]
);
assert_eq!(
null_session["client_metadata"]["session_id"],
null_session["prompt_cache_key"]
);
assert_eq!(
null_session["client_metadata"]["thread_id"],
null_session["prompt_cache_key"]
);
assert_eq!(null_session["client_metadata"]["caller"], "sdk");
}
#[test]
fn leaves_malformed_codex_client_metadata_unchanged() {
let mut body = json!({
"model": "gpt-5.6-luna",
"input": "hello",
"prompt_cache_key": "generic-affinity",
"client_metadata": "invalid"
});
let mut malformed_fields = json!({
"model": "gpt-5.6-luna",
"input": "hello",
"prompt_cache_key": "generic-affinity",
"client_metadata": {
"session_id": 42,
"thread_id": ""
}
});
let expected_malformed_metadata = malformed_fields["client_metadata"].clone();
for candidate in [&mut body, &mut malformed_fields] {
apply_codex_openai_responses_special_body_edits(
candidate,
"codex",
"openai:responses",
None,
None,
);
}
assert_eq!(body["prompt_cache_key"], "generic-affinity");
assert_eq!(body["client_metadata"], "invalid");
assert_eq!(malformed_fields["prompt_cache_key"], "generic-affinity");
assert_eq!(
malformed_fields["client_metadata"],
expected_malformed_metadata
);
}
#[test]
fn limits_prompt_cache_identity_adaptation_to_codex_responses_family() {
let original = json!({
"model": "gpt-5.6-luna",
"input": "hello",
"prompt_cache_key": "generic-affinity"
});
let mut standard_openai = original.clone();
let mut codex_compact = original.clone();
apply_codex_openai_responses_special_body_edits(
&mut standard_openai,
"openai",
"openai:responses",
None,
None,
);
apply_codex_openai_responses_special_body_edits(
&mut codex_compact,
"codex",
"openai:responses:compact",
None,
None,
);
assert_eq!(standard_openai, original);
assert_ne!(codex_compact["prompt_cache_key"], "generic-affinity");
assert!(codex_compact.get("client_metadata").is_none());
}
#[test]
fn chat_to_codex_responses_adapts_prompt_cache_identity_end_to_end() {
let body = json!({
"model": "gpt-5.6-luna",
"messages": [{"role": "user", "content": "hello"}],
"prompt_cache_key": "ltm-pc-v2-5557e02f5c9b447a97673ba330dbe77a"
});
let provider_request_body = build_cross_format_openai_responses_request_body(
&body,
"gpt-5.6-luna",
"openai:chat",
"openai:responses",
true,
false,
"codex",
None,
None,
&HeaderMap::new(),
false,
)
.expect("chat to Codex Responses request should build");
let expected_identity = "d9c5d122-7c1c-5fb1-ba9d-656062eda44e";
assert_eq!(provider_request_body["prompt_cache_key"], expected_identity);
assert_eq!(
provider_request_body["client_metadata"]["session_id"],
expected_identity
);
assert_eq!(
provider_request_body["client_metadata"]["thread_id"],
expected_identity
);
let mut provider_request_headers = BTreeMap::new();
apply_codex_openai_special_headers(
&mut provider_request_headers,
&provider_request_body,
&HeaderMap::new(),
"codex",
"openai:responses",
Some("trace-codex-cache-identity"),
None,
);
apply_codex_openai_responses_identity_headers(
&mut provider_request_headers,
&provider_request_body,
"codex",
"openai:responses",
);
assert_eq!(
provider_request_headers
.get("session-id")
.map(String::as_str),
Some(expected_identity)
);
assert_eq!(
provider_request_headers
.get("thread-id")
.map(String::as_str),
Some(expected_identity)
);
}
#[test]
fn projects_uuid_prompt_cache_identity_into_missing_session_headers() {
let mut headers = BTreeMap::new();
let body = json!({
"model": "gpt-5",
@@ -215,7 +497,7 @@ fn injects_identity_headers_without_deriving_session_headers_from_body() {
Some("trace-codex-123"),
Some(r#"{"account_id":"acc-123","is_fedramp":true}"#),
);
apply_codex_openai_responses_identity_headers(&mut headers, &body, "codex", "openai:responses");
assert_eq!(
headers.get("chatgpt-account-id"),
Some(&"acc-123".to_string())
@@ -228,8 +510,88 @@ fn injects_identity_headers_without_deriving_session_headers_from_body() {
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);
assert_eq!(
headers.get("session-id").map(String::as_str),
Some("172c39e6-c0a0-5a70-8b63-e0f8e0d185a3")
);
assert_eq!(
headers.get("thread-id").map(String::as_str),
Some("172c39e6-c0a0-5a70-8b63-e0f8e0d185a3")
);
}
#[test]
fn projects_native_codex_metadata_for_non_uuid_cache_overrides() {
let mut headers = BTreeMap::new();
let body = json!({
"model": "gpt-5.6-luna",
"prompt_cache_key": "guardian:parent-thread",
"client_metadata": {
"session_id": "019f687b-8e92-7842-9631-d5bf0dba0a3b",
"thread_id": "019f6d20-1111-7222-8333-444455556666"
}
});
apply_codex_openai_special_headers(
&mut headers,
&body,
&HeaderMap::new(),
"codex",
"openai:responses",
None,
None,
);
apply_codex_openai_responses_identity_headers(&mut headers, &body, "codex", "openai:responses");
assert_eq!(
headers.get("session-id").map(String::as_str),
Some("019f687b-8e92-7842-9631-d5bf0dba0a3b")
);
assert_eq!(
headers.get("thread-id").map(String::as_str),
Some("019f6d20-1111-7222-8333-444455556666")
);
}
#[test]
fn leaves_non_native_cache_keys_out_of_identity_headers() {
let mut headers = BTreeMap::new();
let body = json!({
"model": "gpt-5.6-luna",
"prompt_cache_key": "generic-cache-key"
});
apply_codex_openai_special_headers(
&mut headers,
&body,
&HeaderMap::new(),
"codex",
"openai:responses",
None,
None,
);
apply_codex_openai_responses_identity_headers(&mut headers, &body, "codex", "openai:responses");
assert!(!headers.contains_key("session-id"));
assert!(!headers.contains_key("thread-id"));
}
#[test]
fn leaves_malformed_native_metadata_out_of_identity_headers() {
let mut headers = BTreeMap::new();
let body = json!({
"model": "gpt-5.6-luna",
"prompt_cache_key": "172c39e6-c0a0-5a70-8b63-e0f8e0d185a3",
"client_metadata": {
"session_id": 42,
"thread_id": ""
}
});
apply_codex_openai_responses_identity_headers(&mut headers, &body, "codex", "openai:responses");
assert!(!headers.contains_key("session-id"));
assert!(!headers.contains_key("thread-id"));
}
#[test]
@@ -247,7 +609,6 @@ fn injects_only_codex_client_headers_for_images_requests() {
Some("trace-codex-image-123"),
Some(r#"{"account_id":"acc-123","is_fedramp":true}"#),
);
assert_eq!(
headers.get("chatgpt-account-id"),
Some(&"acc-123".to_string())
@@ -330,6 +691,7 @@ fn preserves_client_context_headers_and_enforces_codex_provider_identity() {
Some("trace-codex-123"),
Some(r#"{"account_id":"acc-123","is_fedramp":true}"#),
);
apply_codex_openai_responses_identity_headers(&mut headers, &body, "codex", "openai:responses");
assert_eq!(
headers.get("x-client-request-id"),
@@ -371,7 +733,7 @@ fn preserves_client_context_headers_and_enforces_codex_provider_identity() {
}
#[test]
fn compact_does_not_derive_session_headers_from_body() {
fn compact_projects_uuid_prompt_cache_identity_into_session_headers() {
let mut headers = BTreeMap::new();
let body = json!({
"model": "gpt-5",
@@ -387,6 +749,12 @@ fn compact_does_not_derive_session_headers_from_body() {
Some("trace-codex-compact-123"),
Some(r#"{"account_id":"acc-123","is_fedramp":true}"#),
);
apply_codex_openai_responses_identity_headers(
&mut headers,
&body,
"codex",
"openai:responses:compact",
);
assert_eq!(
headers.get("chatgpt-account-id"),
@@ -400,6 +768,12 @@ fn compact_does_not_derive_session_headers_from_body() {
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);
assert_eq!(
headers.get("session-id").map(String::as_str),
Some("172c39e6-c0a0-5a70-8b63-e0f8e0d185a3")
);
assert_eq!(
headers.get("thread-id").map(String::as_str),
Some("172c39e6-c0a0-5a70-8b63-e0f8e0d185a3")
);
}
@@ -4,6 +4,7 @@ pub(crate) use aether_ai_formats::api::{
aggregate_standard_chat_stream_sync_response, aggregate_standard_cli_stream_sync_response,
api_format_alias_matches, api_format_storage_aliases,
apply_codex_openai_compact_terminal_headers, apply_codex_openai_responses_chat_body_edits,
apply_codex_openai_responses_identity_headers,
apply_codex_openai_responses_lite_header_for_request_body_with_capabilities,
apply_codex_openai_responses_lite_header_with_capabilities,
apply_codex_openai_responses_special_body_edits,
@@ -657,7 +657,8 @@ async fn gateway_executes_openai_responses_compact_as_unary_request_impl() {
);
assert_eq!(
seen_execution_runtime_request.body["prompt_cache_key"],
json!("session:compact-e2e")
// Compact omits client_metadata, but keeps the same deterministic Codex cache identity.
json!("f3eb8726-b7b2-56c5-90b5-8789d628c8cf")
);
assert_eq!(
seen_execution_runtime_request.proxy_node_id,
+1
View File
@@ -173,6 +173,7 @@ pub use crate::formats::{
codex::{
apply_codex_openai_compact_terminal_headers,
apply_codex_openai_responses_chat_body_edits,
apply_codex_openai_responses_identity_headers,
apply_codex_openai_responses_lite_header_for_request_body_with_capabilities,
apply_codex_openai_responses_lite_header_with_capabilities,
apply_codex_openai_responses_special_body_edits,
@@ -7,6 +7,8 @@ use serde_json::{json, Value};
const CODEX_DEFAULT_REASONING_EFFORT: &str = "medium";
const CODEX_REASONING_ENCRYPTED_CONTENT_INCLUDE: &str = "reasoning.encrypted_content";
const CODEX_PROMPT_CACHE_IDENTITY_NAMESPACE: &str =
"https://github.com/fawney19/Aether/codex/prompt-cache-identity/v1/";
pub const CODEX_RESPONSES_LITE_HEADER: &str = "x-openai-internal-codex-responses-lite";
pub const CODEX_MODEL_CATALOG_METADATA_FIELD: &str = "codex_models";
const CODEX_OPENAI_RESPONSES_UNSUPPORTED_BODY_FIELDS: &[&str] = &[
@@ -1466,6 +1468,148 @@ fn wrap_codex_responses_string_input_for_backend(
);
}
fn non_empty_json_string(value: Option<&Value>) -> Option<&str> {
value
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
}
fn adapt_codex_prompt_cache_identity_for_backend(body_object: &mut serde_json::Map<String, Value>) {
// The standard OpenAI contract permits arbitrary prompt_cache_key strings, while the Codex
// backend's native session identity is UUID-shaped. Adapt only requests that carry a cache key
// but do not already carry a native Codex session identity.
let Some(prompt_cache_key) = body_object
.get("prompt_cache_key")
.and_then(Value::as_str)
.filter(|value| !value.trim().is_empty())
.map(ToOwned::to_owned)
else {
return;
};
match body_object.get("client_metadata") {
Some(Value::Object(metadata)) => {
match metadata.get("session_id") {
Some(Value::String(session_id)) if !session_id.trim().is_empty() => return,
None | Some(Value::Null) => {}
Some(_) => return,
}
match metadata.get("thread_id") {
None | Some(Value::Null) => {}
Some(Value::String(thread_id)) if !thread_id.trim().is_empty() => {}
Some(_) => return,
}
}
Some(Value::Null) | None => {}
Some(_) => return,
}
let cache_identity = uuid::Uuid::parse_str(&prompt_cache_key)
.unwrap_or_else(|_| {
uuid::Uuid::new_v5(
&uuid::Uuid::NAMESPACE_URL,
format!("{CODEX_PROMPT_CACHE_IDENTITY_NAMESPACE}{prompt_cache_key}").as_bytes(),
)
})
.to_string();
{
let metadata = body_object
.entry("client_metadata".to_string())
.or_insert_with(|| json!({}));
if metadata.is_null() {
*metadata = json!({});
}
let Some(metadata) = metadata.as_object_mut() else {
return;
};
metadata.insert(
"session_id".to_string(),
Value::String(cache_identity.clone()),
);
if metadata.get("thread_id").is_none_or(Value::is_null) {
metadata.insert(
"thread_id".to_string(),
Value::String(cache_identity.clone()),
);
}
}
body_object.insert(
"prompt_cache_key".to_string(),
Value::String(cache_identity),
);
}
fn valid_codex_identity_header(value: &str) -> Option<String> {
let value = value.trim();
if value.is_empty() || value.parse::<http::HeaderValue>().is_err() {
return None;
}
Some(value.to_string())
}
fn codex_prompt_cache_header_identity(provider_request_body: &Value) -> Option<(String, String)> {
let prompt_cache_key = non_empty_json_string(provider_request_body.get("prompt_cache_key"))?;
match provider_request_body.get("client_metadata") {
Some(Value::Object(metadata)) => match metadata.get("session_id") {
Some(Value::String(session_id)) if !session_id.trim().is_empty() => {
let session_id = valid_codex_identity_header(session_id)?;
let thread_id = match metadata.get("thread_id") {
None | Some(Value::Null) => session_id.clone(),
Some(Value::String(thread_id)) if !thread_id.trim().is_empty() => {
valid_codex_identity_header(thread_id)?
}
Some(_) => return None,
};
return Some((session_id, thread_id));
}
None | Some(Value::Null) => {}
Some(_) => return None,
},
Some(Value::Null) | None => {}
Some(_) => return None,
}
let cache_identity = uuid::Uuid::parse_str(prompt_cache_key).ok()?.to_string();
Some((cache_identity.clone(), cache_identity))
}
fn insert_btree_header_if_missing(
headers: &mut BTreeMap<String, String>,
header_name: &str,
header_value: String,
) {
if headers.iter().any(|(name, value)| {
name.trim().eq_ignore_ascii_case(header_name) && !value.trim().is_empty()
}) {
return;
}
remove_btree_header(headers, header_name);
headers.insert(header_name.to_string(), header_value);
}
pub fn apply_codex_openai_responses_identity_headers(
provider_request_headers: &mut BTreeMap<String, String>,
provider_request_body: &Value,
provider_type: &str,
provider_api_format: &str,
) {
// Codex projects its body session identity into compatibility HTTP headers. Existing explicit
// headers remain authoritative; only missing values are completed here.
if !is_codex_openai_responses_request(provider_type, provider_api_format) {
return;
}
let Some((session_id, thread_id)) = codex_prompt_cache_header_identity(provider_request_body)
else {
return;
};
insert_btree_header_if_missing(provider_request_headers, "session-id", session_id);
insert_btree_header_if_missing(provider_request_headers, "thread-id", thread_id);
}
pub fn apply_codex_openai_responses_special_body_edits(
provider_request_body: &mut Value,
provider_type: &str,
@@ -1532,6 +1676,7 @@ pub fn apply_codex_openai_responses_special_body_edits_with_source_model_and_cap
body_object.remove(*field);
}
}
adapt_codex_prompt_cache_identity_for_backend(body_object);
if is_openai_responses_compact_request(provider_api_format) {
body_object.remove("store");
} else if !body_rules_handle_path(body_rules, "store") {