fix(provider): normalize responses fallback

* fix(provider): avoid duplicate /v1 in responses endpoint

* fix(provider): derive precise responses endpoint from configured path
This commit is contained in:
Chummy 2026-02-16 18:26:01 +08:00 committed by GitHub
parent 85fc12bcf7
commit 2b04ebd2fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -67,13 +67,42 @@ impl OpenAiCompatibleProvider {
}
}
fn path_ends_with(&self, suffix: &str) -> bool {
if let Ok(url) = reqwest::Url::parse(&self.base_url) {
return url.path().trim_end_matches('/').ends_with(suffix);
}
self.base_url.trim_end_matches('/').ends_with(suffix)
}
fn has_explicit_api_path(&self) -> bool {
let Ok(url) = reqwest::Url::parse(&self.base_url) else {
return false;
};
let path = url.path().trim_end_matches('/');
!path.is_empty() && path != "/"
}
/// Build the full URL for responses API, detecting if base_url already includes the path.
fn responses_url(&self) -> String {
// If base_url already contains "responses", use it as-is
if self.base_url.contains("responses") {
self.base_url.clone()
if self.path_ends_with("/responses") {
return self.base_url.clone();
}
let normalized_base = self.base_url.trim_end_matches('/');
// If chat endpoint is explicitly configured, derive sibling responses endpoint.
if let Some(prefix) = normalized_base.strip_suffix("/chat/completions") {
return format!("{prefix}/responses");
}
// If an explicit API path already exists (e.g. /v1, /openai, /api/coding/v3),
// append responses directly to avoid duplicate /v1 segments.
if self.has_explicit_api_path() {
format!("{normalized_base}/responses")
} else {
format!("{}/v1/responses", self.base_url)
format!("{normalized_base}/v1/responses")
}
}
}
@ -663,6 +692,47 @@ mod tests {
);
}
#[test]
fn responses_url_requires_exact_suffix_match() {
let p = make_provider(
"custom",
"https://my-api.example.com/api/v2/responses-proxy",
None,
);
assert_eq!(
p.responses_url(),
"https://my-api.example.com/api/v2/responses-proxy/responses"
);
}
#[test]
fn responses_url_derives_from_chat_endpoint() {
let p = make_provider(
"custom",
"https://my-api.example.com/api/v2/chat/completions",
None,
);
assert_eq!(
p.responses_url(),
"https://my-api.example.com/api/v2/responses"
);
}
#[test]
fn responses_url_base_with_v1_no_duplicate() {
let p = make_provider("test", "https://api.example.com/v1", None);
assert_eq!(p.responses_url(), "https://api.example.com/v1/responses");
}
#[test]
fn responses_url_non_v1_api_path_uses_raw_suffix() {
let p = make_provider("test", "https://api.example.com/api/coding/v3", None);
assert_eq!(
p.responses_url(),
"https://api.example.com/api/coding/v3/responses"
);
}
#[test]
fn chat_completions_url_without_v1() {
// Provider configured without /v1 in base URL