Refine protocol probe filtering for Gemini

This commit is contained in:
musistudio 2026-07-03 10:31:39 +08:00
parent f13e3b4598
commit d45e88387a
6 changed files with 78 additions and 11 deletions

View file

@ -535,7 +535,7 @@ async function probeProtocolConnectivity(
for (const candidate of endpoints) {
const result = await requestJson(candidate.endpoint, requestForProtocol(protocol, model, apiKey));
const message = readResponseMessage(result);
const supported = isProtocolSupported(result.status, message);
const supported = isProtocolSupported(result.status, message, protocol);
const probeResult = {
baseUrl: candidate.baseUrl,
endpoint: candidate.endpoint,
@ -982,7 +982,11 @@ function protocolHints(value: string): GatewayProviderProtocol[] {
return hints;
}
function isProtocolSupported(status: number | undefined, message: string): boolean {
function isProtocolSupported(
status: number | undefined,
message: string,
protocol?: GatewayProviderProtocol
): boolean {
if (status === undefined) {
return false;
}
@ -997,7 +1001,9 @@ function isProtocolSupported(status: number | undefined, message: string): boole
if (status === 400) {
const normalized = message.toLowerCase();
return /model|max_tokens|max output|messages|input|required/.test(normalized) && !/not found|unknown endpoint|unknown route|no route/.test(normalized);
const schemaError = /model|max_tokens|max output|messages|input|required/.test(normalized) ||
(protocol === "gemini_generate_content" && /contents|generatecontentrequest|generationconfig/.test(normalized));
return schemaError && !/not found|unknown endpoint|unknown route|no route/.test(normalized);
}
return false;
@ -1009,7 +1015,7 @@ export function isProviderProtocolEndpointSupportedForProbe(
protocol: GatewayProviderProtocol,
hints: GatewayProviderProtocol[] = []
): boolean {
if (isProtocolSupported(status, message)) {
if (isProtocolSupported(status, message, protocol)) {
return true;
}

View file

@ -1361,7 +1361,7 @@ function App() {
const fallbackBaseUrl = probe?.normalizedBaseUrl || providerDraft.baseUrl;
const selectableProtocols = providerSelectableProtocolsFromProbe(probe);
const selectedProtocols = providerDraft.selectedProtocols.length > 0
? providerDraft.selectedProtocols.filter((protocol) => selectableProtocols.length === 0 || selectableProtocols.includes(protocol))
? providerDraft.selectedProtocols.filter((protocol) => !probe || selectableProtocols.includes(protocol))
: [];
if (selectableProtocols.length > 0 && selectedProtocols.length === 0) {
setProviderProbeError(t("Select at least one protocol."));

View file

@ -1680,7 +1680,7 @@ export function AddProviderForm({
<div className="space-y-1.5">
{probe.protocols.map((item) => {
const selectable = item.supported && selectableProtocols.includes(item.protocol);
const checked = draft.selectedProtocols.includes(item.protocol);
const checked = selectable && draft.selectedProtocols.includes(item.protocol);
return (
<div className="grid grid-cols-[20px_minmax(118px,0.7fr)_72px_minmax(0,1fr)] items-center gap-2 text-[11px]" key={`${item.protocol}-${item.endpoint}`}>
<Checkbox

View file

@ -1750,10 +1750,7 @@ export function providerSelectableProtocolsFromProbe(probe: GatewayProviderProbe
return [];
}
return uniqueProviderProtocols([
...(probe.capabilities ?? []).map((capability) => capability.type),
...probe.protocols.filter((item) => item.supported).map((item) => item.protocol)
]);
return uniqueProviderProtocols(probe.protocols.filter((item) => item.supported).map((item) => item.protocol));
}
export function selectedProviderProtocolsFromCapabilities(
@ -1772,7 +1769,7 @@ export function selectedProviderProtocolsForProbe(
): GatewayProviderProtocol[] {
const selectable = providerSelectableProtocolsFromProbe(probe);
if (selectable.length === 0) {
return selectedProtocols.length > 0 ? uniqueProviderProtocols(selectedProtocols) : [fallback];
return [];
}
if (selectedProtocolsMatchPresetDefault(selectedProtocols, presetId)) {

View file

@ -32,3 +32,16 @@ test("protocol support probe keeps auth-only fallback for unhinted endpoints", (
true
);
});
test("protocol support probe treats Gemini contents validation as Gemini support only", () => {
const message = "HTTP 400: * GenerateContentRequest.contents: contents is not specified";
assert.equal(
isProviderProtocolEndpointSupportedForProbe(400, message, "gemini_generate_content", ["gemini_generate_content"]),
true
);
assert.equal(
isProviderProtocolEndpointSupportedForProbe(400, message, "openai_chat_completions", ["openai_chat_completions"]),
false
);
});

View file

@ -2,6 +2,7 @@ import assert from "node:assert/strict";
import test from "node:test";
import { geminiProviderPreset } from "../../src/main/presets/gemini/index.ts";
import {
applyProviderProbeResult,
createProviderDraft,
providerProtocolOptions,
providerProbeCandidates,
@ -20,3 +21,53 @@ test("Gemini preset keeps full protocol probing candidates", () => {
assert.equal(candidates.length, 1);
assert.deepEqual(candidates[0].protocols, providerProtocolOptions.map((option) => option.value));
});
test("provider probe result drops unavailable selected protocols", () => {
const draft = {
...createProviderDraft([]),
baseUrl: "https://generativelanguage.googleapis.com",
protocol: "gemini_generate_content",
selectedProtocols: providerProtocolOptions.map((option) => option.value)
};
const next = applyProviderProbeResult(draft, {
capabilities: [],
detectedProtocol: "gemini_generate_content",
models: [],
normalizedBaseUrl: draft.baseUrl,
protocols: providerProtocolOptions.map((option) => ({
endpoint: draft.baseUrl,
message: "HTTP 404",
protocol: option.value,
status: 404,
supported: false
}))
});
assert.deepEqual(next.selectedProtocols, []);
});
test("provider probe result keeps only supported selected protocols", () => {
const draft = {
...createProviderDraft([]),
baseUrl: "https://generativelanguage.googleapis.com",
protocol: "gemini_generate_content",
selectedProtocols: providerProtocolOptions.map((option) => option.value)
};
const next = applyProviderProbeResult(draft, {
capabilities: [],
detectedProtocol: "gemini_generate_content",
models: [],
normalizedBaseUrl: draft.baseUrl,
protocols: providerProtocolOptions.map((option) => ({
endpoint: draft.baseUrl,
message: option.value === "gemini_generate_content" ? "HTTP 400: contents is not specified" : "HTTP 404",
protocol: option.value,
status: option.value === "gemini_generate_content" ? 400 : 404,
supported: option.value === "gemini_generate_content"
}))
});
assert.deepEqual(next.selectedProtocols, ["gemini_generate_content"]);
});