mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat: preserve user-selected catalog model in Codex Chat requests
When Codex client sends a model from the catalog (e.g., user selected via /model), preserve that choice instead of always replacing with config.toml's default model. - Check if request model exists in modelCatalog before falling back to config model - Remove CODEX_CHAT_CLIENT_MODEL constant (no longer needed) - Add test coverage for catalog model preservation
This commit is contained in:
@@ -10,6 +10,7 @@ use crate::provider::Provider;
|
||||
use crate::proxy::error::ProxyError;
|
||||
use regex::Regex;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::LazyLock;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
@@ -21,13 +22,6 @@ static CODEX_CLIENT_REGEX: LazyLock<Regex> =
|
||||
/// Codex 适配器
|
||||
pub struct CodexAdapter;
|
||||
|
||||
/// Local model written into Codex config.toml for Chat-only upstreams.
|
||||
///
|
||||
/// Codex itself must see a model it can load metadata for; the real provider
|
||||
/// model is restored inside the proxy immediately before Chat Completions
|
||||
/// conversion.
|
||||
pub const CODEX_CHAT_CLIENT_MODEL: &str = "gpt-5.4";
|
||||
|
||||
/// Whether this Codex provider's real upstream should be called through
|
||||
/// OpenAI Chat Completions, even if the local Codex client is talking to CC
|
||||
/// Switch through the Responses API.
|
||||
@@ -108,8 +102,26 @@ pub fn codex_provider_upstream_model(provider: &Provider) -> Option<String> {
|
||||
})
|
||||
}
|
||||
|
||||
/// For Codex Chat providers, replace the local Codex-safe model with the real
|
||||
/// upstream model before converting the request to Chat Completions.
|
||||
fn codex_provider_catalog_model_ids(provider: &Provider) -> HashSet<String> {
|
||||
provider
|
||||
.settings_config
|
||||
.get("modelCatalog")
|
||||
.and_then(|catalog| catalog.get("models"))
|
||||
.and_then(|models| models.as_array())
|
||||
.map(|models| {
|
||||
models
|
||||
.iter()
|
||||
.filter_map(|model| model.get("model").and_then(|value| value.as_str()))
|
||||
.map(str::trim)
|
||||
.filter(|model| !model.is_empty())
|
||||
.map(ToString::to_string)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// For Codex Chat providers, ensure the request uses the configured upstream
|
||||
/// model before converting the request to Chat Completions.
|
||||
pub fn apply_codex_chat_upstream_model(
|
||||
provider: &Provider,
|
||||
body: &mut JsonValue,
|
||||
@@ -118,6 +130,18 @@ pub fn apply_codex_chat_upstream_model(
|
||||
return None;
|
||||
}
|
||||
|
||||
let catalog_model_ids = codex_provider_catalog_model_ids(provider);
|
||||
if let Some(request_model) = body
|
||||
.get("model")
|
||||
.and_then(|value| value.as_str())
|
||||
.map(str::trim)
|
||||
.filter(|model| !model.is_empty())
|
||||
{
|
||||
if catalog_model_ids.contains(request_model) {
|
||||
return Some(request_model.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let upstream_model = codex_provider_upstream_model(provider)?;
|
||||
body["model"] = JsonValue::String(upstream_model.clone());
|
||||
Some(upstream_model)
|
||||
@@ -567,7 +591,7 @@ wire_api = "responses"
|
||||
..Default::default()
|
||||
});
|
||||
let mut body = json!({
|
||||
"model": CODEX_CHAT_CLIENT_MODEL,
|
||||
"model": "placeholder-client-model",
|
||||
"input": "ping"
|
||||
});
|
||||
|
||||
@@ -579,4 +603,38 @@ wire_api = "responses"
|
||||
Some("deepseek-v4-flash")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_codex_chat_upstream_model_preserves_catalog_model_selection() {
|
||||
let mut provider = create_provider(json!({
|
||||
"config": r#"
|
||||
model_provider = "deepseek"
|
||||
model = "deepseek-v4-flash"
|
||||
|
||||
[model_providers.deepseek]
|
||||
name = "DeepSeek"
|
||||
base_url = "https://api.deepseek.com/v1"
|
||||
wire_api = "responses"
|
||||
"#,
|
||||
"modelCatalog": {
|
||||
"models": [
|
||||
{ "model": "deepseek-v4-flash" },
|
||||
{ "model": "kimi-k2" }
|
||||
]
|
||||
}
|
||||
}));
|
||||
provider.meta = Some(crate::provider::ProviderMeta {
|
||||
api_format: Some("openai_chat".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
let mut body = json!({
|
||||
"model": "kimi-k2",
|
||||
"input": "ping"
|
||||
});
|
||||
|
||||
let upstream_model = apply_codex_chat_upstream_model(&provider, &mut body);
|
||||
|
||||
assert_eq!(upstream_model.as_deref(), Some("kimi-k2"));
|
||||
assert_eq!(body.get("model").and_then(|v| v.as_str()), Some("kimi-k2"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,7 @@ pub use claude::{
|
||||
pub use codex::CodexAdapter;
|
||||
pub use codex::{
|
||||
apply_codex_chat_upstream_model, codex_provider_upstream_model,
|
||||
codex_provider_uses_chat_completions, should_convert_codex_responses_to_chat,
|
||||
CODEX_CHAT_CLIENT_MODEL,
|
||||
should_convert_codex_responses_to_chat,
|
||||
};
|
||||
pub use gemini::GeminiAdapter;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user