feat(proxy): 添加本地代理请求覆盖功能,支持自定义请求头和请求体 (#4589)

* feat(proxy): 添加本地代理请求覆盖功能,支持自定义请求头和请求体

* fix(proxy): harden local request overrides validation

* feat(proxy): 添加受保护的本地代理请求头名称验证功能

* fix(i18n): 更新本地代理请求覆盖的错误提示信息格式

---------

Co-authored-by: jason.mei <jason.mei@ucloud.cn>
This commit is contained in:
Jianfeng Mei
2026-06-24 22:19:01 +08:00
committed by GitHub
parent edeee25fae
commit 6fd4e6f462
14 changed files with 855 additions and 10 deletions
+51 -2
View File
@@ -382,6 +382,21 @@ pub struct CodexChatReasoningConfig {
pub output_format: Option<String>,
}
/// Local proxy request overrides applied after route/protocol transforms.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LocalProxyRequestOverrides {
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub headers: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<serde_json::Value>,
}
impl LocalProxyRequestOverrides {
pub fn is_empty(&self) -> bool {
self.headers.is_empty() && self.body.is_none()
}
}
/// 供应商元数据
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ProviderMeta {
@@ -466,6 +481,12 @@ pub struct ProviderMeta {
/// Custom User-Agent for local proxy routing.
#[serde(rename = "customUserAgent", skip_serializing_if = "Option::is_none")]
pub custom_user_agent: Option<String>,
/// Local proxy request overrides applied to the transformed upstream request.
#[serde(
rename = "localProxyRequestOverrides",
skip_serializing_if = "Option::is_none"
)]
pub local_proxy_request_overrides: Option<LocalProxyRequestOverrides>,
/// 累加模式应用中,该 provider 是否已写入 live config。
/// `None` 表示旧数据/未知状态,`Some(false)` 表示明确仅存在于数据库中。
#[serde(rename = "liveConfigManaged", skip_serializing_if = "Option::is_none")]
@@ -932,10 +953,11 @@ pub struct OpenCodeModelLimit {
#[cfg(test)]
mod tests {
use super::{
ClaudeModelConfig, CodexModelConfig, GeminiModelConfig, OpenCodeProviderConfig, Provider,
ProviderManager, ProviderMeta, UniversalProvider,
ClaudeModelConfig, CodexModelConfig, GeminiModelConfig, LocalProxyRequestOverrides,
OpenCodeProviderConfig, Provider, ProviderManager, ProviderMeta, UniversalProvider,
};
use serde_json::json;
use std::collections::HashMap;
#[test]
fn provider_meta_serializes_pricing_model_source() {
@@ -963,6 +985,33 @@ mod tests {
assert!(value.get("pricingModelSource").is_none());
}
#[test]
fn provider_meta_roundtrips_local_proxy_request_overrides() {
let meta = ProviderMeta {
local_proxy_request_overrides: Some(LocalProxyRequestOverrides {
headers: HashMap::from([("X-Test".to_string(), "yes".to_string())]),
body: Some(json!({ "temperature": 0.2 })),
}),
..ProviderMeta::default()
};
let value = serde_json::to_value(&meta).expect("serialize ProviderMeta");
assert_eq!(
value["localProxyRequestOverrides"]["headers"]["X-Test"],
"yes"
);
assert_eq!(
value["localProxyRequestOverrides"]["body"]["temperature"],
0.2
);
let decoded: ProviderMeta =
serde_json::from_value(value).expect("deserialize ProviderMeta");
let overrides = decoded.local_proxy_request_overrides.unwrap();
assert_eq!(overrides.headers.get("X-Test"), Some(&"yes".to_string()));
assert_eq!(overrides.body.unwrap()["temperature"], 0.2);
}
#[test]
fn provider_with_id_populates_defaults() {
let settings_config = json!({