diff --git a/src/main/provider-probe.ts b/src/main/provider-probe.ts index 917e540..b1d7389 100644 --- a/src/main/provider-probe.ts +++ b/src/main/provider-probe.ts @@ -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; } diff --git a/src/renderer/pages/home/App.tsx b/src/renderer/pages/home/App.tsx index 6fcdbf2..99fdea4 100644 --- a/src/renderer/pages/home/App.tsx +++ b/src/renderer/pages/home/App.tsx @@ -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.")); diff --git a/src/renderer/pages/home/components/providers.tsx b/src/renderer/pages/home/components/providers.tsx index 70cf187..8c50a3c 100644 --- a/src/renderer/pages/home/components/providers.tsx +++ b/src/renderer/pages/home/components/providers.tsx @@ -1680,7 +1680,7 @@ export function AddProviderForm({
{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 (
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)) { diff --git a/tests/main/provider-probe.test.mjs b/tests/main/provider-probe.test.mjs index 3038f7f..1291117 100644 --- a/tests/main/provider-probe.test.mjs +++ b/tests/main/provider-probe.test.mjs @@ -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 + ); +}); diff --git a/tests/renderer/providers.test.ts b/tests/renderer/providers.test.ts index 6a59f8f..5f0a1f0 100644 --- a/tests/renderer/providers.test.ts +++ b/tests/renderer/providers.test.ts @@ -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"]); +});