mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat(proxy): add GET /v1/models endpoint for Codex CLI reachability check (#3818)
* feat(proxy): add GET /v1/models endpoint for Codex CLI reachability check Codex CLI probes GET /v1/models at startup. Without this endpoint the proxy returns 404, causing Codex to fail before any request reaches the upstream LLM. Return an OpenAI-compatible model list derived from the cc-switch–managed model catalog file. Fixes #3812 * fix(proxy): return Codex catalog schema from /v1/models Codex deserializes the response as a catalog with a top-level `models` field, not the OpenAI `{"object":"list","data":[...]}` envelope. Return the catalog file content directly so the format matches what Codex expects. Co-authored-by: Codex review bot * fix(proxy): guard /v1/models against serving stale catalog Only return the model catalog when config.toml still references it via `model_catalog_json`. After switching to a provider without a custom catalog, the old file lingers on disk — serving it unconditionally would advertise the previous provider's models to Codex. Co-authored-by: Codex review bot * fix(proxy): match relative model_catalog_json in stale-guard cc-switch writes `model_catalog_json = "cc-switch-model-catalog.json"` (relative) via set_codex_model_catalog_json_field. Match on the filename constant rather than the absolute path so the guard works with both relative and absolute paths. Co-authored-by: Codex review bot * fix(proxy): parse model_catalog_json field instead of substring match Replace raw config_text.contains() with proper TOML field parsing so commented-out lines and stray mentions of the filename in other fields don't defeat the stale guard. Also switch from contains() to exact filename match (Path::new(val).file_name() == Some(...)) to stay consistent with resolve_cc_switch_catalog_path in codex_config.rs. Add log::debug! when the guard blocks serving so the operator can distinguish "no models configured" from "guard blocked stale catalog". * refactor(proxy): reuse resolve_cc_switch_catalog_path in handle_models Replace the inline config.toml parsing and filename match in handle_models with the existing resolve_cc_switch_catalog_path helper (now pub(crate)). This removes the duplicated stale-guard logic, keeps a single source of truth for catalog-path ownership, and makes the handler honor absolute model_catalog_json paths the same way Codex live-setting import does. --------- Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
@@ -766,7 +766,10 @@ pub fn read_codex_model_catalog_simplified_from_live() -> Result<Option<Value>,
|
||||
/// Given `config.toml` text, resolve the on-disk path of the cc-switch–owned
|
||||
/// catalog file (returns `None` if `model_catalog_json` is absent or points at
|
||||
/// a file we don't own). Relative paths fall back to `generated_path`.
|
||||
fn resolve_cc_switch_catalog_path(config_text: &str, generated_path: &Path) -> Option<PathBuf> {
|
||||
pub(crate) fn resolve_cc_switch_catalog_path(
|
||||
config_text: &str,
|
||||
generated_path: &Path,
|
||||
) -> Option<PathBuf> {
|
||||
if config_text.trim().is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,41 @@ pub async fn get_status(State(state): State<ProxyState>) -> Result<Json<ProxySta
|
||||
Ok(Json(status))
|
||||
}
|
||||
|
||||
/// GET /v1/models — Codex model list (reachability check)
|
||||
///
|
||||
/// Codex CLI probes this endpoint at startup and deserializes the response as a
|
||||
/// catalog with a top-level `models` field. Return the cc-switch–managed model
|
||||
/// catalog file directly so the format always matches what the current version
|
||||
/// of Codex expects.
|
||||
///
|
||||
/// Only serves the catalog when the live config.toml still references the
|
||||
/// cc-switch–owned `model_catalog_json`, using the same path ownership rules as
|
||||
/// Codex live-setting import.
|
||||
pub async fn handle_models() -> Result<Json<Value>, ProxyError> {
|
||||
let generated_path = crate::codex_config::get_codex_model_catalog_path();
|
||||
let active_catalog_path = match crate::codex_config::read_codex_config_text() {
|
||||
Ok(config_text) => {
|
||||
crate::codex_config::resolve_cc_switch_catalog_path(&config_text, &generated_path)
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
let catalog = if let Some(catalog_path) =
|
||||
active_catalog_path.as_ref().filter(|path| path.exists())
|
||||
{
|
||||
let text = std::fs::read_to_string(catalog_path).unwrap_or_default();
|
||||
serde_json::from_str(&text).unwrap_or(json!({"models": []}))
|
||||
} else {
|
||||
if active_catalog_path.is_none() {
|
||||
log::debug!(
|
||||
"[models] stale guard: catalog not served (model_catalog_json not set to cc-switch catalog)"
|
||||
);
|
||||
}
|
||||
json!({"models": []})
|
||||
};
|
||||
Ok(Json(catalog))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Claude API 处理器(包含格式转换逻辑)
|
||||
// ============================================================================
|
||||
|
||||
@@ -315,6 +315,9 @@ impl ProxyServer {
|
||||
"/codex/v1/chat/completions",
|
||||
post(handlers::handle_chat_completions),
|
||||
)
|
||||
// OpenAI Models API (Codex CLI reachability check)
|
||||
.route("/models", get(handlers::handle_models))
|
||||
.route("/v1/models", get(handlers::handle_models))
|
||||
// OpenAI Responses API (Codex CLI,支持带前缀和不带前缀)
|
||||
.route("/responses", post(handlers::handle_responses))
|
||||
.route("/v1/responses", post(handlers::handle_responses))
|
||||
|
||||
Reference in New Issue
Block a user