mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-11 05:30:19 +08:00
fix(codex): 补齐缓存身份终态请求头
This commit is contained in:
@@ -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,8 +1,8 @@
|
||||
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,
|
||||
@@ -302,8 +302,18 @@ fn completes_partial_and_null_codex_client_metadata() {
|
||||
"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] {
|
||||
for body in [&mut partial, &mut null_metadata, &mut null_session] {
|
||||
apply_codex_openai_responses_special_body_edits(
|
||||
body,
|
||||
"codex",
|
||||
@@ -327,6 +337,15 @@ fn completes_partial_and_null_codex_client_metadata() {
|
||||
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]
|
||||
@@ -337,17 +356,34 @@ fn leaves_malformed_codex_client_metadata_unchanged() {
|
||||
"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();
|
||||
|
||||
apply_codex_openai_responses_special_body_edits(
|
||||
&mut body,
|
||||
"codex",
|
||||
"openai:responses",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
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]
|
||||
@@ -413,10 +449,39 @@ fn chat_to_codex_responses_adapts_prompt_cache_identity_end_to_end() {
|
||||
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 injects_identity_headers_without_deriving_session_headers_from_body() {
|
||||
fn projects_uuid_prompt_cache_identity_into_missing_session_headers() {
|
||||
let mut headers = BTreeMap::new();
|
||||
let body = json!({
|
||||
"model": "gpt-5",
|
||||
@@ -432,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())
|
||||
@@ -445,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]
|
||||
@@ -464,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())
|
||||
@@ -547,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"),
|
||||
@@ -588,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",
|
||||
@@ -604,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"),
|
||||
@@ -617,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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1468,10 +1468,17 @@ 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 Codex backend scopes prompt caching through client_metadata.session_id, while the
|
||||
// standard OpenAI contract permits arbitrary prompt_cache_key strings. Adapt only requests
|
||||
// that carry a cache key but do not already carry a native Codex session identity.
|
||||
// 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)
|
||||
@@ -1482,8 +1489,19 @@ fn adapt_codex_prompt_cache_identity_for_backend(body_object: &mut serde_json::M
|
||||
};
|
||||
|
||||
match body_object.get("client_metadata") {
|
||||
Some(Value::Object(metadata)) if metadata.contains_key("session_id") => return,
|
||||
Some(Value::Object(_) | Value::Null) | None => {}
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -1506,12 +1524,16 @@ fn adapt_codex_prompt_cache_identity_for_backend(body_object: &mut serde_json::M
|
||||
let Some(metadata) = metadata.as_object_mut() else {
|
||||
return;
|
||||
};
|
||||
metadata
|
||||
.entry("session_id".to_string())
|
||||
.or_insert_with(|| Value::String(cache_identity.clone()));
|
||||
metadata
|
||||
.entry("thread_id".to_string())
|
||||
.or_insert_with(|| Value::String(cache_identity.clone()));
|
||||
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(
|
||||
@@ -1520,6 +1542,74 @@ fn adapt_codex_prompt_cache_identity_for_backend(body_object: &mut serde_json::M
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user