From f183501fbd2199d9d10040e1a63392de604f172c Mon Sep 17 00:00:00 2001 From: Hunter Bown Date: Fri, 8 May 2026 02:59:56 -0500 Subject: [PATCH] fix(client): route non-beta paths from beta base to v1 (#1174) * test: add reproducer for /models 404 on beta base URL * fix: route non-beta paths to /v1 when base URL ends with /beta --------- Co-authored-by: Hanmiao Li <894876246@qq.com> --- crates/tui/src/client.rs | 46 ++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/crates/tui/src/client.rs b/crates/tui/src/client.rs index 38ff11d3..84ec1b71 100644 --- a/crates/tui/src/client.rs +++ b/crates/tui/src/client.rs @@ -394,11 +394,16 @@ pub(super) fn api_url(base_url: &str, path: &str) -> String { if path.starts_with("beta/") { return format!("{}/{}", unversioned_base_url(base_url), path); } - format!( - "{}/{}", - versioned_base_url(base_url).trim_end_matches('/'), - path - ) + let mut versioned = versioned_base_url(base_url); + // The /beta suffix is not a real API version — it is an + // opt-in surface for beta features. Only paths with an + // explicit `beta/` prefix should hit the beta surface; + // everything else (models, chat/completions, health, …) + // must go to the standard /v1 surface. + if versioned.ends_with("beta") { + versioned = format!("{}/v1", unversioned_base_url(base_url)); + } + format!("{}/{}", versioned.trim_end_matches('/'), path) } // === DeepSeekClient === @@ -1042,9 +1047,11 @@ mod tests { api_url("https://api.deepseek.com/v1", "chat/completions"), "https://api.deepseek.com/v1/chat/completions" ); + // Non-beta paths from a /beta base URL route to /v1. + // Only paths with an explicit beta/ prefix use the beta surface. assert_eq!( api_url("https://api.deepseek.com/beta", "chat/completions"), - "https://api.deepseek.com/beta/chat/completions" + "https://api.deepseek.com/v1/chat/completions" ); assert_eq!( api_url( @@ -1071,6 +1078,33 @@ mod tests { ); } + #[test] + fn api_url_routes_models_and_non_beta_paths_to_v1() { + // The /models endpoint only exists at /v1/models, never at + // /beta/models. Non-beta paths from a /beta base URL must + // still route to /v1. + assert_eq!( + api_url("https://api.deepseek.com", "models"), + "https://api.deepseek.com/v1/models" + ); + assert_eq!( + api_url("https://api.deepseek.com/v1", "models"), + "https://api.deepseek.com/v1/models" + ); + assert_eq!( + api_url("https://api.deepseek.com/beta", "models"), + "https://api.deepseek.com/v1/models" + ); + // explicit v versions other than /v1 should be preserved + assert_eq!( + api_url( + "https://openai-compatible.example/api/coding/paas/v4", + "models" + ), + "https://openai-compatible.example/api/coding/paas/v4/models" + ); + } + #[test] fn default_headers_include_custom_headers_when_configured() { let mut extra = HashMap::new();