Skip to content

Commit c74d55a

Browse files
committed
feat(proxy): strip_params 参数过滤机制
- ProfileConfig 新增 strip_params 字段,支持 "auto" / "none" / 列表三种模式 - auto 模式根据 base_url 自动推断:chatgpt.com 端点自动剥离 temperature/top_p/top_k - ProviderAdapter trait 新增 filter_translated_body 默认方法 - handler 在翻译后、发送前统一调用过滤 - 修复 Codex ChatGPT 端点 "Unsupported parameter: temperature" 400 错误 Closes #2
1 parent 1d6a328 commit c74d55a

7 files changed

Lines changed: 91 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config.example.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ enabled = false
123123
# ChatGPT Plus/Pro / Codex subscription (via OpenAI OAuth → Responses API)
124124
# Supported models: gpt-5.1-codex, gpt-5.1-codex-mini, codex-mini-latest
125125
# Note: gpt-4o is NOT supported on the Codex ChatGPT endpoint
126+
# strip_params = "auto" will auto-detect chatgpt.com and strip temperature/top_p/top_k
126127
[[profiles]]
127128
name = "codex-sub"
128129
provider_type = "OpenAIResponses"

src/config.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,74 @@ pub struct ProfileConfig {
9696
/// 最大输出 token 数上限(可选,用于限制转发给 provider 的 max_tokens)
9797
#[serde(default)]
9898
pub max_tokens: Option<u64>,
99+
/// 从翻译后的请求体中剥离的参数名列表
100+
/// 用于处理上游端点不支持某些参数的情况(如 Codex ChatGPT 不支持 temperature)
101+
/// 设为 "auto" 时根据 base_url 自动推断
102+
#[serde(default)]
103+
pub strip_params: StripParams,
104+
}
105+
106+
/// 参数剥离配置
107+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
108+
#[serde(from = "StripParamsRaw")]
109+
pub enum StripParams {
110+
/// 根据 base_url 自动推断需要剥离的参数
111+
#[default]
112+
Auto,
113+
/// 不剥离任何参数
114+
None,
115+
/// 剥离指定的参数列表
116+
List(Vec<String>),
117+
}
118+
119+
#[derive(Deserialize)]
120+
#[serde(untagged)]
121+
enum StripParamsRaw {
122+
Str(String),
123+
List(Vec<String>),
124+
}
125+
126+
impl From<StripParamsRaw> for StripParams {
127+
fn from(raw: StripParamsRaw) -> Self {
128+
match raw {
129+
StripParamsRaw::Str(s) => match s.to_lowercase().as_str() {
130+
"auto" => StripParams::Auto,
131+
"none" | "false" | "" => StripParams::None,
132+
_ => StripParams::List(
133+
s.split(',')
134+
.map(|p| p.trim().to_string())
135+
.filter(|p| !p.is_empty())
136+
.collect(),
137+
),
138+
},
139+
StripParamsRaw::List(list) => StripParams::List(list),
140+
}
141+
}
142+
}
143+
144+
impl StripParams {
145+
/// 解析实际要剥离的参数列表,Auto 模式根据 base_url 推断
146+
pub fn resolve(&self, base_url: &str) -> Vec<String> {
147+
match self {
148+
StripParams::None => vec![],
149+
StripParams::List(list) => list.clone(),
150+
StripParams::Auto => Self::infer_from_url(base_url),
151+
}
152+
}
153+
154+
/// 已知端点的参数兼容性规则
155+
fn infer_from_url(base_url: &str) -> Vec<String> {
156+
if base_url.contains("chatgpt.com") {
157+
// Codex ChatGPT 端点不支持采样参数
158+
vec![
159+
"temperature".to_string(),
160+
"top_p".to_string(),
161+
"top_k".to_string(),
162+
]
163+
} else {
164+
vec![]
165+
}
166+
}
99167
}
100168

101169
/// Claude Code 模型 slot 映射
@@ -386,6 +454,7 @@ mod tests {
386454
oauth_provider: None,
387455
models: ProfileModels::default(),
388456
max_tokens: None,
457+
strip_params: StripParams::default(),
389458
}
390459
}
391460

src/oauth/providers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ fn ensure_oauth_profile(
7878
oauth_provider: Some(provider.clone()),
7979
models: ProfileModels::default(),
8080
max_tokens: None,
81+
strip_params: crate::config::StripParams::default(),
8182
};
8283

8384
config.profiles.push(profile);

src/profile.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ pub async fn interactive_add(config: &mut ClaudexConfig) -> Result<()> {
279279
oauth_provider: None,
280280
models: ProfileModels::default(),
281281
max_tokens: None,
282+
strip_params: crate::config::StripParams::default(),
282283
};
283284

284285
// Test connectivity

src/proxy/adapter/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ pub trait ProviderAdapter: Send + Sync {
2727
fn translate_request(&self, body: &Value, profile: &ProfileConfig)
2828
-> Result<TranslatedRequest>;
2929

30+
/// 根据 profile 的 strip_params 配置过滤翻译后的请求体
31+
fn filter_translated_body(&self, body: &mut Value, profile: &ProfileConfig) {
32+
let params_to_strip = profile.strip_params.resolve(&profile.base_url);
33+
if let Some(obj) = body.as_object_mut() {
34+
for param in &params_to_strip {
35+
if obj.remove(param).is_some() {
36+
tracing::debug!(
37+
"stripped unsupported param '{}' for {}",
38+
param,
39+
profile.name
40+
);
41+
}
42+
}
43+
}
44+
}
45+
3046
/// 设置认证头
3147
fn apply_auth(&self, builder: RequestBuilder, profile: &ProfileConfig) -> RequestBuilder;
3248

src/proxy/handler.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ async fn try_forward(
304304
is_streaming: bool,
305305
) -> anyhow::Result<Response> {
306306
let adapter = super::adapter::for_provider(&profile.provider_type);
307-
let translated = adapter.translate_request(body, profile)?;
307+
let mut translated = adapter.translate_request(body, profile)?;
308+
adapter.filter_translated_body(&mut translated.body, profile);
308309

309310
let url = format!(
310311
"{}{}",

0 commit comments

Comments
 (0)