From 13f6ed7871250630310b23f71e8943c8835aca2f Mon Sep 17 00:00:00 2001 From: Chummy Date: Mon, 16 Feb 2026 14:57:48 +0800 Subject: [PATCH] fix(provider): require exact chat endpoint suffix match (#277) --- src/providers/compatible.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/providers/compatible.rs b/src/providers/compatible.rs index 8a8cd59..d7cbd34 100644 --- a/src/providers/compatible.rs +++ b/src/providers/compatible.rs @@ -48,8 +48,19 @@ impl OpenAiCompatibleProvider { /// This allows custom providers with non-standard endpoints (e.g., VolcEngine ARK uses /// `/api/coding/v3/chat/completions` instead of `/v1/chat/completions`). fn chat_completions_url(&self) -> String { - // If base_url already contains "chat/completions", use it as-is - if self.base_url.contains("chat/completions") { + let has_full_endpoint = reqwest::Url::parse(&self.base_url) + .map(|url| { + url.path() + .trim_end_matches('/') + .ends_with("/chat/completions") + }) + .unwrap_or_else(|_| { + self.base_url + .trim_end_matches('/') + .ends_with("/chat/completions") + }); + + if has_full_endpoint { self.base_url.clone() } else { format!("{}/chat/completions", self.base_url) @@ -618,6 +629,19 @@ mod tests { ); } + #[test] + fn chat_completions_url_requires_exact_suffix_match() { + let p = make_provider( + "custom", + "https://my-api.example.com/v2/llm/chat/completions-proxy", + None, + ); + assert_eq!( + p.chat_completions_url(), + "https://my-api.example.com/v2/llm/chat/completions-proxy/chat/completions" + ); + } + #[test] fn responses_url_standard() { // Standard providers get /v1/responses appended