feat(proxy): add full URL mode and refactor endpoint rewriting

- Add `isFullUrl` provider meta to treat base_url as complete API endpoint
- Remove hardcoded `?beta=true` from Claude adapter, pass through from client
- Refactor forwarder endpoint rewriting with proper query string handling
- Block provider switching when proxy is required but not running
- Add full URL toggle UI in endpoint field with i18n (zh/en/ja)
This commit is contained in:
YoVinchen
2026-03-16 14:22:54 +08:00
parent 36bbdc36f5
commit eeda9adb03
15 changed files with 313 additions and 55 deletions
+61 -16
View File
@@ -312,22 +312,12 @@ impl StreamCheckService {
.unwrap_or("anthropic");
let is_openai_chat = api_format == "openai_chat";
// URL: /v1/chat/completions for openai_chat, /v1/messages?beta=true for anthropic
let url = if is_openai_chat {
if base.ends_with("/v1") {
format!("{base}/chat/completions")
} else {
format!("{base}/v1/chat/completions")
}
} else {
// ?beta=true is required by some relay services to verify request origin
if base.ends_with("/v1") {
format!("{base}/messages?beta=true")
} else {
format!("{base}/v1/messages?beta=true")
}
};
let is_full_url = provider
.meta
.as_ref()
.and_then(|meta| meta.is_full_url)
.unwrap_or(false);
let url = Self::build_claude_stream_check_url(base, is_openai_chat, is_full_url);
// Body: identical structure for minimal test (both APIs accept messages array)
let body = json!({
@@ -692,6 +682,28 @@ impl StreamCheckService {
other => other,
}
}
fn build_claude_stream_check_url(
base_url: &str,
is_openai_chat: bool,
is_full_url: bool,
) -> String {
if is_full_url {
return base_url.to_string();
}
if is_openai_chat {
if base_url.ends_with("/v1") {
format!("{base_url}/chat/completions")
} else {
format!("{base_url}/v1/chat/completions")
}
} else if base_url.ends_with("/v1") {
format!("{base_url}/messages?beta=true")
} else {
format!("{base_url}/v1/messages?beta=true")
}
}
}
#[cfg(test)]
@@ -794,4 +806,37 @@ mod tests {
assert_eq!(claude_auth, AuthStrategy::ClaudeAuth);
assert_eq!(bearer, AuthStrategy::Bearer);
}
#[test]
fn test_build_claude_stream_check_url_for_full_url_mode() {
let url = StreamCheckService::build_claude_stream_check_url(
"https://relay.example/v1/chat/completions",
true,
true,
);
assert_eq!(url, "https://relay.example/v1/chat/completions");
}
#[test]
fn test_build_claude_stream_check_url_for_openai_chat() {
let url = StreamCheckService::build_claude_stream_check_url(
"https://relay.example/v1",
true,
false,
);
assert_eq!(url, "https://relay.example/v1/chat/completions");
}
#[test]
fn test_build_claude_stream_check_url_for_anthropic() {
let url = StreamCheckService::build_claude_stream_check_url(
"https://relay.example",
false,
false,
);
assert_eq!(url, "https://relay.example/v1/messages?beta=true");
}
}