mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-05-06 22:01:44 +08:00
fix(proxy): preserve Vertex AI full URLs (#2415)
This commit is contained in:
@@ -81,6 +81,10 @@ fn should_normalize_gemini_full_url(base_url: &str) -> bool {
|
||||
let path = path.trim_end_matches('/');
|
||||
let on_google_host = is_google_gemini_host(extract_host(origin));
|
||||
|
||||
if matches_vertex_ai_publisher_model_path(path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unconditional layer: only paths whose grammar is *intrinsically*
|
||||
// Gemini-specific — the `/models/...:generateContent` method-call
|
||||
// shape and the deep OpenAI-compat endpoints (`/openai/chat/completions`,
|
||||
@@ -238,6 +242,27 @@ fn matches_structured_gemini_models_path(path: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Vertex AI endpoint paths include project/location/publisher routing before
|
||||
/// `models/*:generateContent`; in full-URL mode that routing is user-provided
|
||||
/// and must not be collapsed into the public Gemini `/v1beta/models/*` shape.
|
||||
fn matches_vertex_ai_publisher_model_path(path: &str) -> bool {
|
||||
let Some(projects_index) = path.find("/projects/") else {
|
||||
return false;
|
||||
};
|
||||
let Some(publisher_models_index) = path.find("/publishers/google/models/") else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if projects_index >= publisher_models_index
|
||||
|| !path[projects_index..publisher_models_index].contains("/locations/")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let after_model = &path[publisher_models_index + "/publishers/google/models/".len()..];
|
||||
after_model.contains(":generateContent") || after_model.contains(":streamGenerateContent")
|
||||
}
|
||||
|
||||
fn merge_queries(base_query: Option<&str>, endpoint_query: Option<&str>) -> Option<String> {
|
||||
let parts: Vec<&str> = [base_query, endpoint_query]
|
||||
.into_iter()
|
||||
@@ -334,6 +359,20 @@ mod tests {
|
||||
assert_eq!(url, "https://relay.example/custom/generate-content?alt=sse");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_cloudflare_vertex_ai_full_url_with_action() {
|
||||
let url = resolve_gemini_native_url(
|
||||
"https://gateway.ai.cloudflare.com/v1/account/gateway/google-vertex-ai/v1/projects/project/locations/us-central1/publishers/google/models/gemini-3.1-pro-preview:streamGenerateContent",
|
||||
"/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse",
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://gateway.ai.cloudflare.com/v1/account/gateway/google-vertex-ai/v1/projects/project/locations/us-central1/publishers/google/models/gemini-3.1-pro-preview:streamGenerateContent?alt=sse"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_opaque_full_url_containing_models_segment() {
|
||||
let url = resolve_gemini_native_url(
|
||||
|
||||
@@ -1928,6 +1928,22 @@ mod tests {
|
||||
assert_eq!(url, "https://relay.example/custom/generate-content?alt=sse");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_claude_stream_url_for_gemini_native_cloudflare_vertex_full_url() {
|
||||
let url = StreamCheckService::resolve_claude_stream_url(
|
||||
"https://gateway.ai.cloudflare.com/v1/account/gateway/google-vertex-ai/v1/projects/project/locations/us-central1/publishers/google/models/gemini-3.1-pro-preview:streamGenerateContent",
|
||||
AuthStrategy::Google,
|
||||
"gemini_native",
|
||||
true,
|
||||
"gemini-2.5-flash",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://gateway.ai.cloudflare.com/v1/account/gateway/google-vertex-ai/v1/projects/project/locations/us-central1/publishers/google/models/gemini-3.1-pro-preview:streamGenerateContent?alt=sse"
|
||||
);
|
||||
}
|
||||
|
||||
/// Regression: Gemini SDK outputs commonly surface model ids as the
|
||||
/// resource-name form `models/gemini-2.5-pro`. Interpolating that raw
|
||||
/// value used to produce `/v1beta/models/models/gemini-2.5-pro:...`
|
||||
|
||||
Reference in New Issue
Block a user