From 4347068a092b4df869c86529a354188d76f8326e Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Sun, 26 Jul 2026 16:59:54 +0300 Subject: [PATCH 1/6] refactor: finish canonical provider identifier audit --- .../src/__tests__/provider-settings.test.ts | 49 ++++++++++++++++++- packages/types/src/provider-settings.ts | 17 +++++-- .../__tests__/checkExistApiConfig.spec.ts | 43 +++++++++++++++- src/shared/checkExistApiConfig.ts | 13 +++-- 4 files changed, 112 insertions(+), 10 deletions(-) diff --git a/packages/types/src/__tests__/provider-settings.test.ts b/packages/types/src/__tests__/provider-settings.test.ts index 724fc20f34..5422613dc7 100644 --- a/packages/types/src/__tests__/provider-settings.test.ts +++ b/packages/types/src/__tests__/provider-settings.test.ts @@ -1,4 +1,4 @@ -import { getApiProtocol } from "../provider-settings.js" +import { getApiProtocol, providerIdentifiers, type ProviderName } from "../index.js" describe("getApiProtocol", () => { describe("Anthropic-style providers", () => { @@ -15,6 +15,19 @@ describe("getApiProtocol", () => { }) describe("Vertex provider with Claude models", () => { + it("uses the canonical Vertex identifier for Claude protocol selection", () => { + const identifiers = providerIdentifiers as Record + const originalIdentifier = identifiers.vertex! + + try { + identifiers.vertex = "canonical-vertex" + + expect(getApiProtocol(identifiers.vertex as ProviderName, "claude-3-opus")).toBe("anthropic") + } finally { + identifiers.vertex = originalIdentifier + } + }) + it("should return 'anthropic' for vertex provider with claude models", () => { expect(getApiProtocol("vertex", "claude-3-opus")).toBe("anthropic") expect(getApiProtocol("vertex", "Claude-3-Sonnet")).toBe("anthropic") @@ -34,6 +47,27 @@ describe("getApiProtocol", () => { }) describe("Vercel AI Gateway provider", () => { + it("uses canonical gateway identifiers for Anthropic model protocol selection", () => { + const identifiers = providerIdentifiers as Record + const originalVercelIdentifier = identifiers.vercelAiGateway! + const originalZooIdentifier = identifiers.zooGateway! + + try { + identifiers.vercelAiGateway = "canonical-vercel-ai-gateway" + identifiers.zooGateway = "canonical-zoo-gateway" + + expect(getApiProtocol(identifiers.vercelAiGateway as ProviderName, "anthropic/claude-3-opus")).toBe( + "anthropic", + ) + expect(getApiProtocol(identifiers.zooGateway as ProviderName, "anthropic/claude-3-opus")).toBe( + "anthropic", + ) + } finally { + identifiers.vercelAiGateway = originalVercelIdentifier + identifiers.zooGateway = originalZooIdentifier + } + }) + it("should return 'anthropic' for vercel-ai-gateway provider with anthropic models", () => { expect(getApiProtocol("vercel-ai-gateway", "anthropic/claude-3-opus")).toBe("anthropic") expect(getApiProtocol("vercel-ai-gateway", "anthropic/claude-3.5-sonnet")).toBe("anthropic") @@ -54,6 +88,19 @@ describe("getApiProtocol", () => { }) describe("Opencode Go provider", () => { + it("uses the canonical OpenCode Go identifier for model-specific protocol selection", () => { + const identifiers = providerIdentifiers as Record + const originalIdentifier = identifiers.opencodeGo! + + try { + identifiers.opencodeGo = "canonical-opencode-go" + + expect(getApiProtocol(identifiers.opencodeGo as ProviderName, "qwen3.7-max")).toBe("anthropic") + } finally { + identifiers.opencodeGo = originalIdentifier + } + }) + it("should return 'anthropic' for opencode-go Anthropic-format models (Qwen/MiniMax)", () => { expect(getApiProtocol("opencode-go", "qwen3.7-max")).toBe("anthropic") expect(getApiProtocol("opencode-go", "qwen3.7-plus")).toBe("anthropic") diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 3898c65bcc..0cbdfdf37f 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -581,21 +581,25 @@ export const modelIdKeysByProvider: Record = { */ // Providers that use Anthropic-style API protocol. -export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "bedrock", "minimax"] +export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = [ + providerIdentifiers.anthropic, + providerIdentifiers.bedrock, + providerIdentifiers.minimax, +] export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => { if (provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider)) { return "anthropic" } - if (provider && provider === "vertex" && modelId && modelId.toLowerCase().includes("claude")) { + if (provider && provider === providerIdentifiers.vertex && modelId && modelId.toLowerCase().includes("claude")) { return "anthropic" } // Vercel AI Gateway and Zoo Gateway use the anthropic protocol for anthropic models. if ( provider && - ["vercel-ai-gateway", "zoo-gateway"].includes(provider) && + (provider === providerIdentifiers.vercelAiGateway || provider === providerIdentifiers.zooGateway) && modelId && modelId.toLowerCase().startsWith("anthropic/") ) { @@ -609,7 +613,12 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str // models must use the anthropic protocol so token/cost aggregation adds the // cache tokens back into the input total — otherwise the cached prefix is // dropped from `contextTokens`, undercounting context-window usage. - if (provider && provider === "opencode-go" && modelId && isOpencodeGoAnthropicFormatModel(modelId)) { + if ( + provider && + provider === providerIdentifiers.opencodeGo && + modelId && + isOpencodeGoAnthropicFormatModel(modelId) + ) { return "anthropic" } diff --git a/src/shared/__tests__/checkExistApiConfig.spec.ts b/src/shared/__tests__/checkExistApiConfig.spec.ts index 5c32e567e2..3afa835985 100644 --- a/src/shared/__tests__/checkExistApiConfig.spec.ts +++ b/src/shared/__tests__/checkExistApiConfig.spec.ts @@ -1,6 +1,6 @@ // npx vitest run src/shared/__tests__/checkExistApiConfig.spec.ts -import type { ProviderSettings } from "@roo-code/types" +import { providerIdentifiers, type ProviderSettings } from "@roo-code/types" import { checkExistKey } from "../checkExistApiConfig" @@ -66,6 +66,19 @@ describe("checkExistKey", () => { expect(checkExistKey(config)).toBe(true) }) + it("recognizes keyless providers through their canonical identifiers", () => { + const identifiers = providerIdentifiers as Record + const originalIdentifier = identifiers.fakeAi! + + try { + identifiers.fakeAi = "canonical-fake-ai" + + expect(checkExistKey({ apiProvider: identifiers.fakeAi } as ProviderSettings)).toBe(true) + } finally { + identifiers.fakeAi = originalIdentifier + } + }) + it("should return true for openai-codex provider without API key", () => { const config: ProviderSettings = { apiProvider: "openai-codex", @@ -95,6 +108,21 @@ describe("checkExistKey", () => { expect(checkExistKey(config)).toBe(true) }) + it("recognizes OAuth authentication through the canonical Kimi Code identifier", () => { + const identifiers = providerIdentifiers as Record + const originalIdentifier = identifiers.kimiCode! + + try { + identifiers.kimiCode = "canonical-kimi-code" + + expect( + checkExistKey({ apiProvider: identifiers.kimiCode, kimiCodeAuthMethod: "oauth" } as ProviderSettings), + ).toBe(true) + } finally { + identifiers.kimiCode = originalIdentifier + } + }) + it("should return true for kimi-code provider without auth method (defaults to OAuth)", () => { const config: ProviderSettings = { apiProvider: "kimi-code", @@ -128,6 +156,19 @@ describe("checkExistKey", () => { expect(checkExistKey(config, false)).toBe(false) }) + it("recognizes session authentication through the canonical Zoo Gateway identifier", () => { + const identifiers = providerIdentifiers as Record + const originalIdentifier = identifiers.zooGateway! + + try { + identifiers.zooGateway = "canonical-zoo-gateway" + + expect(checkExistKey({ apiProvider: identifiers.zooGateway } as ProviderSettings, true)).toBe(true) + } finally { + identifiers.zooGateway = originalIdentifier + } + }) + it("should return true for zoo-gateway when profile has zooSessionToken", () => { const config: ProviderSettings = { apiProvider: "zoo-gateway", diff --git a/src/shared/checkExistApiConfig.ts b/src/shared/checkExistApiConfig.ts index 9ce55a9ac3..3560199b91 100644 --- a/src/shared/checkExistApiConfig.ts +++ b/src/shared/checkExistApiConfig.ts @@ -1,4 +1,4 @@ -import { SECRET_STATE_KEYS, GLOBAL_SECRET_KEYS, ProviderSettings } from "@roo-code/types" +import { SECRET_STATE_KEYS, GLOBAL_SECRET_KEYS, providerIdentifiers, ProviderSettings } from "@roo-code/types" /** * Returns whether a provider profile is sufficiently configured to leave the @@ -14,17 +14,22 @@ export function checkExistKey(config: ProviderSettings | undefined, zooCodeIsAut } // Special case for fake-ai, openai-codex, and qwen-code providers which don't need any configuration. - if (config.apiProvider && ["fake-ai", "openai-codex", "qwen-code"].includes(config.apiProvider)) { + const configurationFreeProviders: ProviderSettings["apiProvider"][] = [ + providerIdentifiers.fakeAi, + providerIdentifiers.openaiCodex, + providerIdentifiers.qwenCode, + ] + if (config.apiProvider && configurationFreeProviders.includes(config.apiProvider)) { return true } - if (config.apiProvider === "kimi-code" && (config.kimiCodeAuthMethod ?? "oauth") === "oauth") { + if (config.apiProvider === providerIdentifiers.kimiCode && (config.kimiCodeAuthMethod ?? "oauth") === "oauth") { return true } // Zoo Gateway uses session auth (profile token and/or global Zoo Code login), // not a traditional API key listed in SECRET_STATE_KEYS. - if (config.apiProvider === "zoo-gateway") { + if (config.apiProvider === providerIdentifiers.zooGateway) { return Boolean(config.zooSessionToken) || Boolean(zooCodeIsAuthenticated) } From 131c1e103d17c013684cb1138c9f1fa23473bc85 Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Sun, 26 Jul 2026 18:51:48 +0300 Subject: [PATCH 2/6] fix: address provider identifier review feedback --- .../src/__tests__/provider-settings.test.ts | 52 ++----------------- packages/types/src/provider-settings.ts | 18 +++++-- .../__tests__/checkExistApiConfig.spec.ts | 35 ++----------- 3 files changed, 23 insertions(+), 82 deletions(-) diff --git a/packages/types/src/__tests__/provider-settings.test.ts b/packages/types/src/__tests__/provider-settings.test.ts index 5422613dc7..b8ea73eb5e 100644 --- a/packages/types/src/__tests__/provider-settings.test.ts +++ b/packages/types/src/__tests__/provider-settings.test.ts @@ -1,4 +1,4 @@ -import { getApiProtocol, providerIdentifiers, type ProviderName } from "../index.js" +import { getApiProtocol, providerIdentifiers } from "../index.js" describe("getApiProtocol", () => { describe("Anthropic-style providers", () => { @@ -15,21 +15,8 @@ describe("getApiProtocol", () => { }) describe("Vertex provider with Claude models", () => { - it("uses the canonical Vertex identifier for Claude protocol selection", () => { - const identifiers = providerIdentifiers as Record - const originalIdentifier = identifiers.vertex! - - try { - identifiers.vertex = "canonical-vertex" - - expect(getApiProtocol(identifiers.vertex as ProviderName, "claude-3-opus")).toBe("anthropic") - } finally { - identifiers.vertex = originalIdentifier - } - }) - it("should return 'anthropic' for vertex provider with claude models", () => { - expect(getApiProtocol("vertex", "claude-3-opus")).toBe("anthropic") + expect(getApiProtocol(providerIdentifiers.vertex, "claude-3-opus")).toBe("anthropic") expect(getApiProtocol("vertex", "Claude-3-Sonnet")).toBe("anthropic") expect(getApiProtocol("vertex", "CLAUDE-instant")).toBe("anthropic") expect(getApiProtocol("vertex", "anthropic/claude-3-haiku")).toBe("anthropic") @@ -48,24 +35,8 @@ describe("getApiProtocol", () => { describe("Vercel AI Gateway provider", () => { it("uses canonical gateway identifiers for Anthropic model protocol selection", () => { - const identifiers = providerIdentifiers as Record - const originalVercelIdentifier = identifiers.vercelAiGateway! - const originalZooIdentifier = identifiers.zooGateway! - - try { - identifiers.vercelAiGateway = "canonical-vercel-ai-gateway" - identifiers.zooGateway = "canonical-zoo-gateway" - - expect(getApiProtocol(identifiers.vercelAiGateway as ProviderName, "anthropic/claude-3-opus")).toBe( - "anthropic", - ) - expect(getApiProtocol(identifiers.zooGateway as ProviderName, "anthropic/claude-3-opus")).toBe( - "anthropic", - ) - } finally { - identifiers.vercelAiGateway = originalVercelIdentifier - identifiers.zooGateway = originalZooIdentifier - } + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "anthropic/claude-3-opus")).toBe("anthropic") + expect(getApiProtocol(providerIdentifiers.zooGateway, "anthropic/claude-3-opus")).toBe("anthropic") }) it("should return 'anthropic' for vercel-ai-gateway provider with anthropic models", () => { @@ -88,21 +59,8 @@ describe("getApiProtocol", () => { }) describe("Opencode Go provider", () => { - it("uses the canonical OpenCode Go identifier for model-specific protocol selection", () => { - const identifiers = providerIdentifiers as Record - const originalIdentifier = identifiers.opencodeGo! - - try { - identifiers.opencodeGo = "canonical-opencode-go" - - expect(getApiProtocol(identifiers.opencodeGo as ProviderName, "qwen3.7-max")).toBe("anthropic") - } finally { - identifiers.opencodeGo = originalIdentifier - } - }) - it("should return 'anthropic' for opencode-go Anthropic-format models (Qwen/MiniMax)", () => { - expect(getApiProtocol("opencode-go", "qwen3.7-max")).toBe("anthropic") + expect(getApiProtocol(providerIdentifiers.opencodeGo, "qwen3.7-max")).toBe("anthropic") expect(getApiProtocol("opencode-go", "qwen3.7-plus")).toBe("anthropic") expect(getApiProtocol("opencode-go", "qwen3.6-plus")).toBe("anthropic") expect(getApiProtocol("opencode-go", "minimax-m3")).toBe("anthropic") diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 0cbdfdf37f..2cf600e756 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -587,21 +587,33 @@ export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = [ providerIdentifiers.minimax, ] +const CLAUDE_MODEL_ID_FRAGMENT = "claude" +const ANTHROPIC_MODEL_ID_PREFIX = "anthropic/" +const ANTHROPIC_MODEL_GATEWAY_PROVIDERS: ProviderName[] = [ + providerIdentifiers.vercelAiGateway, + providerIdentifiers.zooGateway, +] + export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => { if (provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider)) { return "anthropic" } - if (provider && provider === providerIdentifiers.vertex && modelId && modelId.toLowerCase().includes("claude")) { + if ( + provider && + provider === providerIdentifiers.vertex && + modelId && + modelId.toLowerCase().includes(CLAUDE_MODEL_ID_FRAGMENT) + ) { return "anthropic" } // Vercel AI Gateway and Zoo Gateway use the anthropic protocol for anthropic models. if ( provider && - (provider === providerIdentifiers.vercelAiGateway || provider === providerIdentifiers.zooGateway) && + ANTHROPIC_MODEL_GATEWAY_PROVIDERS.includes(provider) && modelId && - modelId.toLowerCase().startsWith("anthropic/") + modelId.toLowerCase().startsWith(ANTHROPIC_MODEL_ID_PREFIX) ) { return "anthropic" } diff --git a/src/shared/__tests__/checkExistApiConfig.spec.ts b/src/shared/__tests__/checkExistApiConfig.spec.ts index 3afa835985..d35c83b3cf 100644 --- a/src/shared/__tests__/checkExistApiConfig.spec.ts +++ b/src/shared/__tests__/checkExistApiConfig.spec.ts @@ -67,16 +67,7 @@ describe("checkExistKey", () => { }) it("recognizes keyless providers through their canonical identifiers", () => { - const identifiers = providerIdentifiers as Record - const originalIdentifier = identifiers.fakeAi! - - try { - identifiers.fakeAi = "canonical-fake-ai" - - expect(checkExistKey({ apiProvider: identifiers.fakeAi } as ProviderSettings)).toBe(true) - } finally { - identifiers.fakeAi = originalIdentifier - } + expect(checkExistKey({ apiProvider: providerIdentifiers.fakeAi })).toBe(true) }) it("should return true for openai-codex provider without API key", () => { @@ -109,18 +100,7 @@ describe("checkExistKey", () => { }) it("recognizes OAuth authentication through the canonical Kimi Code identifier", () => { - const identifiers = providerIdentifiers as Record - const originalIdentifier = identifiers.kimiCode! - - try { - identifiers.kimiCode = "canonical-kimi-code" - - expect( - checkExistKey({ apiProvider: identifiers.kimiCode, kimiCodeAuthMethod: "oauth" } as ProviderSettings), - ).toBe(true) - } finally { - identifiers.kimiCode = originalIdentifier - } + expect(checkExistKey({ apiProvider: providerIdentifiers.kimiCode, kimiCodeAuthMethod: "oauth" })).toBe(true) }) it("should return true for kimi-code provider without auth method (defaults to OAuth)", () => { @@ -157,16 +137,7 @@ describe("checkExistKey", () => { }) it("recognizes session authentication through the canonical Zoo Gateway identifier", () => { - const identifiers = providerIdentifiers as Record - const originalIdentifier = identifiers.zooGateway! - - try { - identifiers.zooGateway = "canonical-zoo-gateway" - - expect(checkExistKey({ apiProvider: identifiers.zooGateway } as ProviderSettings, true)).toBe(true) - } finally { - identifiers.zooGateway = originalIdentifier - } + expect(checkExistKey({ apiProvider: providerIdentifiers.zooGateway }, true)).toBe(true) }) it("should return true for zoo-gateway when profile has zooSessionToken", () => { From 2ff97ad29d33c63bcbe4b7f5473515392f23713b Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Sun, 26 Jul 2026 18:59:42 +0300 Subject: [PATCH 3/6] refactor: centralize Anthropic protocol value --- packages/types/src/provider-settings.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 2cf600e756..7c845cfc41 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -589,6 +589,7 @@ export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = [ const CLAUDE_MODEL_ID_FRAGMENT = "claude" const ANTHROPIC_MODEL_ID_PREFIX = "anthropic/" +const ANTHROPIC_API_PROTOCOL = "anthropic" const ANTHROPIC_MODEL_GATEWAY_PROVIDERS: ProviderName[] = [ providerIdentifiers.vercelAiGateway, providerIdentifiers.zooGateway, @@ -596,7 +597,7 @@ const ANTHROPIC_MODEL_GATEWAY_PROVIDERS: ProviderName[] = [ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => { if (provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider)) { - return "anthropic" + return ANTHROPIC_API_PROTOCOL } if ( @@ -605,7 +606,7 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str modelId && modelId.toLowerCase().includes(CLAUDE_MODEL_ID_FRAGMENT) ) { - return "anthropic" + return ANTHROPIC_API_PROTOCOL } // Vercel AI Gateway and Zoo Gateway use the anthropic protocol for anthropic models. @@ -615,7 +616,7 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str modelId && modelId.toLowerCase().startsWith(ANTHROPIC_MODEL_ID_PREFIX) ) { - return "anthropic" + return ANTHROPIC_API_PROTOCOL } // Opencode Go routes a subset of its models (Qwen, MiniMax) through the @@ -631,7 +632,7 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str modelId && isOpencodeGoAnthropicFormatModel(modelId) ) { - return "anthropic" + return ANTHROPIC_API_PROTOCOL } return "openai" From abb5388bb8de4c0c6cd46586df0808616912f81d Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Sun, 26 Jul 2026 19:07:51 +0300 Subject: [PATCH 4/6] refactor: reuse canonical provider protocol constants --- .../src/__tests__/provider-settings.test.ts | 102 ++++++++++-------- packages/types/src/provider-settings.ts | 6 +- packages/types/src/providers/anthropic.ts | 3 + 3 files changed, 63 insertions(+), 48 deletions(-) diff --git a/packages/types/src/__tests__/provider-settings.test.ts b/packages/types/src/__tests__/provider-settings.test.ts index b8ea73eb5e..677d7dbbfe 100644 --- a/packages/types/src/__tests__/provider-settings.test.ts +++ b/packages/types/src/__tests__/provider-settings.test.ts @@ -1,95 +1,107 @@ -import { getApiProtocol, providerIdentifiers } from "../index.js" +import { ANTHROPIC_API_PROTOCOL, getApiProtocol, providerIdentifiers } from "../index.js" describe("getApiProtocol", () => { describe("Anthropic-style providers", () => { it("should return 'anthropic' for anthropic provider", () => { - expect(getApiProtocol("anthropic")).toBe("anthropic") - expect(getApiProtocol("anthropic", "gpt-4")).toBe("anthropic") + expect(getApiProtocol(providerIdentifiers.anthropic)).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.anthropic, "gpt-4")).toBe(ANTHROPIC_API_PROTOCOL) }) it("should return 'anthropic' for bedrock provider", () => { - expect(getApiProtocol("bedrock")).toBe("anthropic") - expect(getApiProtocol("bedrock", "gpt-4")).toBe("anthropic") - expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic") + expect(getApiProtocol(providerIdentifiers.bedrock)).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.bedrock, "gpt-4")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.bedrock, "claude-3-opus")).toBe(ANTHROPIC_API_PROTOCOL) }) }) describe("Vertex provider with Claude models", () => { it("should return 'anthropic' for vertex provider with claude models", () => { - expect(getApiProtocol(providerIdentifiers.vertex, "claude-3-opus")).toBe("anthropic") - expect(getApiProtocol("vertex", "Claude-3-Sonnet")).toBe("anthropic") - expect(getApiProtocol("vertex", "CLAUDE-instant")).toBe("anthropic") - expect(getApiProtocol("vertex", "anthropic/claude-3-haiku")).toBe("anthropic") + expect(getApiProtocol(providerIdentifiers.vertex, "claude-3-opus")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.vertex, "Claude-3-Sonnet")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.vertex, "CLAUDE-instant")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.vertex, "anthropic/claude-3-haiku")).toBe(ANTHROPIC_API_PROTOCOL) }) it("should return 'openai' for vertex provider with non-claude models", () => { - expect(getApiProtocol("vertex", "gpt-4")).toBe("openai") - expect(getApiProtocol("vertex", "gemini-pro")).toBe("openai") - expect(getApiProtocol("vertex", "llama-2")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vertex, "gpt-4")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vertex, "gemini-pro")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vertex, "llama-2")).toBe("openai") }) it("should return 'openai' for vertex provider without model", () => { - expect(getApiProtocol("vertex")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vertex)).toBe("openai") }) }) describe("Vercel AI Gateway provider", () => { it("uses canonical gateway identifiers for Anthropic model protocol selection", () => { - expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "anthropic/claude-3-opus")).toBe("anthropic") - expect(getApiProtocol(providerIdentifiers.zooGateway, "anthropic/claude-3-opus")).toBe("anthropic") + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "anthropic/claude-3-opus")).toBe( + ANTHROPIC_API_PROTOCOL, + ) + expect(getApiProtocol(providerIdentifiers.zooGateway, "anthropic/claude-3-opus")).toBe( + ANTHROPIC_API_PROTOCOL, + ) }) it("should return 'anthropic' for vercel-ai-gateway provider with anthropic models", () => { - expect(getApiProtocol("vercel-ai-gateway", "anthropic/claude-3-opus")).toBe("anthropic") - expect(getApiProtocol("vercel-ai-gateway", "anthropic/claude-3.5-sonnet")).toBe("anthropic") - expect(getApiProtocol("vercel-ai-gateway", "ANTHROPIC/claude-sonnet-4")).toBe("anthropic") - expect(getApiProtocol("vercel-ai-gateway", "anthropic/claude-opus-4.1")).toBe("anthropic") + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "anthropic/claude-3-opus")).toBe( + ANTHROPIC_API_PROTOCOL, + ) + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "anthropic/claude-3.5-sonnet")).toBe( + ANTHROPIC_API_PROTOCOL, + ) + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "ANTHROPIC/claude-sonnet-4")).toBe( + ANTHROPIC_API_PROTOCOL, + ) + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "anthropic/claude-opus-4.1")).toBe( + ANTHROPIC_API_PROTOCOL, + ) }) it("should return 'openai' for vercel-ai-gateway provider with non-anthropic models", () => { - expect(getApiProtocol("vercel-ai-gateway", "openai/gpt-4")).toBe("openai") - expect(getApiProtocol("vercel-ai-gateway", "google/gemini-pro")).toBe("openai") - expect(getApiProtocol("vercel-ai-gateway", "meta/llama-3")).toBe("openai") - expect(getApiProtocol("vercel-ai-gateway", "mistral/mixtral")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "openai/gpt-4")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "google/gemini-pro")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "meta/llama-3")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "mistral/mixtral")).toBe("openai") }) it("should return 'openai' for vercel-ai-gateway provider without model", () => { - expect(getApiProtocol("vercel-ai-gateway")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vercelAiGateway)).toBe("openai") }) }) describe("Opencode Go provider", () => { it("should return 'anthropic' for opencode-go Anthropic-format models (Qwen/MiniMax)", () => { - expect(getApiProtocol(providerIdentifiers.opencodeGo, "qwen3.7-max")).toBe("anthropic") - expect(getApiProtocol("opencode-go", "qwen3.7-plus")).toBe("anthropic") - expect(getApiProtocol("opencode-go", "qwen3.6-plus")).toBe("anthropic") - expect(getApiProtocol("opencode-go", "minimax-m3")).toBe("anthropic") - expect(getApiProtocol("opencode-go", "minimax-m2.7")).toBe("anthropic") - expect(getApiProtocol("opencode-go", "minimax-m2.5")).toBe("anthropic") + expect(getApiProtocol(providerIdentifiers.opencodeGo, "qwen3.7-max")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.opencodeGo, "qwen3.7-plus")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.opencodeGo, "qwen3.6-plus")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.opencodeGo, "minimax-m3")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.opencodeGo, "minimax-m2.7")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.opencodeGo, "minimax-m2.5")).toBe(ANTHROPIC_API_PROTOCOL) }) it("should return 'openai' for opencode-go OpenAI-format models (GLM/DeepSeek/etc.)", () => { - expect(getApiProtocol("opencode-go", "glm-5.2")).toBe("openai") - expect(getApiProtocol("opencode-go", "deepseek-v4-pro")).toBe("openai") - expect(getApiProtocol("opencode-go", "kimi-k2.5")).toBe("openai") - expect(getApiProtocol("opencode-go", "mimo-v2.5")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.opencodeGo, "glm-5.2")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.opencodeGo, "deepseek-v4-pro")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.opencodeGo, "kimi-k2.5")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.opencodeGo, "mimo-v2.5")).toBe("openai") }) it("should return 'openai' for opencode-go without a model", () => { - expect(getApiProtocol("opencode-go")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.opencodeGo)).toBe("openai") }) it("should return 'openai' for opencode-go with an unknown model id", () => { - expect(getApiProtocol("opencode-go", "some-future-model")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.opencodeGo, "some-future-model")).toBe("openai") }) }) describe("Other providers", () => { it("should return 'openai' for non-anthropic providers regardless of model", () => { - expect(getApiProtocol("openrouter", "claude-3-opus")).toBe("openai") - expect(getApiProtocol("openai", "claude-3-sonnet")).toBe("openai") - expect(getApiProtocol("litellm", "claude-instant")).toBe("openai") - expect(getApiProtocol("ollama", "claude-model")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.openrouter, "claude-3-opus")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.openai, "claude-3-sonnet")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.litellm, "claude-instant")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.ollama, "claude-model")).toBe("openai") }) }) @@ -100,13 +112,13 @@ describe("getApiProtocol", () => { }) it("should handle empty strings", () => { - expect(getApiProtocol("vertex", "")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vertex, "")).toBe("openai") }) it("should be case-insensitive for claude detection", () => { - expect(getApiProtocol("vertex", "CLAUDE-3-OPUS")).toBe("anthropic") - expect(getApiProtocol("vertex", "claude-3-opus")).toBe("anthropic") - expect(getApiProtocol("vertex", "ClAuDe-InStAnT")).toBe("anthropic") + expect(getApiProtocol(providerIdentifiers.vertex, "CLAUDE-3-OPUS")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.vertex, "claude-3-opus")).toBe(ANTHROPIC_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.vertex, "ClAuDe-InStAnT")).toBe(ANTHROPIC_API_PROTOCOL) }) }) }) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 7c845cfc41..3a721dce2f 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -29,6 +29,9 @@ import { minimaxModels, mimoModels, isOpencodeGoAnthropicFormatModel, + ANTHROPIC_API_PROTOCOL, + ANTHROPIC_MODEL_ID_PREFIX, + CLAUDE_MODEL_ID_FRAGMENT, } from "./providers/index.js" /** @@ -587,9 +590,6 @@ export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = [ providerIdentifiers.minimax, ] -const CLAUDE_MODEL_ID_FRAGMENT = "claude" -const ANTHROPIC_MODEL_ID_PREFIX = "anthropic/" -const ANTHROPIC_API_PROTOCOL = "anthropic" const ANTHROPIC_MODEL_GATEWAY_PROVIDERS: ProviderName[] = [ providerIdentifiers.vercelAiGateway, providerIdentifiers.zooGateway, diff --git a/packages/types/src/providers/anthropic.ts b/packages/types/src/providers/anthropic.ts index f5f7e71116..13d53f3732 100644 --- a/packages/types/src/providers/anthropic.ts +++ b/packages/types/src/providers/anthropic.ts @@ -5,6 +5,9 @@ import type { ModelInfo } from "../model.js" export type AnthropicModelId = keyof typeof anthropicModels export const anthropicDefaultModelId: AnthropicModelId = "claude-sonnet-4-5" +export const ANTHROPIC_API_PROTOCOL = "anthropic" +export const ANTHROPIC_MODEL_ID_PREFIX = "anthropic/" +export const CLAUDE_MODEL_ID_FRAGMENT = "claude" export const anthropicModels = { "claude-sonnet-4-6": { From 14f7277b3f14d961b4839a860477356b5346f084 Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Sun, 26 Jul 2026 19:16:00 +0300 Subject: [PATCH 5/6] refactor: centralize OpenAI protocol value --- .../src/__tests__/provider-settings.test.ts | 46 +++++++++---------- packages/types/src/provider-settings.ts | 3 +- packages/types/src/providers/openai.ts | 1 + 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/packages/types/src/__tests__/provider-settings.test.ts b/packages/types/src/__tests__/provider-settings.test.ts index 677d7dbbfe..57f2ffbc52 100644 --- a/packages/types/src/__tests__/provider-settings.test.ts +++ b/packages/types/src/__tests__/provider-settings.test.ts @@ -1,4 +1,4 @@ -import { ANTHROPIC_API_PROTOCOL, getApiProtocol, providerIdentifiers } from "../index.js" +import { ANTHROPIC_API_PROTOCOL, getApiProtocol, OPENAI_API_PROTOCOL, providerIdentifiers } from "../index.js" describe("getApiProtocol", () => { describe("Anthropic-style providers", () => { @@ -23,13 +23,13 @@ describe("getApiProtocol", () => { }) it("should return 'openai' for vertex provider with non-claude models", () => { - expect(getApiProtocol(providerIdentifiers.vertex, "gpt-4")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.vertex, "gemini-pro")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.vertex, "llama-2")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vertex, "gpt-4")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.vertex, "gemini-pro")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.vertex, "llama-2")).toBe(OPENAI_API_PROTOCOL) }) it("should return 'openai' for vertex provider without model", () => { - expect(getApiProtocol(providerIdentifiers.vertex)).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vertex)).toBe(OPENAI_API_PROTOCOL) }) }) @@ -59,14 +59,14 @@ describe("getApiProtocol", () => { }) it("should return 'openai' for vercel-ai-gateway provider with non-anthropic models", () => { - expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "openai/gpt-4")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "google/gemini-pro")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "meta/llama-3")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "mistral/mixtral")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "openai/gpt-4")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "google/gemini-pro")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "meta/llama-3")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.vercelAiGateway, "mistral/mixtral")).toBe(OPENAI_API_PROTOCOL) }) it("should return 'openai' for vercel-ai-gateway provider without model", () => { - expect(getApiProtocol(providerIdentifiers.vercelAiGateway)).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vercelAiGateway)).toBe(OPENAI_API_PROTOCOL) }) }) @@ -81,38 +81,38 @@ describe("getApiProtocol", () => { }) it("should return 'openai' for opencode-go OpenAI-format models (GLM/DeepSeek/etc.)", () => { - expect(getApiProtocol(providerIdentifiers.opencodeGo, "glm-5.2")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.opencodeGo, "deepseek-v4-pro")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.opencodeGo, "kimi-k2.5")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.opencodeGo, "mimo-v2.5")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.opencodeGo, "glm-5.2")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.opencodeGo, "deepseek-v4-pro")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.opencodeGo, "kimi-k2.5")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.opencodeGo, "mimo-v2.5")).toBe(OPENAI_API_PROTOCOL) }) it("should return 'openai' for opencode-go without a model", () => { - expect(getApiProtocol(providerIdentifiers.opencodeGo)).toBe("openai") + expect(getApiProtocol(providerIdentifiers.opencodeGo)).toBe(OPENAI_API_PROTOCOL) }) it("should return 'openai' for opencode-go with an unknown model id", () => { - expect(getApiProtocol(providerIdentifiers.opencodeGo, "some-future-model")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.opencodeGo, "some-future-model")).toBe(OPENAI_API_PROTOCOL) }) }) describe("Other providers", () => { it("should return 'openai' for non-anthropic providers regardless of model", () => { - expect(getApiProtocol(providerIdentifiers.openrouter, "claude-3-opus")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.openai, "claude-3-sonnet")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.litellm, "claude-instant")).toBe("openai") - expect(getApiProtocol(providerIdentifiers.ollama, "claude-model")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.openrouter, "claude-3-opus")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.openai, "claude-3-sonnet")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.litellm, "claude-instant")).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(providerIdentifiers.ollama, "claude-model")).toBe(OPENAI_API_PROTOCOL) }) }) describe("Edge cases", () => { it("should return 'openai' when provider is undefined", () => { - expect(getApiProtocol(undefined)).toBe("openai") - expect(getApiProtocol(undefined, "claude-3-opus")).toBe("openai") + expect(getApiProtocol(undefined)).toBe(OPENAI_API_PROTOCOL) + expect(getApiProtocol(undefined, "claude-3-opus")).toBe(OPENAI_API_PROTOCOL) }) it("should handle empty strings", () => { - expect(getApiProtocol(providerIdentifiers.vertex, "")).toBe("openai") + expect(getApiProtocol(providerIdentifiers.vertex, "")).toBe(OPENAI_API_PROTOCOL) }) it("should be case-insensitive for claude detection", () => { diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 3a721dce2f..122a7b7119 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -32,6 +32,7 @@ import { ANTHROPIC_API_PROTOCOL, ANTHROPIC_MODEL_ID_PREFIX, CLAUDE_MODEL_ID_FRAGMENT, + OPENAI_API_PROTOCOL, } from "./providers/index.js" /** @@ -635,7 +636,7 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str return ANTHROPIC_API_PROTOCOL } - return "openai" + return OPENAI_API_PROTOCOL } /** diff --git a/packages/types/src/providers/openai.ts b/packages/types/src/providers/openai.ts index 1601961964..5f114e41f6 100644 --- a/packages/types/src/providers/openai.ts +++ b/packages/types/src/providers/openai.ts @@ -3,6 +3,7 @@ import type { ModelInfo } from "../model.js" // https://openai.com/api/pricing/ export type OpenAiNativeModelId = keyof typeof openAiNativeModels +export const OPENAI_API_PROTOCOL = "openai" export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5.6-sol" export const openAiNativeModels = { From 7b084ea9ea02ce0af879e1d964e9e17447bd6abd Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Sun, 26 Jul 2026 21:57:38 +0300 Subject: [PATCH 6/6] test: use canonical keyless provider identifiers --- src/shared/__tests__/checkExistApiConfig.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/__tests__/checkExistApiConfig.spec.ts b/src/shared/__tests__/checkExistApiConfig.spec.ts index d35c83b3cf..ab92beea36 100644 --- a/src/shared/__tests__/checkExistApiConfig.spec.ts +++ b/src/shared/__tests__/checkExistApiConfig.spec.ts @@ -72,14 +72,14 @@ describe("checkExistKey", () => { it("should return true for openai-codex provider without API key", () => { const config: ProviderSettings = { - apiProvider: "openai-codex", + apiProvider: providerIdentifiers.openaiCodex, } expect(checkExistKey(config)).toBe(true) }) it("should return true for qwen-code provider without API key", () => { const config: ProviderSettings = { - apiProvider: "qwen-code", + apiProvider: providerIdentifiers.qwenCode, } expect(checkExistKey(config)).toBe(true) })