From 8b0e1cc3c0b3c09fcae46b7cd88717b993529203 Mon Sep 17 00:00:00 2001 From: RefuseOdd <192543033+RefuseOdd@users.noreply.github.com> Date: Tue, 2 Jun 2026 17:42:33 +1200 Subject: [PATCH] Limit path suffix to chat completions --- crates/tui/src/client.rs | 58 ++++++++++++++++++++++++++++++----- crates/tui/src/client/chat.rs | 14 +++++++-- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/crates/tui/src/client.rs b/crates/tui/src/client.rs index f5ac56a6..24815a2e 100644 --- a/crates/tui/src/client.rs +++ b/crates/tui/src/client.rs @@ -428,8 +428,14 @@ pub(super) fn api_url_with_suffix(base_url: &str, path: &str, path_suffix: Optio if path.starts_with("beta/") { return format!("{}/{}", unversioned_base_url(base_url), path); } - if let Some(suffix) = path_suffix { - return format!("{}/{}", base_url.trim_end_matches('/'), suffix.trim_start_matches('/')); + if path == "chat/completions" { + if let Some(suffix) = path_suffix { + return format!( + "{}/{}", + unversioned_base_url(base_url), + suffix.trim_start_matches('/') + ); + } } let mut versioned = versioned_base_url(base_url); // The /beta suffix is not a real API version — it is an @@ -703,7 +709,11 @@ impl DeepSeekClient { model: &str, target_language: &str, ) -> Result { - let url = api_url_with_suffix(&self.base_url, "chat/completions", self.path_suffix.as_deref()); + let url = api_url_with_suffix( + &self.base_url, + "chat/completions", + self.path_suffix.as_deref(), + ); let model = wire_model_for_provider(self.api_provider, model); let mut body = serde_json::json!({ "model": model, @@ -750,7 +760,7 @@ impl DeepSeekClient { /// List available models from the provider. pub async fn list_models(&self) -> Result> { - let url = api_url_with_suffix(&self.base_url, "models", self.path_suffix.as_deref()); + let url = api_url(&self.base_url, "models"); let response = self.send_with_retry(|| self.http_client.get(&url)).await?; let status = response.status(); @@ -892,7 +902,7 @@ impl DeepSeekClient { if !should_probe { return; } - let health_url = api_url_with_suffix(&self.base_url, "models", self.path_suffix.as_deref()); + let health_url = api_url(&self.base_url, "models"); let probe = self.http_client.get(health_url).send().await; match probe { Ok(resp) if resp.status().is_success() => { @@ -1008,7 +1018,7 @@ impl LlmClient for DeepSeekClient { } async fn health_check(&self) -> Result { - let health_url = api_url_with_suffix(&self.base_url, "models", self.path_suffix.as_deref()); + let health_url = api_url(&self.base_url, "models"); self.wait_for_rate_limit().await; let response = self.http_client.get(health_url).send().await; match response { @@ -1331,7 +1341,7 @@ impl DeepSeekClient { suffix: &str, max_tokens: u32, ) -> anyhow::Result { - let url = api_url_with_suffix(&self.base_url, "beta/completions", self.path_suffix.as_deref()); + let url = api_url_with_suffix(&self.base_url, "beta/completions", None); let model = wire_model_for_provider(self.api_provider, model); let body = json!({ "model": model, @@ -3483,7 +3493,7 @@ mod tests { } #[test] - fn api_url_with_suffix_uses_suffix_path() { + fn api_url_with_suffix_strips_version_before_chat_suffix() { assert_eq!( api_url_with_suffix( "https://api.example.com/v1", @@ -3492,6 +3502,14 @@ mod tests { ), "https://api.example.com/chat/completions" ); + assert_eq!( + api_url_with_suffix( + "https://api.example.com/beta", + "chat/completions", + Some("/chat/completions") + ), + "https://api.example.com/chat/completions" + ); } #[test] @@ -3506,6 +3524,30 @@ mod tests { ); } + #[test] + fn api_url_with_suffix_ignores_suffix_for_models() { + assert_eq!( + api_url_with_suffix( + "https://api.example.com/v1", + "models", + Some("/chat/completions") + ), + "https://api.example.com/v1/models" + ); + } + + #[test] + fn api_url_with_suffix_ignores_suffix_for_beta_paths() { + assert_eq!( + api_url_with_suffix( + "https://api.example.com/v1", + "beta/completions", + Some("/chat/completions") + ), + "https://api.example.com/beta/completions" + ); + } + #[test] fn api_url_with_suffix_default_behavior_without_suffix() { assert_eq!( diff --git a/crates/tui/src/client/chat.rs b/crates/tui/src/client/chat.rs index e8fb42c4..3aa217b0 100644 --- a/crates/tui/src/client/chat.rs +++ b/crates/tui/src/client/chat.rs @@ -70,7 +70,7 @@ use crate::models::{ use super::{ DeepSeekClient, ERROR_BODY_MAX_BYTES, SSE_BACKPRESSURE_HIGH_WATERMARK, - SSE_BACKPRESSURE_SLEEP_MS, SSE_MAX_LINES_PER_CHUNK, acquire_stream_buffer, api_url, + SSE_BACKPRESSURE_SLEEP_MS, SSE_MAX_LINES_PER_CHUNK, acquire_stream_buffer, api_url_with_suffix, apply_reasoning_effort, bounded_error_text, from_api_tool_name, parse_usage, release_stream_buffer, system_to_instructions, to_api_tool_name, }; @@ -137,7 +137,11 @@ impl DeepSeekClient { self.api_provider, ); - let url = api_url(&self.base_url, "chat/completions"); + let url = api_url_with_suffix( + &self.base_url, + "chat/completions", + self.path_suffix.as_deref(), + ); let open_timeout = stream_open_timeout(); let response = match tokio_timeout( open_timeout, @@ -245,7 +249,11 @@ impl DeepSeekClient { self.api_provider, ); - let url = api_url(&self.base_url, "chat/completions"); + let url = api_url_with_suffix( + &self.base_url, + "chat/completions", + self.path_suffix.as_deref(), + ); let response = self .send_with_retry(|| self.http_client.post(&url).json(&body)) .await?;