mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
fix(openai): preserve deployment account routing policy
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -32,7 +32,6 @@ impl CodexBackendClient {
|
||||
content_type: Option<&str>,
|
||||
) -> CodexClientResult<HeaderMap> {
|
||||
let mut headers = self.model_request_headers(&self.profile.snapshot(), context)?;
|
||||
let mut passthrough = HeaderMap::new();
|
||||
for header in extra_protocol_headers {
|
||||
let name = HeaderName::from_bytes(header.name().as_bytes())?;
|
||||
if is_managed_identity_header(name.as_str())
|
||||
@@ -58,9 +57,8 @@ impl CodexBackendClient {
|
||||
} else {
|
||||
HeaderValue::from_bytes(header.value())?
|
||||
};
|
||||
passthrough.append(name, value);
|
||||
headers.append(name, value);
|
||||
}
|
||||
headers.extend(passthrough);
|
||||
if let Some(content_type) = content_type {
|
||||
headers.insert(
|
||||
reqwest::header::CONTENT_TYPE,
|
||||
|
||||
@@ -34,7 +34,9 @@ pub(super) fn is_managed_identity_header(name: &str) -> bool {
|
||||
| "originator"
|
||||
| "user-agent"
|
||||
| "version"
|
||||
// 地域和账号路由统一按部署配置生成
|
||||
| "x-openai-internal-codex-residency"
|
||||
| "x-openai-account-routing-override"
|
||||
| "x-oai-attestation"
|
||||
| "x-oai-is"
|
||||
| "x-oai-is-update"
|
||||
|
||||
@@ -45,7 +45,6 @@ const DOWNSTREAM_CLIENT_HEADERS: &[(&str, &str)] = &[
|
||||
];
|
||||
|
||||
const BUSINESS_EXTENSION_HEADERS: &[(&str, &str)] = &[
|
||||
("X-OpenAI-Account-Routing-Override", "first-route"),
|
||||
("x-openai-fedramp", "true"),
|
||||
("x-grok-turn-idx", "7"),
|
||||
("X-Grok-Model-Override", "synthetic-model"),
|
||||
@@ -79,6 +78,10 @@ fn request_with_opaque_headers(use_websocket: bool) -> CodexResponsesRequest {
|
||||
"x-openai-internal-codex-residency",
|
||||
STANDARD.encode(b"future-region")
|
||||
],
|
||||
[
|
||||
"X-OpenAI-Account-Routing-Override",
|
||||
STANDARD.encode(b"client-route")
|
||||
],
|
||||
["x-codex-turn-state", STANDARD.encode(b"turn-ascii")],
|
||||
["x-codex-turn-state", STANDARD.encode(b"turn-\x80")],
|
||||
["bad header name", STANDARD.encode(b"ignored")],
|
||||
@@ -286,6 +289,10 @@ async fn backend_http_should_preserve_business_headers_without_downstream_transp
|
||||
raw_header_values(&raw, "traceparent"),
|
||||
vec![b"synthetic-trace".to_vec()]
|
||||
);
|
||||
assert_eq!(
|
||||
raw_header_values(&raw, "x-openai-account-routing-override"),
|
||||
vec![b"configured-route".to_vec()]
|
||||
);
|
||||
assert_eq!(
|
||||
raw_header_values(&raw, "x-codex-installation-id"),
|
||||
vec![b"lease-installation".to_vec()]
|
||||
@@ -475,6 +482,10 @@ async fn backend_websocket_should_preserve_business_headers_without_downstream_t
|
||||
assert_eq!(values(name), vec![value.to_vec()], "missing {name}");
|
||||
}
|
||||
assert_eq!(values("traceparent"), vec![b"synthetic-trace".to_vec()]);
|
||||
assert_eq!(
|
||||
values("x-openai-account-routing-override"),
|
||||
vec![b"configured-route".to_vec()]
|
||||
);
|
||||
assert_eq!(
|
||||
values("x-codex-installation-id"),
|
||||
vec![b"lease-installation".to_vec()]
|
||||
|
||||
@@ -71,20 +71,15 @@ fn assert_isolated_request(
|
||||
body: &Value,
|
||||
owner: Option<&str>,
|
||||
) {
|
||||
for (name, values) in [
|
||||
("x-openai-account-routing-override", ["us", "us_cr"]),
|
||||
("x-openai-fedramp", ["true", "false"]),
|
||||
] {
|
||||
assert_eq!(
|
||||
headers
|
||||
.get_all(name)
|
||||
.iter()
|
||||
.map(|value| value.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
values,
|
||||
"changed {name} with owner {owner:?}"
|
||||
);
|
||||
}
|
||||
assert!(!headers.contains_key("x-openai-account-routing-override"));
|
||||
assert_eq!(
|
||||
headers
|
||||
.get_all("x-openai-fedramp")
|
||||
.iter()
|
||||
.map(|value| value.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
["true", "false"]
|
||||
);
|
||||
assert_eq!(headers["x-codex-guardian"], "true");
|
||||
assert_eq!(
|
||||
headers
|
||||
@@ -120,7 +115,7 @@ fn assert_isolated_request(
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_preserves_routing_headers_and_only_scopes_guardian_parent_reference() {
|
||||
async fn http_applies_deployment_routing_and_only_scopes_guardian_parent_reference() {
|
||||
for owner in [Some(SELECTED_ACCOUNT), Some("acct_scope_old"), None] {
|
||||
let store = Arc::new(MemoryAccountStore::default());
|
||||
create_account(&store, SELECTED_ACCOUNT).await;
|
||||
@@ -163,7 +158,7 @@ async fn http_preserves_routing_headers_and_only_scopes_guardian_parent_referenc
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_preserves_routing_headers_and_only_scopes_guardian_parent_reference() {
|
||||
async fn websocket_applies_deployment_routing_and_only_scopes_guardian_parent_reference() {
|
||||
for owner in [Some(SELECTED_ACCOUNT), Some("acct_scope_old"), None] {
|
||||
let store = Arc::new(MemoryAccountStore::default());
|
||||
create_account(&store, SELECTED_ACCOUNT).await;
|
||||
|
||||
@@ -142,8 +142,12 @@ async fn independent_endpoints_send_opaque_headers_without_client_identity() {
|
||||
let sent = requests.last().unwrap();
|
||||
assert_eq!(sent.url.path(), endpoint);
|
||||
uuid::Uuid::parse_str(sent.headers["x-codex-installation-id"].to_str().unwrap()).unwrap();
|
||||
assert!(
|
||||
!sent
|
||||
.headers
|
||||
.contains_key("x-openai-account-routing-override")
|
||||
);
|
||||
for (name, expected) in [
|
||||
("x-openai-account-routing-override", "client-route"),
|
||||
("x-openai-fedramp", "true"),
|
||||
("traceparent", "client-trace"),
|
||||
("tracestate", "vendor=client"),
|
||||
|
||||
@@ -457,14 +457,6 @@ fn assert_live_correlation(headers: &reqwest::header::HeaderMap) {
|
||||
assert_eq!(headers["thread-id"], session);
|
||||
assert_eq!(headers["openai-alpha"], "live-test");
|
||||
assert_eq!(headers["x-openai-fedramp"], "true");
|
||||
assert_eq!(
|
||||
headers
|
||||
.get_all("x-openai-account-routing-override")
|
||||
.iter()
|
||||
.map(|value| value.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
["first-route", "second-route"]
|
||||
);
|
||||
assert_eq!(
|
||||
headers
|
||||
.get_all("x-test-business")
|
||||
@@ -478,6 +470,7 @@ fn assert_live_correlation(headers: &reqwest::header::HeaderMap) {
|
||||
"x-oai-attestation",
|
||||
"x-forwarded-for",
|
||||
"x-stainless-runtime",
|
||||
"x-openai-account-routing-override",
|
||||
"x-codex-turn-state",
|
||||
"x-codex-routing-hint",
|
||||
] {
|
||||
|
||||
@@ -143,7 +143,7 @@ fn deployment_routing_survives_artifact_updates_and_profile_selection() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_transports_apply_residency_and_client_or_default_routing() {
|
||||
async fn model_transports_apply_deployment_routing_with_or_without_client_headers() {
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use gateway_core::operation::{GenerateRequest, ProtocolPayload};
|
||||
|
||||
@@ -266,19 +266,13 @@ async fn model_transports_apply_residency_and_client_or_default_routing() {
|
||||
.expect("model response");
|
||||
let headers = capture.await;
|
||||
for (name, value) in HEADERS.into_iter().zip(expected) {
|
||||
let expected_values =
|
||||
if name == "x-openai-account-routing-override" && has_downstream_headers {
|
||||
vec!["downstream-first", "downstream-second"]
|
||||
} else {
|
||||
value.into_iter().collect::<Vec<_>>()
|
||||
};
|
||||
assert_eq!(
|
||||
headers
|
||||
.get_all(name)
|
||||
.iter()
|
||||
.map(|value| value.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
expected_values,
|
||||
value.into_iter().collect::<Vec<_>>(),
|
||||
"{document}, websocket={websocket}, downstream={has_downstream_headers}, {name}",
|
||||
);
|
||||
}
|
||||
|
||||
+3
-4
@@ -392,10 +392,9 @@ PostgreSQL/Redis 启动密码。日常校验使用 `config --quiet`
|
||||
位置覆盖只影响 OpenAI 请求中受支持的位置与时区信息,不改变上下文日期、真实出口 IP、epoch 时间戳或系统时区。
|
||||
`openai.residency` 和 `openai.account_routing_override` 是独立于客户端身份的启动配置,
|
||||
分别指定 `x-openai-internal-codex-residency` 和 `x-openai-account-routing-override` 请求头。
|
||||
两项均为可选字符串,不校验地域或路由枚举。
|
||||
地域头使用配置值,省略或设为 `null` 时不发送。
|
||||
路由头优先透传客户端值,客户端未提供时使用配置值,两者均缺省时不发送。
|
||||
示例配置均填写 `us`,修改后重启应用生效。身份与位置字段见 [运行设置 API](../docs/api.md#8-运行设置)
|
||||
两项均为可选字符串,配置值原样发送并覆盖客户端同名头,支持自定义地域或路由值。
|
||||
省略或设为 `null` 时删除对应请求头。
|
||||
修改后重启应用生效。身份与位置字段见 [运行设置 API](../docs/api.md#8-运行设置)
|
||||
后台账号操作使用 Provider 自己的官方画像;发布资料刷新不改写用户选择或 `config.yaml`
|
||||
|
||||
### CPR 互联
|
||||
|
||||
+4
-4
@@ -396,10 +396,10 @@ API Key 与 OAuth 共用模拟客户端画像(`User-Agent`、`originator`、`v
|
||||
上游认证只来自选中的账号;API Key 不携带 OAuth Cookie、ChatGPT 账号身份或下游的
|
||||
`X-OpenAI-Actor-Authorization` 托管认证声明。
|
||||
`x-codex-installation-id` 使用选中账号已生成并持久化的安装 ID,与正文 `client_metadata` 中的安装身份一致。
|
||||
`x-openai-internal-codex-residency` 仅按部署配置 `openai.residency` 生成,省略或 `null` 时不发送。
|
||||
`x-openai-account-routing-override` 和 `x-openai-fedramp` 按业务头透传,保留原始多值。
|
||||
客户端未提供路由头时,使用 `openai.account_routing_override` 配置值,配置说明见
|
||||
[部署与运维](../deploy/README.md#启动后的运行设置)
|
||||
`x-openai-internal-codex-residency` 和 `x-openai-account-routing-override` 分别由部署配置
|
||||
`openai.residency`、`openai.account_routing_override` 控制,配置值原样发送并覆盖客户端同名头,
|
||||
省略或 `null` 时删除对应请求头。配置说明见 [部署与运维](../deploy/README.md#启动后的运行设置)
|
||||
`x-openai-fedramp` 按业务头透传,保留原始多值。
|
||||
上述过滤描述原生转发;已信任插件的显式 header 改写可以覆盖账号与画像生成的默认头,
|
||||
不按认证或会话字段名称拦截,边界见 [SDK 请求中间件](../backend/crates/gateway-plugin/sdk/docs/capabilities.md#模型请求与-attempt)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user