diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 393c6ac143..bbe101f0c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -668,6 +668,9 @@ importers: '@types/vscode': specifier: 1.100.0 version: 1.100.0 + '@typescript-eslint/parser': + specifier: 8.32.1 + version: 8.32.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) '@vitest/coverage-v8': specifier: 4.1.9 version: 4.1.9(vitest@4.1.9) diff --git a/src/__tests__/no-raw-provider-identifiers.spec.mjs b/src/__tests__/no-raw-provider-identifiers.spec.mjs new file mode 100644 index 0000000000..46d981b791 --- /dev/null +++ b/src/__tests__/no-raw-provider-identifiers.spec.mjs @@ -0,0 +1,137 @@ +import { Linter } from "eslint" +import typescriptParser from "@typescript-eslint/parser" +import { describe, expect, it } from "vitest" + +import { noRawProviderIdentifiers } from "../eslint-rules/no-raw-provider-identifiers.mjs" + +const linter = new Linter({ configType: "eslintrc" }) + +linter.defineRule("zoo/no-raw-provider-identifiers", noRawProviderIdentifiers) +linter.defineParser("@typescript-eslint/parser", typescriptParser) + +function lint(code) { + return linter.verify(code, { + parserOptions: { ecmaVersion: 2022, sourceType: "module" }, + rules: { "zoo/no-raw-provider-identifiers": "error" }, + }) +} + +function lintTypeScript(code) { + return linter.verify(code, { + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 2022, + sourceType: "module", + warnOnUnsupportedTypeScriptVersion: false, + }, + rules: { "zoo/no-raw-provider-identifiers": "error" }, + }) +} + +describe("no-raw-provider-identifiers", () => { + it("rejects a canonical provider literal in an apiProvider property", () => { + const messages = lint('const config = { apiProvider: "poe" }') + + expect(messages).toHaveLength(1) + expect(messages[0]).toMatchObject({ + ruleId: "zoo/no-raw-provider-identifiers", + message: 'Use providerIdentifiers.poe instead of the raw provider identifier "poe".', + }) + }) + + it("allows a non-canonical literal and a canonical registry member", () => { + expect(lint('const config = { apiProvider: "external-provider" }')).toHaveLength(0) + expect(lint("const config = { apiProvider: providerIdentifiers.poe }")).toHaveLength(0) + }) + + it("matches provider-like property names and static template literals", () => { + const messages = lint('const config = { provider: "poe", imageProvider: `openrouter` }') + + expect(messages).toHaveLength(2) + }) + + it("allows an empty static template in a provider-like context", () => { + expect(lint("const config = { apiProvider: `` }")).toHaveLength(0) + }) + + it("rejects canonical literals in provider-like variable declarations", () => { + const messages = lint(` + const apiProvider = "poe" + let fallbackProvider = \`openrouter\` + const label = "poe" + `) + + expect(messages.map(({ message }) => message)).toEqual([ + 'Use providerIdentifiers.poe instead of the raw provider identifier "poe".', + 'Use providerIdentifiers.openrouter instead of the raw provider identifier "openrouter".', + ]) + }) + + it("rejects canonical provider literals wrapped in TypeScript expressions", () => { + const messages = lintTypeScript(` + const apiProvider = "poe" as ApiProvider + const fallbackProvider = "openrouter" satisfies ApiProvider + const imageProvider = "openai-native" + const nestedProvider = ("anthropic" as ApiProvider)! + `) + + expect(messages.map(({ message }) => message)).toEqual([ + 'Use providerIdentifiers.poe instead of the raw provider identifier "poe".', + 'Use providerIdentifiers.openrouter instead of the raw provider identifier "openrouter".', + 'Use providerIdentifiers.openaiNative instead of the raw provider identifier "openai-native".', + 'Use providerIdentifiers.anthropic instead of the raw provider identifier "anthropic".', + ]) + }) + + it("rejects canonical literals in provider-like class fields", () => { + const messages = lintTypeScript(` + class Settings { + apiProvider = "poe" + label = "openrouter" + } + `) + + expect(messages.map(({ message }) => message)).toEqual([ + 'Use providerIdentifiers.poe instead of the raw provider identifier "poe".', + ]) + }) + + it("rejects canonical literals in provider-like assignments and comparisons", () => { + const messages = lint(` + config["apiProvider"] = "poe" + if (imageProvider === "openrouter") {} + if ("openai-native" !== config.fallbackProvider) {} + `) + + expect(messages.map(({ message }) => message)).toEqual([ + 'Use providerIdentifiers.poe instead of the raw provider identifier "poe".', + 'Use providerIdentifiers.openrouter instead of the raw provider identifier "openrouter".', + 'Use providerIdentifiers.openaiNative instead of the raw provider identifier "openai-native".', + ]) + }) + + it("rejects canonical literals in provider-like switch cases", () => { + const messages = lint(` + switch (config.apiProvider) { + case "poe": break + case providerIdentifiers.openrouter: break + } + `) + + expect(messages).toHaveLength(1) + expect(messages[0].message).toContain("providerIdentifiers.poe") + }) + + it("does not report canonical values outside provider-like contexts", () => { + const messages = lint(` + const label = "poe" + const config = { protocol: "anthropic", format: "openai" } + config[dynamicKey] = "poe" + if (apiProtocol === "anthropic") {} + if (provider > "poe") {} + switch (format) { case "openai": break } + `) + + expect(messages).toHaveLength(0) + }) +}) diff --git a/src/__tests__/single-open-invariant.spec.ts b/src/__tests__/single-open-invariant.spec.ts index 954261b145..af1631df9c 100644 --- a/src/__tests__/single-open-invariant.spec.ts +++ b/src/__tests__/single-open-invariant.spec.ts @@ -8,6 +8,7 @@ import { TaskScheduler } from "../core/task/TaskScheduler" import { type Task } from "../core/task/Task" import { API } from "../extension/api" import * as ProfileValidatorMod from "../shared/ProfileValidator" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" type PrivateClineProviderMethods = { createTask: ( @@ -45,7 +46,7 @@ vi.mock("../core/task/Task", () => { }) { this.taskId = opts.historyItem?.id ?? `task-${Math.random().toString(36).slice(2, 8)}` this.parentTask = opts.parentTask - this.apiConfiguration = opts.apiConfiguration ?? { apiProvider: "anthropic" } + this.apiConfiguration = opts.apiConfiguration ?? { apiProvider: providerIdentifiers.anthropic } opts.onCreated?.(this) } start() {} @@ -86,7 +87,7 @@ describe("Single-open-task invariant", () => { }, setValues: vi.fn(), getState: vi.fn().mockResolvedValue({ - apiConfiguration: { apiProvider: "anthropic", consecutiveMistakeLimit: 0 }, + apiConfiguration: { apiProvider: providerIdentifiers.anthropic, consecutiveMistakeLimit: 0 }, organizationAllowList: "*", enableCheckpoints: true, checkpointTimeout: 60, @@ -130,7 +131,7 @@ describe("Single-open-task invariant", () => { taskScheduler: new TaskScheduler(), setValues: vi.fn(), getState: vi.fn().mockResolvedValue({ - apiConfiguration: { apiProvider: "anthropic", consecutiveMistakeLimit: 0 }, + apiConfiguration: { apiProvider: providerIdentifiers.anthropic, consecutiveMistakeLimit: 0 }, organizationAllowList: "*", enableCheckpoints: true, checkpointTimeout: 60, @@ -182,7 +183,7 @@ describe("Single-open-task invariant", () => { listConfig: vi.fn().mockResolvedValue([]), }, getState: vi.fn().mockResolvedValue({ - apiConfiguration: { apiProvider: "anthropic", consecutiveMistakeLimit: 0 }, + apiConfiguration: { apiProvider: providerIdentifiers.anthropic, consecutiveMistakeLimit: 0 }, enableCheckpoints: true, checkpointTimeout: 60, experiments: {}, @@ -256,7 +257,7 @@ describe("Single-open-task invariant", () => { listConfig: vi.fn().mockResolvedValue([]), }, getState: vi.fn().mockResolvedValue({ - apiConfiguration: { apiProvider: "anthropic", consecutiveMistakeLimit: 0 }, + apiConfiguration: { apiProvider: providerIdentifiers.anthropic, consecutiveMistakeLimit: 0 }, enableCheckpoints: true, checkpointTimeout: 60, experiments: {}, @@ -328,7 +329,7 @@ describe("Single-open-task invariant", () => { listConfig: vi.fn().mockResolvedValue([]), }, getState: vi.fn().mockResolvedValue({ - apiConfiguration: { apiProvider: "anthropic", consecutiveMistakeLimit: 0 }, + apiConfiguration: { apiProvider: providerIdentifiers.anthropic, consecutiveMistakeLimit: 0 }, enableCheckpoints: true, checkpointTimeout: 60, experiments: {}, diff --git a/src/api/providers/__tests__/bedrock-reasoning.spec.ts b/src/api/providers/__tests__/bedrock-reasoning.spec.ts index 1577d51f93..d0ee0b8169 100644 --- a/src/api/providers/__tests__/bedrock-reasoning.spec.ts +++ b/src/api/providers/__tests__/bedrock-reasoning.spec.ts @@ -5,6 +5,7 @@ import { BedrockRuntimeClient, ConverseStreamCommand } from "@aws-sdk/client-bed import { logger } from "../../../utils/logging" import { clearAllMocks } from "../../../test-utils/reset" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Mock the AWS SDK vi.mock("@aws-sdk/client-bedrock-runtime") @@ -45,7 +46,7 @@ describe("AwsBedrockHandler - Extended Thinking", () => { describe("Extended Thinking Support", () => { it("should include thinking parameter for Claude Sonnet 4 when reasoning is enabled", async () => { handler = new AwsBedrockHandler({ - apiProvider: "bedrock", + apiProvider: providerIdentifiers.bedrock, apiModelId: "anthropic.claude-sonnet-4-20250514-v1:0", awsRegion: "us-east-1", enableReasoningEffort: true, @@ -113,7 +114,7 @@ describe("AwsBedrockHandler - Extended Thinking", () => { it("should pass thinking parameters from metadata", async () => { handler = new AwsBedrockHandler({ - apiProvider: "bedrock", + apiProvider: providerIdentifiers.bedrock, apiModelId: "anthropic.claude-3-7-sonnet-20250219-v1:0", awsRegion: "us-east-1", }) @@ -156,7 +157,7 @@ describe("AwsBedrockHandler - Extended Thinking", () => { it("should log when extended thinking is enabled", async () => { handler = new AwsBedrockHandler({ - apiProvider: "bedrock", + apiProvider: providerIdentifiers.bedrock, apiModelId: "anthropic.claude-opus-4-20250514-v1:0", awsRegion: "us-east-1", enableReasoningEffort: true, @@ -188,7 +189,7 @@ describe("AwsBedrockHandler - Extended Thinking", () => { it("should not include topP when thinking is disabled (global removal)", async () => { handler = new AwsBedrockHandler({ - apiProvider: "bedrock", + apiProvider: providerIdentifiers.bedrock, apiModelId: "anthropic.claude-3-7-sonnet-20250219-v1:0", awsRegion: "us-east-1", // Note: no enableReasoningEffort = true, so thinking is disabled @@ -234,7 +235,7 @@ describe("AwsBedrockHandler - Extended Thinking", () => { it("should enable reasoning when enableReasoningEffort is true in settings", async () => { handler = new AwsBedrockHandler({ - apiProvider: "bedrock", + apiProvider: providerIdentifiers.bedrock, apiModelId: "anthropic.claude-sonnet-4-20250514-v1:0", awsRegion: "us-east-1", enableReasoningEffort: true, // This should trigger reasoning @@ -288,7 +289,7 @@ describe("AwsBedrockHandler - Extended Thinking", () => { it("should support API key authentication", async () => { handler = new AwsBedrockHandler({ - apiProvider: "bedrock", + apiProvider: providerIdentifiers.bedrock, apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", awsRegion: "us-east-1", awsUseApiKey: true, diff --git a/src/api/providers/__tests__/friendli.spec.ts b/src/api/providers/__tests__/friendli.spec.ts index 0e6b21c5e5..6ac7d9c1fe 100644 --- a/src/api/providers/__tests__/friendli.spec.ts +++ b/src/api/providers/__tests__/friendli.spec.ts @@ -10,6 +10,7 @@ import { getModelMaxOutputTokens } from "../../../shared/api" import { FriendliHandler } from "../friendli" import { asyncStreamFrom, collectStream } from "../../../test-utils/stream" import { clearAllMocks } from "../../../test-utils/reset" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Create mock functions const mockCreate = vi.fn() @@ -324,7 +325,7 @@ describe("FriendliHandler", () => { describe("buildApiHandler friendli wiring", () => { it("returns a FriendliHandler for apiProvider='friendli'", () => { - const handler = buildApiHandler({ apiProvider: "friendli", friendliApiKey: "test-key" }) + const handler = buildApiHandler({ apiProvider: providerIdentifiers.friendli, friendliApiKey: "test-key" }) expect(handler).toBeInstanceOf(FriendliHandler) }) }) @@ -335,7 +336,7 @@ describe("Friendli model max output tokens (clamping behavior)", () => { const result = getModelMaxOutputTokens({ modelId: "zai-org/GLM-5.2", model, - settings: { apiProvider: "friendli" }, + settings: { apiProvider: providerIdentifiers.friendli }, format: "openai", }) // 1_000_000 * 0.2 = 200_000 > 131_072 → no clamping @@ -347,7 +348,7 @@ describe("Friendli model max output tokens (clamping behavior)", () => { const result = getModelMaxOutputTokens({ modelId: "zai-org/GLM-5.1", model, - settings: { apiProvider: "friendli" }, + settings: { apiProvider: providerIdentifiers.friendli }, format: "openai", }) // 200_000 * 0.2 = 40_000 < 131_072 → clamped to 40_000 @@ -359,7 +360,7 @@ describe("Friendli model max output tokens (clamping behavior)", () => { const result = getModelMaxOutputTokens({ modelId: "zai-org/GLM-5.1", model, - settings: { apiProvider: "friendli", modelMaxTokens: 80_000 }, + settings: { apiProvider: providerIdentifiers.friendli, modelMaxTokens: 80_000 }, format: "openai", }) // supportsMaxTokens=true, user set 80k, model ceiling 131072 → min(80000, 131072) = 80000 diff --git a/src/api/providers/__tests__/gemini-handler.spec.ts b/src/api/providers/__tests__/gemini-handler.spec.ts index 110f60289c..4f2ec12295 100644 --- a/src/api/providers/__tests__/gemini-handler.spec.ts +++ b/src/api/providers/__tests__/gemini-handler.spec.ts @@ -13,6 +13,7 @@ vi.mock("@roo-code/telemetry", () => ({ import { GeminiHandler } from "../gemini" import type { ApiHandlerOptions } from "../../../shared/api" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" describe("GeminiHandler backend support", () => { beforeEach(() => { @@ -24,7 +25,7 @@ describe("GeminiHandler backend support", () => { // in Gemini API, so createMessage only uses function declarations. // URL context/grounding are only added in completePrompt. const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, enableUrlContext: true, enableGrounding: true, } as ApiHandlerOptions @@ -41,7 +42,7 @@ describe("GeminiHandler backend support", () => { it("completePrompt passes config overrides without tools when URL context and grounding disabled", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, enableUrlContext: false, enableGrounding: false, } as ApiHandlerOptions @@ -58,7 +59,7 @@ describe("GeminiHandler backend support", () => { describe("error scenarios", () => { it("should handle grounding metadata extraction failure gracefully", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, enableGrounding: true, } as ApiHandlerOptions const handler = new GeminiHandler(options) @@ -93,7 +94,7 @@ describe("GeminiHandler backend support", () => { it("should handle malformed grounding metadata", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, enableGrounding: true, } as ApiHandlerOptions const handler = new GeminiHandler(options) @@ -144,7 +145,7 @@ describe("GeminiHandler backend support", () => { it("should handle API errors when tools are enabled", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, enableUrlContext: true, enableGrounding: true, } as ApiHandlerOptions @@ -192,7 +193,7 @@ describe("GeminiHandler backend support", () => { it("should ignore allowedFunctionNames because Gemini rejects larger restriction lists", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) @@ -213,7 +214,7 @@ describe("GeminiHandler backend support", () => { it("should include all tools when allowedFunctionNames is provided", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) @@ -236,7 +237,7 @@ describe("GeminiHandler backend support", () => { it("should not pass large allowedFunctionNames lists to Gemini", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) @@ -267,7 +268,7 @@ describe("GeminiHandler backend support", () => { it("should not pass allowedFunctionNames even when history includes tool calls", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) @@ -304,7 +305,7 @@ describe("GeminiHandler backend support", () => { it("should fall back to tool_choice when allowedFunctionNames is provided", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) @@ -327,7 +328,7 @@ describe("GeminiHandler backend support", () => { it("should fall back to tool_choice when allowedFunctionNames is empty", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) @@ -351,7 +352,7 @@ describe("GeminiHandler backend support", () => { it("should not set toolConfig when allowedFunctionNames is undefined and no tool_choice", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) @@ -374,7 +375,7 @@ describe("GeminiHandler backend support", () => { describe("Gemini schema compatibility", () => { it("should strip broad JSON Schema metadata from function declarations", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) @@ -435,7 +436,7 @@ describe("GeminiHandler backend support", () => { it("should collapse composition and type arrays in function declaration schemas", async () => { const options = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) @@ -495,7 +496,7 @@ describe("GeminiHandler backend support", () => { }) it("should deep-merge allOf fragments instead of overwriting earlier properties", async () => { - const options = { apiProvider: "gemini" } as ApiHandlerOptions + const options = { apiProvider: providerIdentifiers.gemini } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) // @ts-ignore access private client @@ -540,7 +541,7 @@ describe("GeminiHandler backend support", () => { }) it("should resolve $ref entries before dropping $defs", async () => { - const options = { apiProvider: "gemini" } as ApiHandlerOptions + const options = { apiProvider: providerIdentifiers.gemini } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) // @ts-ignore access private client @@ -589,7 +590,7 @@ describe("GeminiHandler backend support", () => { }) it("should preserve top-level properties and required entries when allOf is also present", async () => { - const options = { apiProvider: "gemini" } as ApiHandlerOptions + const options = { apiProvider: providerIdentifiers.gemini } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) // @ts-ignore access private client @@ -631,7 +632,7 @@ describe("GeminiHandler backend support", () => { }) it("should stop recursive $ref expansion before the sanitized schema becomes cyclic", async () => { - const options = { apiProvider: "gemini" } as ApiHandlerOptions + const options = { apiProvider: providerIdentifiers.gemini } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) // @ts-ignore access private client @@ -683,7 +684,7 @@ describe("GeminiHandler backend support", () => { }) it("should preserve parameter names that collide with stripped schema keywords", async () => { - const options = { apiProvider: "gemini" } as ApiHandlerOptions + const options = { apiProvider: providerIdentifiers.gemini } as ApiHandlerOptions const handler = new GeminiHandler(options) const stub = vi.fn().mockReturnValue((async function* () {})()) // @ts-ignore access private client diff --git a/src/api/providers/__tests__/kimi-code.spec.ts b/src/api/providers/__tests__/kimi-code.spec.ts index df909d57d4..c8343f7bc4 100644 --- a/src/api/providers/__tests__/kimi-code.spec.ts +++ b/src/api/providers/__tests__/kimi-code.spec.ts @@ -2,6 +2,7 @@ import { buildApiHandler } from "../../index" import { KimiCodeHandler } from "../kimi-code" import { clearAllMocks } from "../../../test-utils/reset" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" const { mockGetAccessToken, mockForceRefreshAccessToken, mockGetModels } = vi.hoisted(() => ({ mockGetAccessToken: vi.fn(), @@ -28,7 +29,7 @@ describe("KimiCodeHandler", () => { it("is dispatched separately from Moonshot and preserves an unknown selected model", () => { const handler = buildApiHandler({ - apiProvider: "kimi-code", + apiProvider: providerIdentifiers.kimiCode, kimiCodeAuthMethod: "api-key", kimiCodeApiKey: "kimi-key", apiModelId: "future-kimi-model", diff --git a/src/api/transform/__tests__/reasoning.spec.ts b/src/api/transform/__tests__/reasoning.spec.ts index a14fffe550..004df4e60c 100644 --- a/src/api/transform/__tests__/reasoning.spec.ts +++ b/src/api/transform/__tests__/reasoning.spec.ts @@ -1,6 +1,7 @@ // npx vitest run src/api/transform/__tests__/reasoning.spec.ts import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@roo-code/types" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" import { getOpenRouterReasoning, @@ -703,7 +704,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, enableReasoningEffort: true, reasoningEffort: "high", } @@ -730,7 +731,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, // Even with this flag false, an explicit effort selection should win enableReasoningEffort: false, reasoningEffort: "high", @@ -755,7 +756,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, reasoningEffort: "minimal", } @@ -778,7 +779,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, reasoningEffort: "medium", } @@ -809,7 +810,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, reasoningEffort: level, } @@ -833,7 +834,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, reasoningEffort: "disable", } @@ -856,7 +857,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, reasoningEffort: "none", } @@ -880,7 +881,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, enableReasoningEffort: true, } @@ -904,7 +905,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, enableReasoningEffort: true, reasoningEffort: "high", } @@ -929,7 +930,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, } const options: GetModelReasoningOptions = { @@ -953,7 +954,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, reasoningEffort: "medium", } @@ -977,7 +978,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, reasoningEffort: "medium", } @@ -1001,7 +1002,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, reasoningEffort: "high", } @@ -1025,7 +1026,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, reasoningEffort: "medium", } @@ -1049,7 +1050,7 @@ describe("reasoning.ts", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, reasoningEffort: "minimal", } diff --git a/src/core/config/__tests__/ContextProxy.spec.ts b/src/core/config/__tests__/ContextProxy.spec.ts index d4cae48177..2319a6b1a5 100644 --- a/src/core/config/__tests__/ContextProxy.spec.ts +++ b/src/core/config/__tests__/ContextProxy.spec.ts @@ -8,6 +8,7 @@ import { clearAllMocks } from "../../../test-utils/reset" import { makeExtensionContext, makeUri } from "../../../test-utils/vscode" import { ContextProxy } from "../ContextProxy" +import { providerIdentifiers, retiredProviderIdentifiers } from "@roo-code/types/provider-identifiers" vi.mock("vscode", () => ({ Uri: { @@ -270,7 +271,7 @@ describe("ContextProxy", () => { // Test with multiple values await proxy.setValues({ apiModelId: "gpt-4", - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, mode: "test-mode", }) @@ -310,7 +311,7 @@ describe("ContextProxy", () => { describe("setProviderSettings", () => { it("stores and returns the complete NanoGPT configuration across secret and global state", async () => { await proxy.setProviderSettings({ - apiProvider: "nanogpt", + apiProvider: providerIdentifiers.nanogpt, nanoGptApiKey: "nanogpt-secret", nanoGptModelId: "openai/model", nanoGptRoutingPreference: "throughput", @@ -320,7 +321,7 @@ describe("ContextProxy", () => { expect(mockGlobalState.update).toHaveBeenCalledWith("nanoGptModelId", "openai/model") expect(mockGlobalState.update).toHaveBeenCalledWith("nanoGptRoutingPreference", "throughput") expect(proxy.getProviderSettings()).toMatchObject({ - apiProvider: "nanogpt", + apiProvider: providerIdentifiers.nanogpt, nanoGptApiKey: "nanogpt-secret", nanoGptModelId: "openai/model", nanoGptRoutingPreference: "throughput", @@ -339,7 +340,7 @@ describe("ContextProxy", () => { // Call setProviderSettings with new configuration await proxy.setProviderSettings({ apiModelId: "new-model", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, // Note: openAiBaseUrl is not included in the new config }) @@ -349,7 +350,7 @@ describe("ContextProxy", () => { expect(setValuesSpy).toHaveBeenCalledWith( expect.objectContaining({ apiModelId: "new-model", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, openAiBaseUrl: undefined, modelTemperature: undefined, }), @@ -565,7 +566,7 @@ describe("ContextProxy", () => { it("should preserve retired apiProvider and provider fields", async () => { await proxy.setValues({ - apiProvider: "groq", + apiProvider: retiredProviderIdentifiers.groq, apiModelId: "llama3-70b", openAiBaseUrl: "https://api.retired-provider.example/v1", apiKey: "retired-provider-key", diff --git a/src/core/config/__tests__/ProviderSettingsManager.spec.ts b/src/core/config/__tests__/ProviderSettingsManager.spec.ts index 56d4a6951b..b7a0a9595c 100644 --- a/src/core/config/__tests__/ProviderSettingsManager.spec.ts +++ b/src/core/config/__tests__/ProviderSettingsManager.spec.ts @@ -4,6 +4,7 @@ import { OPEN_AI_CODEX_SERVICE_TIER_KEY, OpenAiCodexServiceTier, providerIdentifiers, + retiredProviderIdentifiers, type ProviderSettings, } from "@roo-code/types" @@ -119,7 +120,7 @@ describe("ProviderSettingsManager", () => { config: {}, }, test: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, }, }, migrations: { @@ -151,11 +152,11 @@ describe("ProviderSettingsManager", () => { rateLimitSeconds: undefined, }, test: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, rateLimitSeconds: undefined, }, existing: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, // this should not really be possible, unless someone has loaded a hand edited config, // but we don't overwrite so we'll check that rateLimitSeconds: 43, @@ -188,11 +189,11 @@ describe("ProviderSettingsManager", () => { consecutiveMistakeLimit: undefined, }, test: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, consecutiveMistakeLimit: undefined, }, existing: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, // this should not really be possible, unless someone has loaded a hand edited config, // but we don't overwrite so we'll check that consecutiveMistakeLimit: 5, @@ -228,11 +229,11 @@ describe("ProviderSettingsManager", () => { todoListEnabled: undefined, }, test: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, todoListEnabled: undefined, }, existing: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, // this should not really be possible, unless someone has loaded a hand edited config, // but we don't overwrite so we'll check that todoListEnabled: false, @@ -266,19 +267,19 @@ describe("ProviderSettingsManager", () => { default: { config: {}, id: "default", - apiProvider: "roo", + apiProvider: retiredProviderIdentifiers.roo, apiModelId: "roo/code-supernova", // Old model ID }, test: { - apiProvider: "roo", + apiProvider: retiredProviderIdentifiers.roo, apiModelId: "roo/code-supernova", // Old model ID }, existing: { - apiProvider: "roo", + apiProvider: retiredProviderIdentifiers.roo, apiModelId: "roo/code-supernova-1-million", // Already migrated }, otherProvider: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "roo/code-supernova", // Should not be migrated (different provider) }, noProvider: { @@ -329,7 +330,7 @@ describe("ProviderSettingsManager", () => { await providerSettingsManager.saveConfig("router-profile", { id: "router-id", - apiProvider: "roo", + apiProvider: retiredProviderIdentifiers.roo, apiModelId: "roo/code-supernova", rooApiKey: "router-key", } as any) @@ -357,7 +358,7 @@ describe("ProviderSettingsManager", () => { id: "default", }, test: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, id: "test-id", }, }, @@ -376,7 +377,7 @@ describe("ProviderSettingsManager", () => { const configs = await providerSettingsManager.listConfig() expect(configs).toEqual([ { name: "default", id: "default", apiProvider: undefined }, - { name: "test", id: "test-id", apiProvider: "anthropic" }, + { name: "test", id: "test-id", apiProvider: providerIdentifiers.anthropic }, ]) }) @@ -426,7 +427,7 @@ describe("ProviderSettingsManager", () => { ) const newConfig: ProviderSettings = { - apiProvider: "vertex", + apiProvider: providerIdentifiers.vertex, apiModelId: "gemini-2.5-flash-preview-05-20", vertexKeyFile: "test-key-file", } @@ -499,7 +500,7 @@ describe("ProviderSettingsManager", () => { ) const newConfig: ProviderSettings = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "test-key", } const newConfigWithExtra: ProviderSettings = { @@ -538,7 +539,7 @@ describe("ProviderSettingsManager", () => { currentApiConfigName: "default", apiConfigs: { test: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "old-key", id: "test-id", }, @@ -551,7 +552,7 @@ describe("ProviderSettingsManager", () => { mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig)) const updatedConfig: ProviderSettings = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "new-key", } @@ -561,7 +562,7 @@ describe("ProviderSettingsManager", () => { currentApiConfigName: "default", apiConfigs: { test: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "new-key", id: "test-id", }, @@ -614,7 +615,7 @@ describe("ProviderSettingsManager", () => { // Include a legacy provider-specific field (groqApiKey) that is no // longer in the schema — passthrough() must keep it. const retiredConfig = { - apiProvider: "groq", + apiProvider: retiredProviderIdentifiers.groq, apiKey: "legacy-key", apiModelId: "legacy-model", openAiBaseUrl: "https://legacy.example/v1", @@ -647,7 +648,7 @@ describe("ProviderSettingsManager", () => { id: "default", }, test: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, id: "test-id", }, }, @@ -704,7 +705,7 @@ describe("ProviderSettingsManager", () => { currentApiConfigName: "default", apiConfigs: { test: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "test-key", id: "test-id", }, @@ -720,7 +721,11 @@ describe("ProviderSettingsManager", () => { const { name, ...providerSettings } = await providerSettingsManager.activateProfile({ name: "test" }) expect(name).toBe("test") - expect(providerSettings).toEqual({ apiProvider: "anthropic", apiKey: "test-key", id: "test-id" }) + expect(providerSettings).toEqual({ + apiProvider: providerIdentifiers.anthropic, + apiKey: "test-key", + id: "test-id", + }) // Get the stored config to check the structure. const calls = mockSecrets.store.mock.calls @@ -728,7 +733,7 @@ describe("ProviderSettingsManager", () => { expect(storedConfig.currentApiConfigName).toBe("test") expect(storedConfig.apiConfigs.test).toEqual({ - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "test-key", id: "test-id", }) @@ -751,7 +756,7 @@ describe("ProviderSettingsManager", () => { mockSecrets.get.mockResolvedValue( JSON.stringify({ currentApiConfigName: "default", - apiConfigs: { test: { apiProvider: "anthropic", id: "test-id" } }, + apiConfigs: { test: { apiProvider: providerIdentifiers.anthropic, id: "test-id" } }, migrations: { rateLimitSecondsMigrated: true, openAiHeadersMigrated: true, @@ -771,7 +776,7 @@ describe("ProviderSettingsManager", () => { currentApiConfigName: "valid", apiConfigs: { valid: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "valid-key", apiModelId: "claude-3-opus-20240229", id: "valid-id", @@ -818,7 +823,7 @@ describe("ProviderSettingsManager", () => { apiConfigs: { retiredProvider: { id: "retired-id", - apiProvider: "groq", + apiProvider: retiredProviderIdentifiers.groq, apiKey: "legacy-key", apiModelId: "legacy-model", openAiBaseUrl: "https://legacy.example/v1", @@ -861,7 +866,7 @@ describe("ProviderSettingsManager", () => { currentApiConfigName: "valid", apiConfigs: { valid: { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "valid-key", apiModelId: "claude-3-opus-20240229", rateLimitSeconds: 0, @@ -912,7 +917,7 @@ describe("ProviderSettingsManager", () => { apiConfigs: { retired: { id: "retired-id", - apiProvider: "groq", + apiProvider: retiredProviderIdentifiers.groq, apiKey: "legacy-key", apiModelId: "legacy-model", openAiBaseUrl: "https://legacy.example/v1", @@ -940,7 +945,7 @@ describe("ProviderSettingsManager", () => { apiConfigs: { glm: { id: "glm-id", - apiProvider: "zai", + apiProvider: providerIdentifiers.zai, apiModelId: "glm-5.1", modelMaxTokens: 8192, modelMaxThinkingTokens: 2048, @@ -964,7 +969,7 @@ describe("ProviderSettingsManager", () => { apiConfigs: { anthropic: { id: "anthropic-id", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-haiku-20241022", modelMaxTokens: 8192, modelMaxThinkingTokens: 2048, @@ -987,7 +992,7 @@ describe("ProviderSettingsManager", () => { mockSecrets.get.mockResolvedValue( JSON.stringify({ currentApiConfigName: "test", - apiConfigs: { test: { apiProvider: "anthropic", id: "test-id" } }, + apiConfigs: { test: { apiProvider: providerIdentifiers.anthropic, id: "test-id" } }, }), ) @@ -1002,7 +1007,10 @@ describe("ProviderSettingsManager", () => { it("should return true for existing config", async () => { const existingConfig: ProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { default: { id: "default" }, test: { apiProvider: "anthropic", id: "test-id" } }, + apiConfigs: { + default: { id: "default" }, + test: { apiProvider: providerIdentifiers.anthropic, id: "test-id" }, + }, migrations: { rateLimitSecondsMigrated: false }, } @@ -1045,7 +1053,7 @@ describe("ProviderSettingsManager", () => { const cloudProfiles = { "cloud-profile": { id: "cloud-id-1", - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, apiKey: "secret-key", // This should be removed apiModelId: "claude-3-opus-20240229", }, @@ -1060,7 +1068,7 @@ describe("ProviderSettingsManager", () => { const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1]) expect(storedConfig.apiConfigs["cloud-profile"]).toEqual({ id: "cloud-id-1", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-opus-20240229", // apiKey should be removed }) @@ -1074,7 +1082,7 @@ describe("ProviderSettingsManager", () => { default: { id: "default-id" }, "existing-cloud": { id: "cloud-id-1", - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, apiKey: "existing-secret", apiModelId: "claude-3-haiku-20240307", }, @@ -1087,7 +1095,7 @@ describe("ProviderSettingsManager", () => { const cloudProfiles = { "updated-name": { id: "cloud-id-1", - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, apiKey: "new-secret", // Should be ignored apiModelId: "claude-3-opus-20240229", }, @@ -1102,7 +1110,7 @@ describe("ProviderSettingsManager", () => { const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1]) expect(storedConfig.apiConfigs["updated-name"]).toEqual({ id: "cloud-id-1", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "existing-secret", // Preserved apiModelId: "claude-3-opus-20240229", // Updated }) @@ -1115,8 +1123,8 @@ describe("ProviderSettingsManager", () => { currentApiConfigName: "default", apiConfigs: { default: { id: "default-id" }, - "cloud-profile-1": { id: "cloud-id-1", apiProvider: "anthropic" as const }, - "cloud-profile-2": { id: "cloud-id-2", apiProvider: "openai" as const }, + "cloud-profile-1": { id: "cloud-id-1", apiProvider: providerIdentifiers.anthropic }, + "cloud-profile-2": { id: "cloud-id-2", apiProvider: providerIdentifiers.openai }, }, cloudProfileIds: ["cloud-id-1", "cloud-id-2"], } @@ -1126,7 +1134,7 @@ describe("ProviderSettingsManager", () => { const cloudProfiles = { "cloud-profile-1": { id: "cloud-id-1", - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, }, // cloud-profile-2 is missing, should be deleted } @@ -1148,7 +1156,7 @@ describe("ProviderSettingsManager", () => { currentApiConfigName: "default", apiConfigs: { default: { id: "default-id" }, - "conflict-name": { id: "local-id", apiProvider: "openai" as const }, + "conflict-name": { id: "local-id", apiProvider: providerIdentifiers.openai }, }, cloudProfileIds: [], } @@ -1158,7 +1166,7 @@ describe("ProviderSettingsManager", () => { const cloudProfiles = { "conflict-name": { id: "cloud-id-1", - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, }, } @@ -1171,11 +1179,11 @@ describe("ProviderSettingsManager", () => { const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1]) expect(storedConfig.apiConfigs["conflict-name"]).toEqual({ id: "cloud-id-1", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, }) expect(storedConfig.apiConfigs["conflict-name_local"]).toEqual({ id: "local-id", - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, }) expect(storedConfig.cloudProfileIds).toEqual(["cloud-id-1"]) }) @@ -1185,8 +1193,8 @@ describe("ProviderSettingsManager", () => { currentApiConfigName: "default", apiConfigs: { default: { id: "default-id" }, - "conflict-name": { id: "local-id-1", apiProvider: "openai" as const }, - "conflict-name_local": { id: "local-id-2", apiProvider: "vertex" as const }, + "conflict-name": { id: "local-id-1", apiProvider: providerIdentifiers.openai }, + "conflict-name_local": { id: "local-id-2", apiProvider: providerIdentifiers.vertex }, }, cloudProfileIds: [], } @@ -1196,7 +1204,7 @@ describe("ProviderSettingsManager", () => { const cloudProfiles = { "conflict-name": { id: "cloud-id-1", - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, }, } @@ -1209,15 +1217,15 @@ describe("ProviderSettingsManager", () => { const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1]) expect(storedConfig.apiConfigs["conflict-name"]).toEqual({ id: "cloud-id-1", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, }) expect(storedConfig.apiConfigs["conflict-name_1"]).toEqual({ id: "local-id-1", - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, }) expect(storedConfig.apiConfigs["conflict-name_local"]).toEqual({ id: "local-id-2", - apiProvider: "vertex", + apiProvider: providerIdentifiers.vertex, }) }) @@ -1226,8 +1234,8 @@ describe("ProviderSettingsManager", () => { currentApiConfigName: "default", apiConfigs: { default: { id: "default-id" }, - "cloud-profile-1": { id: "cloud-id-1", apiProvider: "anthropic" as const }, - "cloud-profile-2": { id: "cloud-id-2", apiProvider: "openai" as const }, + "cloud-profile-1": { id: "cloud-id-1", apiProvider: providerIdentifiers.anthropic }, + "cloud-profile-2": { id: "cloud-id-2", apiProvider: providerIdentifiers.openai }, }, cloudProfileIds: ["cloud-id-1", "cloud-id-2"], } @@ -1263,11 +1271,11 @@ describe("ProviderSettingsManager", () => { const cloudProfiles = { "valid-profile": { id: "cloud-id-1", - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, }, "invalid-profile": { // Missing id - apiProvider: "openai" as const, + apiProvider: providerIdentifiers.openai, }, } @@ -1288,9 +1296,9 @@ describe("ProviderSettingsManager", () => { currentApiConfigName: "default", apiConfigs: { default: { id: "default-id" }, - "keep-cloud": { id: "cloud-id-1", apiProvider: "anthropic" as const, apiKey: "secret1" }, - "delete-cloud": { id: "cloud-id-2", apiProvider: "openai" as const }, - "rename-me": { id: "local-id", apiProvider: "vertex" as const }, + "keep-cloud": { id: "cloud-id-1", apiProvider: providerIdentifiers.anthropic, apiKey: "secret1" }, + "delete-cloud": { id: "cloud-id-2", apiProvider: providerIdentifiers.openai }, + "rename-me": { id: "local-id", apiProvider: providerIdentifiers.vertex }, }, cloudProfileIds: ["cloud-id-1", "cloud-id-2"], } @@ -1300,19 +1308,19 @@ describe("ProviderSettingsManager", () => { const cloudProfiles = { "updated-keep": { id: "cloud-id-1", - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, apiKey: "new-secret", // Should be ignored apiModelId: "claude-3-opus-20240229", }, "rename-me": { id: "cloud-id-3", - apiProvider: "openai" as const, + apiProvider: providerIdentifiers.openai, }, // delete-cloud is missing (should be deleted) // new profile "new-cloud": { id: "cloud-id-4", - apiProvider: "vertex" as const, + apiProvider: providerIdentifiers.vertex, }, } @@ -1331,7 +1339,7 @@ describe("ProviderSettingsManager", () => { // Check updates expect(storedConfig.apiConfigs["updated-keep"]).toEqual({ id: "cloud-id-1", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "secret1", // preserved apiModelId: "claude-3-opus-20240229", }) @@ -1339,17 +1347,17 @@ describe("ProviderSettingsManager", () => { // Check renames expect(storedConfig.apiConfigs["rename-me_local"]).toEqual({ id: "local-id", - apiProvider: "vertex", + apiProvider: providerIdentifiers.vertex, }) expect(storedConfig.apiConfigs["rename-me"]).toEqual({ id: "cloud-id-3", - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, }) // Check new additions expect(storedConfig.apiConfigs["new-cloud"]).toEqual({ id: "cloud-id-4", - apiProvider: "vertex", + apiProvider: providerIdentifiers.vertex, }) expect(storedConfig.cloudProfileIds).toEqual(["cloud-id-1", "cloud-id-3", "cloud-id-4"]) @@ -1376,7 +1384,7 @@ describe("ProviderSettingsManager", () => { apiConfigs: { "active-profile": { id: "active-id", - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, apiKey: "old-key", }, }, @@ -1388,7 +1396,7 @@ describe("ProviderSettingsManager", () => { const cloudProfiles = { "active-profile": { id: "active-id", - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-opus-20240229", // Updated setting }, } @@ -1404,8 +1412,8 @@ describe("ProviderSettingsManager", () => { const existingConfig: ProviderProfiles = { currentApiConfigName: "active-profile", apiConfigs: { - "active-profile": { id: "active-id", apiProvider: "anthropic" as const }, - "backup-profile": { id: "backup-id", apiProvider: "openai" as const }, + "active-profile": { id: "active-id", apiProvider: providerIdentifiers.anthropic }, + "backup-profile": { id: "backup-id", apiProvider: providerIdentifiers.openai }, }, cloudProfileIds: ["active-id"], } @@ -1425,7 +1433,7 @@ describe("ProviderSettingsManager", () => { const existingConfig: ProviderProfiles = { currentApiConfigName: "only-profile", apiConfigs: { - "only-profile": { id: "only-id", apiProvider: "anthropic" as const }, + "only-profile": { id: "only-id", apiProvider: providerIdentifiers.anthropic }, }, cloudProfileIds: ["only-id"], } @@ -1449,8 +1457,8 @@ describe("ProviderSettingsManager", () => { const existingConfig: ProviderProfiles = { currentApiConfigName: "local-profile", apiConfigs: { - "local-profile": { id: "local-id", apiProvider: "anthropic" as const }, - "cloud-profile": { id: "cloud-id", apiProvider: "openai" as const }, + "local-profile": { id: "local-id", apiProvider: providerIdentifiers.anthropic }, + "cloud-profile": { id: "cloud-id", apiProvider: providerIdentifiers.openai }, }, cloudProfileIds: ["cloud-id"], } @@ -1460,7 +1468,7 @@ describe("ProviderSettingsManager", () => { const cloudProfiles = { "cloud-profile": { id: "cloud-id", - apiProvider: "openai" as const, + apiProvider: providerIdentifiers.openai, apiModelId: "gpt-4", // Updated cloud profile }, } diff --git a/src/core/config/__tests__/importExport.spec.ts b/src/core/config/__tests__/importExport.spec.ts index 78e6afca75..58104009d8 100644 --- a/src/core/config/__tests__/importExport.spec.ts +++ b/src/core/config/__tests__/importExport.spec.ts @@ -18,6 +18,7 @@ import { CustomModesManager } from "../CustomModesManager" import { safeWriteJson } from "../../../utils/safeWriteJson" import type { Mock } from "vitest" +import { providerIdentifiers, retiredProviderIdentifiers } from "@roo-code/types/provider-identifiers" vi.mock("vscode", () => ({ workspace: { @@ -72,7 +73,10 @@ vi.mock("../../../api", () => ({ buildApiHandler: vi.fn().mockImplementation((config) => { // Return different model info based on the provider and model const getModelInfo = () => { - if (config.apiProvider === "anthropic" && config.apiModelId === "claude-3-5-sonnet-20241022") { + if ( + config.apiProvider === providerIdentifiers.anthropic && + config.apiModelId === "claude-3-5-sonnet-20241022" + ) { return { id: "claude-3-5-sonnet-20241022", info: { @@ -177,7 +181,9 @@ describe("importExport", () => { const mockFileContent = JSON.stringify({ providerProfiles: { currentApiConfigName: "test", - apiConfigs: { test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" } }, + apiConfigs: { + test: { apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "test-id" }, + }, }, globalSettings: { mode: "code", autoApprovalEnabled: true }, }) @@ -186,14 +192,14 @@ describe("importExport", () => { const previousProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, } mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "test", id: "test-id", apiProvider: "openai" as ProviderName }, - { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, + { name: "test", id: "test-id", apiProvider: providerIdentifiers.openai }, + { name: "default", id: "default-id", apiProvider: providerIdentifiers.anthropic }, ]) mockContextProxy.export.mockResolvedValue({ mode: "code" }) @@ -211,8 +217,8 @@ describe("importExport", () => { expect(mockProviderSettingsManager.import).toHaveBeenCalledWith({ currentApiConfigName: "test", apiConfigs: { - default: { apiProvider: "anthropic" as ProviderName, id: "default-id" }, - test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" }, + default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" }, + test: { apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "test-id" }, }, modeApiConfigs: {}, }) @@ -221,8 +227,8 @@ describe("importExport", () => { expect(mockContextProxy.setValue).toHaveBeenCalledWith("currentApiConfigName", "test") expect(mockContextProxy.setValue).toHaveBeenCalledWith("listApiConfigMeta", [ - { name: "test", id: "test-id", apiProvider: "openai" as ProviderName }, - { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, + { name: "test", id: "test-id", apiProvider: providerIdentifiers.openai }, + { name: "default", id: "default-id", apiProvider: providerIdentifiers.anthropic }, ]) }) @@ -255,7 +261,9 @@ describe("importExport", () => { const mockFileContent = JSON.stringify({ providerProfiles: { currentApiConfigName: "test", - apiConfigs: { test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" } }, + apiConfigs: { + test: { apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "test-id" }, + }, }, }) @@ -263,14 +271,14 @@ describe("importExport", () => { const previousProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, } mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "test", id: "test-id", apiProvider: "openai" as ProviderName }, - { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, + { name: "test", id: "test-id", apiProvider: providerIdentifiers.openai }, + { name: "default", id: "default-id", apiProvider: providerIdentifiers.anthropic }, ]) mockContextProxy.export.mockResolvedValue({ mode: "code" }) @@ -287,8 +295,8 @@ describe("importExport", () => { expect(mockProviderSettingsManager.import).toHaveBeenCalledWith({ currentApiConfigName: "test", apiConfigs: { - default: { apiProvider: "anthropic" as ProviderName, id: "default-id" }, - test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" }, + default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" }, + test: { apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "test-id" }, }, modeApiConfigs: {}, }) @@ -297,8 +305,8 @@ describe("importExport", () => { expect(mockContextProxy.setValues).toHaveBeenCalledWith({}) expect(mockContextProxy.setValue).toHaveBeenCalledWith("currentApiConfigName", "test") expect(mockContextProxy.setValue).toHaveBeenCalledWith("listApiConfigMeta", [ - { name: "test", id: "test-id", apiProvider: "openai" as ProviderName }, - { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, + { name: "test", id: "test-id", apiProvider: providerIdentifiers.openai }, + { name: "default", id: "default-id", apiProvider: providerIdentifiers.anthropic }, ]) }) @@ -338,7 +346,10 @@ describe("importExport", () => { it("should not clobber existing api configs", async () => { const providerSettingsManager = new ProviderSettingsManager(mockExtensionContext) - await providerSettingsManager.saveConfig("openai", { apiProvider: "openai", id: "openai" }) + await providerSettingsManager.saveConfig("openai", { + apiProvider: providerIdentifiers.openai, + id: "openai", + }) const configs = await providerSettingsManager.listConfig() expect(configs[0].name).toBe("default") @@ -349,7 +360,7 @@ describe("importExport", () => { globalSettings: { mode: "code" }, providerProfiles: { currentApiConfigName: "anthropic", - apiConfigs: { default: { apiProvider: "anthropic" as const, id: "anthropic" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "anthropic" } }, }, }) @@ -412,7 +423,9 @@ describe("importExport", () => { const mockFileContent = JSON.stringify({ providerProfiles: { currentApiConfigName: "test", - apiConfigs: { test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" } }, + apiConfigs: { + test: { apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "test-id" }, + }, }, globalSettings: { mode: "code", autoApprovalEnabled: true }, }) @@ -422,13 +435,13 @@ describe("importExport", () => { const previousProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, } mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "test", id: "test-id", apiProvider: "openai" as ProviderName }, - { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, + { name: "test", id: "test-id", apiProvider: providerIdentifiers.openai }, + { name: "default", id: "default-id", apiProvider: providerIdentifiers.anthropic }, ]) mockContextProxy.export.mockResolvedValue({ mode: "code" }) @@ -447,8 +460,8 @@ describe("importExport", () => { expect(mockProviderSettingsManager.import).toHaveBeenCalledWith({ currentApiConfigName: "test", apiConfigs: { - default: { apiProvider: "anthropic" as ProviderName, id: "default-id" }, - test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" }, + default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" }, + test: { apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "test-id" }, }, modeApiConfigs: {}, }) @@ -499,7 +512,7 @@ describe("importExport", () => { currentApiConfigName: "openai-provider", apiConfigs: { "openai-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiModelId: "gpt-4", id: "openai-id", apiKey: "test-key", @@ -514,13 +527,13 @@ describe("importExport", () => { const previousProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, } mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "openai-provider", id: "openai-id", apiProvider: "openai" as ProviderName }, - { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, + { name: "openai-provider", id: "openai-id", apiProvider: providerIdentifiers.openai }, + { name: "default", id: "default-id", apiProvider: providerIdentifiers.anthropic }, ]) mockContextProxy.export.mockResolvedValue({ mode: "code" }) @@ -538,9 +551,9 @@ describe("importExport", () => { expect(mockProviderSettingsManager.import).toHaveBeenCalledWith({ currentApiConfigName: "openai-provider", apiConfigs: { - default: { apiProvider: "anthropic" as ProviderName, id: "default-id" }, + default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" }, "openai-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiModelId: "gpt-4", apiKey: "test-key", id: "openai-id", @@ -563,7 +576,7 @@ describe("importExport", () => { currentApiConfigName: "valid-profile", apiConfigs: { "valid-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "valid-id", }, @@ -581,11 +594,11 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "valid-profile", id: "valid-id", apiProvider: "openai" as ProviderName }, - { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, + { name: "valid-profile", id: "valid-id", apiProvider: providerIdentifiers.openai }, + { name: "default", id: "default-id", apiProvider: providerIdentifiers.anthropic }, ]) const result = await importSettings({ @@ -623,7 +636,7 @@ describe("importExport", () => { currentApiConfigName: "valid-profile", apiConfigs: { "valid-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "valid-id", }, @@ -641,10 +654,10 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "valid-profile", id: "valid-id", apiProvider: "openai" as ProviderName }, + { name: "valid-profile", id: "valid-id", apiProvider: providerIdentifiers.openai }, ]) const result = await importSettings({ @@ -695,7 +708,7 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) const result = await importSettings({ @@ -719,7 +732,7 @@ describe("importExport", () => { currentApiConfigName: "valid-profile", apiConfigs: { "valid-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "valid-id", }, @@ -738,10 +751,10 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "valid-profile", id: "valid-id", apiProvider: "openai" as ProviderName }, + { name: "valid-profile", id: "valid-id", apiProvider: providerIdentifiers.openai }, ]) const seenImportedAt: Array = [] @@ -800,7 +813,7 @@ describe("importExport", () => { currentApiConfigName: "valid-profile", apiConfigs: { "valid-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "valid-id", }, @@ -814,10 +827,10 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "valid-profile", id: "valid-id", apiProvider: "openai" as ProviderName }, + { name: "valid-profile", id: "valid-id", apiProvider: providerIdentifiers.openai }, ]) const seenImportedAt: Array = [] @@ -851,12 +864,12 @@ describe("importExport", () => { currentApiConfigName: "anthropic-profile", apiConfigs: { "anthropic-profile": { - apiProvider: "anthropic" as ProviderName, + apiProvider: providerIdentifiers.anthropic, anthropicApiKey: "key-1", id: "anthropic-id", }, "openai-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "key-2", id: "openai-id", }, @@ -879,11 +892,11 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "anthropic-profile", id: "anthropic-id", apiProvider: "anthropic" as ProviderName }, - { name: "openai-profile", id: "openai-id", apiProvider: "openai" as ProviderName }, + { name: "anthropic-profile", id: "anthropic-id", apiProvider: providerIdentifiers.anthropic }, + { name: "openai-profile", id: "openai-id", apiProvider: providerIdentifiers.openai }, ]) const result = await importSettings({ @@ -919,7 +932,7 @@ describe("importExport", () => { currentApiConfigName: "router-profile", apiConfigs: { "router-profile": { - apiProvider: "roo", + apiProvider: retiredProviderIdentifiers.roo, apiModelId: "roo/code-supernova", rooApiKey: "router-key", id: "router-id", @@ -933,7 +946,7 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) const result = await importSettings({ @@ -964,7 +977,7 @@ describe("importExport", () => { id: "invalid-current-id", }, "valid-fallback-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "fallback-id", }, @@ -977,10 +990,10 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "valid-fallback-profile", id: "fallback-id", apiProvider: "openai" as ProviderName }, + { name: "valid-fallback-profile", id: "fallback-id", apiProvider: providerIdentifiers.openai }, ]) const result = await importSettings({ @@ -1040,7 +1053,7 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "existing-profile", apiConfigs: { - "existing-profile": { apiProvider: "anthropic" as ProviderName, id: "existing-id" }, + "existing-profile": { apiProvider: providerIdentifiers.anthropic, id: "existing-id" }, }, }) @@ -1062,7 +1075,7 @@ describe("importExport", () => { currentApiConfigName: "valid-profile", apiConfigs: { "valid-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "valid-id", }, @@ -1086,10 +1099,10 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "valid-profile", id: "valid-id", apiProvider: "openai" as ProviderName }, + { name: "valid-profile", id: "valid-id", apiProvider: providerIdentifiers.openai }, ]) const mockProvider = { @@ -1135,14 +1148,14 @@ describe("importExport", () => { currentApiConfigName: "valid-profile", apiConfigs: { "valid-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "valid-id", }, }, }, globalSettings: { - imageGenerationProvider: "roo", + imageGenerationProvider: retiredProviderIdentifiers.roo, openRouterImageGenerationSelectedModel: "openrouter/model-1", customInstructions: "Keep this setting", }, @@ -1151,10 +1164,10 @@ describe("importExport", () => { ;(fs.readFile as Mock).mockResolvedValue(mockFileContent) mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "valid-profile", id: "valid-id", apiProvider: "openai" as ProviderName }, + { name: "valid-profile", id: "valid-id", apiProvider: providerIdentifiers.openai }, ]) const result = await importSettings({ @@ -1185,7 +1198,7 @@ describe("importExport", () => { currentApiConfigName: "valid-profile", apiConfigs: { "valid-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "valid-id", }, @@ -1202,10 +1215,10 @@ describe("importExport", () => { ;(fs.readFile as Mock).mockResolvedValue(mockFileContent) mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "valid-profile", id: "valid-id", apiProvider: "openai" as ProviderName }, + { name: "valid-profile", id: "valid-id", apiProvider: providerIdentifiers.openai }, ]) const result = await importSettings({ @@ -1237,7 +1250,7 @@ describe("importExport", () => { currentApiConfigName: "valid-profile", apiConfigs: { "valid-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "valid-id", }, @@ -1259,10 +1272,10 @@ describe("importExport", () => { ;(fs.readFile as Mock).mockResolvedValue(mockFileContent) mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "valid-profile", id: "valid-id", apiProvider: "openai" as ProviderName }, + { name: "valid-profile", id: "valid-id", apiProvider: providerIdentifiers.openai }, ]) const result = await importSettings({ @@ -1288,7 +1301,7 @@ describe("importExport", () => { currentApiConfigName: "valid-profile", apiConfigs: { "valid-profile": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, apiKey: "test-key", id: "valid-id", }, @@ -1303,10 +1316,10 @@ describe("importExport", () => { ;(fs.access as Mock).mockResolvedValue(undefined) mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "valid-profile", id: "valid-id", apiProvider: "openai" as ProviderName }, + { name: "valid-profile", id: "valid-id", apiProvider: providerIdentifiers.openai }, ]) const mockProvider = { @@ -1368,7 +1381,7 @@ describe("importExport", () => { const mockProviderProfiles = { currentApiConfigName: "test", - apiConfigs: { test: { apiProvider: "openai" as ProviderName, id: "test-id" } }, + apiConfigs: { test: { apiProvider: providerIdentifiers.openai, id: "test-id" } }, migrations: { rateLimitSecondsMigrated: false }, } @@ -1403,7 +1416,7 @@ describe("importExport", () => { const mockProviderProfiles = { currentApiConfigName: "test", - apiConfigs: { test: { apiProvider: "openai" as ProviderName, id: "test-id" } }, + apiConfigs: { test: { apiProvider: providerIdentifiers.openai, id: "test-id" } }, migrations: { rateLimitSecondsMigrated: false }, } @@ -1435,7 +1448,7 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "test", - apiConfigs: { test: { apiProvider: "openai" as ProviderName, id: "test-id" } }, + apiConfigs: { test: { apiProvider: providerIdentifiers.openai, id: "test-id" } }, migrations: { rateLimitSecondsMigrated: false }, }) @@ -1465,7 +1478,7 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "test", - apiConfigs: { test: { apiProvider: "openai" as ProviderName, id: "test-id" } }, + apiConfigs: { test: { apiProvider: providerIdentifiers.openai, id: "test-id" } }, migrations: { rateLimitSecondsMigrated: false }, }) @@ -1510,12 +1523,12 @@ describe("importExport", () => { currentApiConfigName: "openai-compatible-provider", apiConfigs: { "openai-compatible-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "openai-compatible-id", // Remove OpenAI Compatible settings from provider profile }, "ollama-provider": { - apiProvider: "ollama" as ProviderName, + apiProvider: providerIdentifiers.ollama, id: "ollama-id", codebaseIndexOllamaBaseUrl: "http://localhost:11434", }, @@ -1560,7 +1573,7 @@ describe("importExport", () => { currentApiConfigName: "test-provider", apiConfigs: { "test-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "test-id", // Remove OpenAI Compatible settings from provider profile }, @@ -1607,17 +1620,17 @@ describe("importExport", () => { currentApiConfigName: "openai-compatible-provider", apiConfigs: { "openai-compatible-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "openai-compatible-id", // Remove OpenAI Compatible settings from provider profile }, "ollama-provider": { - apiProvider: "ollama" as ProviderName, + apiProvider: providerIdentifiers.ollama, id: "ollama-id", codebaseIndexOllamaBaseUrl: "http://localhost:11434", }, "anthropic-provider": { - apiProvider: "anthropic" as ProviderName, + apiProvider: providerIdentifiers.anthropic, id: "anthropic-id", }, }, @@ -1667,7 +1680,7 @@ describe("importExport", () => { currentApiConfigName: "incomplete-provider", apiConfigs: { "incomplete-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "incomplete-id", // Missing codebaseIndexOpenAiCompatibleBaseUrl and dimension }, @@ -1713,7 +1726,7 @@ describe("importExport", () => { currentApiConfigName: "openai-provider", apiConfigs: { "openai-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "openai-id", // Regular OpenAI provider without OpenAI Compatible settings }, @@ -1725,7 +1738,7 @@ describe("importExport", () => { mode: "code", codebaseIndexConfig: { codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai" as const, // Not openai-compatible + codebaseIndexEmbedderProvider: providerIdentifiers.openai, // Not openai-compatible codebaseIndexEmbedderModelId: "text-embedding-ada-002", codebaseIndexEmbedderBaseUrl: "https://api.openai.com/v1", }, @@ -1756,7 +1769,7 @@ describe("importExport", () => { currentApiConfigName: "nonexistent-provider", apiConfigs: { "other-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "other-id", }, }, @@ -1802,7 +1815,7 @@ describe("importExport", () => { currentApiConfigName: "openai-compatible-provider", apiConfigs: { "openai-compatible-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "openai-compatible-id", // Provider-specific settings remain in provider profile codebaseIndexOpenAiCompatibleBaseUrl: "https://old-url.example.com/v1", @@ -1829,7 +1842,7 @@ describe("importExport", () => { const previousProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, } mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles) @@ -1837,9 +1850,9 @@ describe("importExport", () => { { name: "openai-compatible-provider", id: "openai-compatible-id", - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, }, - { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, + { name: "default", id: "default-id", apiProvider: providerIdentifiers.anthropic }, ]) const result = await importSettings({ @@ -1877,7 +1890,7 @@ describe("importExport", () => { currentApiConfigName: "openai-compatible-provider", apiConfigs: { "openai-compatible-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "openai-compatible-id", }, }, @@ -1898,7 +1911,7 @@ describe("importExport", () => { const previousProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, } mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles) @@ -1906,7 +1919,7 @@ describe("importExport", () => { { name: "openai-compatible-provider", id: "openai-compatible-id", - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, }, ]) @@ -1928,7 +1941,7 @@ describe("importExport", () => { currentApiConfigName: "anthropic-provider", apiConfigs: { "anthropic-provider": { - apiProvider: "anthropic" as ProviderName, + apiProvider: providerIdentifiers.anthropic, id: "anthropic-id", }, }, @@ -1938,7 +1951,7 @@ describe("importExport", () => { mode: "code", codebaseIndexConfig: { codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai" as const, // Not openai-compatible + codebaseIndexEmbedderProvider: providerIdentifiers.openai, // Not openai-compatible codebaseIndexEmbedderModelId: "text-embedding-ada-002", codebaseIndexEmbedderBaseUrl: "https://api.openai.com/v1", codebaseIndexEmbedderModelDimension: 1536, @@ -1950,12 +1963,12 @@ describe("importExport", () => { const previousProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, } mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "anthropic-provider", id: "anthropic-id", apiProvider: "anthropic" as ProviderName }, + { name: "anthropic-provider", id: "anthropic-id", apiProvider: providerIdentifiers.anthropic }, ]) const result = await importSettings({ @@ -1986,7 +1999,7 @@ describe("importExport", () => { currentApiConfigName: "test-openai-compatible", apiConfigs: { "test-openai-compatible": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "test-id", // Remove OpenAI Compatible settings from provider profile }, @@ -2040,10 +2053,10 @@ describe("importExport", () => { clearAllMocks() mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "test-openai-compatible", id: "test-id", apiProvider: "openai" as ProviderName }, + { name: "test-openai-compatible", id: "test-id", apiProvider: providerIdentifiers.openai }, ]) // Step 7: Import the settings back @@ -2079,7 +2092,7 @@ describe("importExport", () => { currentApiConfigName: "test-openai-compatible", apiConfigs: { "test-openai-compatible": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "test-id", // Remove OpenAI Compatible settings from provider profile }, @@ -2128,10 +2141,10 @@ describe("importExport", () => { clearAllMocks() mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "default", - apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + apiConfigs: { default: { apiProvider: providerIdentifiers.anthropic, id: "default-id" } }, }) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "test-openai-compatible", id: "test-id", apiProvider: "openai" as ProviderName }, + { name: "test-openai-compatible", id: "test-id", apiProvider: providerIdentifiers.openai }, ]) // Import the settings back @@ -2154,7 +2167,7 @@ describe("importExport", () => { currentApiConfigName: "test-openai-compatible", apiConfigs: { "test-openai-compatible": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "test-id", // Remove OpenAI Compatible settings from provider profile }, @@ -2213,13 +2226,13 @@ describe("importExport", () => { currentApiConfigName: "provider-a", apiConfigs: { "provider-a": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "provider-a-id", codebaseIndexOpenAiCompatibleBaseUrl: "https://api-a.example.com/v1", codebaseIndexOpenAiCompatibleModelDimension: 1536, }, "provider-b": { - apiProvider: "anthropic" as ProviderName, + apiProvider: providerIdentifiers.anthropic, id: "provider-b-id", }, }, @@ -2242,7 +2255,7 @@ describe("importExport", () => { currentApiConfigName: "provider-b", // Different from exported settings! apiConfigs: { "provider-b": { - apiProvider: "anthropic" as ProviderName, + apiProvider: providerIdentifiers.anthropic, id: "provider-b-id", }, }, @@ -2254,8 +2267,8 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue(currentProviderProfiles) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "provider-a", id: "provider-a-id", apiProvider: "openai" as ProviderName }, - { name: "provider-b", id: "provider-b-id", apiProvider: "anthropic" as ProviderName }, + { name: "provider-a", id: "provider-a-id", apiProvider: providerIdentifiers.openai }, + { name: "provider-b", id: "provider-b-id", apiProvider: providerIdentifiers.anthropic }, ]) // Step 4: Import the settings @@ -2292,12 +2305,12 @@ describe("importExport", () => { currentApiConfigName: "openai-compatible-provider", apiConfigs: { "openai-compatible-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "openai-compatible-id", // NO OpenAI Compatible settings here in the fixed version }, "anthropic-provider": { - apiProvider: "anthropic" as ProviderName, + apiProvider: providerIdentifiers.anthropic, id: "anthropic-id", }, }, @@ -2322,7 +2335,7 @@ describe("importExport", () => { currentApiConfigName: "anthropic-provider", apiConfigs: { "anthropic-provider": { - apiProvider: "anthropic" as ProviderName, + apiProvider: providerIdentifiers.anthropic, id: "anthropic-id", }, }, @@ -2336,9 +2349,9 @@ describe("importExport", () => { { name: "openai-compatible-provider", id: "openai-compatible-id", - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, }, - { name: "anthropic-provider", id: "anthropic-id", apiProvider: "anthropic" as ProviderName }, + { name: "anthropic-provider", id: "anthropic-id", apiProvider: providerIdentifiers.anthropic }, ]) const importResult = await importSettings({ @@ -2377,11 +2390,11 @@ describe("importExport", () => { currentApiConfigName: "anthropic-provider", apiConfigs: { "anthropic-provider": { - apiProvider: "anthropic" as ProviderName, + apiProvider: providerIdentifiers.anthropic, id: "anthropic-id", }, "openai-compatible-provider": { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "openai-compatible-id", // NO OpenAI Compatible settings in provider profiles }, @@ -2407,7 +2420,7 @@ describe("importExport", () => { currentApiConfigName: "default", apiConfigs: { default: { - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, id: "default-id", }, }, @@ -2418,13 +2431,13 @@ describe("importExport", () => { mockProviderSettingsManager.export.mockResolvedValue(currentProviderProfiles) mockProviderSettingsManager.listConfig.mockResolvedValue([ - { name: "anthropic-provider", id: "anthropic-id", apiProvider: "anthropic" as ProviderName }, + { name: "anthropic-provider", id: "anthropic-id", apiProvider: providerIdentifiers.anthropic }, { name: "openai-compatible-provider", id: "openai-compatible-id", - apiProvider: "openai" as ProviderName, + apiProvider: providerIdentifiers.openai, }, - { name: "default", id: "default-id", apiProvider: "openai" as ProviderName }, + { name: "default", id: "default-id", apiProvider: providerIdentifiers.openai }, ]) const importResult = await importSettings({ @@ -2469,7 +2482,7 @@ describe("importExport", () => { currentApiConfigName: "openrouter-provider", // Current provider is OpenRouter apiConfigs: { "openrouter-provider": { - apiProvider: "openrouter" as ProviderName, + apiProvider: providerIdentifiers.openrouter, id: "openrouter-id", // OpenRouter doesn't have OpenAI Compatible fields }, @@ -2544,7 +2557,7 @@ describe("importExport", () => { // Save a deepseek provider config with token fields await realProviderSettingsManager.saveConfig(providerName, { - apiProvider: "deepseek" as ProviderName, + apiProvider: providerIdentifiers.deepseek, apiModelId: modelId, id: providerId, deepSeekApiKey: "test-key", diff --git a/src/core/task/__tests__/Task.dispose.test.ts b/src/core/task/__tests__/Task.dispose.test.ts index bc14edb366..9f00e9d852 100644 --- a/src/core/task/__tests__/Task.dispose.test.ts +++ b/src/core/task/__tests__/Task.dispose.test.ts @@ -2,6 +2,7 @@ import { type ProviderSettings, RooCodeEventName } from "@roo-code/types" import { Task } from "../Task" import { ClineProvider } from "../../webview/ClineProvider" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Mock dependencies vi.mock("../../webview/ClineProvider") @@ -67,7 +68,7 @@ describe("Task dispose method", () => { // Mock API configuration mockApiConfiguration = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "test-key", } as ProviderSettings @@ -226,7 +227,7 @@ describe("Task.run() idempotency", () => { beforeEach(() => { vi.clearAllMocks() mockProvider = buildMockProvider() - mockApiConfiguration = { apiProvider: "anthropic", apiKey: "test-key" } as ProviderSettings + mockApiConfiguration = { apiProvider: providerIdentifiers.anthropic, apiKey: "test-key" } as ProviderSettings }) test("run() does not invoke startTask when task was already started by constructor", async () => { diff --git a/src/core/task/__tests__/Task.persistence.spec.ts b/src/core/task/__tests__/Task.persistence.spec.ts index 60510a71d1..19bd0c7f34 100644 --- a/src/core/task/__tests__/Task.persistence.spec.ts +++ b/src/core/task/__tests__/Task.persistence.spec.ts @@ -10,6 +10,7 @@ import { TelemetryService } from "@roo-code/telemetry" import { Task } from "../Task" import { ClineProvider } from "../../webview/ClineProvider" import { ContextProxy } from "../../config/ContextProxy" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" type TaskPersistenceAccess = { resumeTaskFromHistory: () => Promise @@ -272,7 +273,7 @@ describe("Task persistence", () => { ) as ClineProvider & Record mockApiConfig = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", apiKey: "test-api-key", } diff --git a/src/core/task/__tests__/Task.resume-eviction-race.spec.ts b/src/core/task/__tests__/Task.resume-eviction-race.spec.ts index 31e532e0e9..8766f38d5b 100644 --- a/src/core/task/__tests__/Task.resume-eviction-race.spec.ts +++ b/src/core/task/__tests__/Task.resume-eviction-race.spec.ts @@ -19,6 +19,7 @@ import { TelemetryService } from "@roo-code/telemetry" import { Task } from "../Task" import { ClineProvider } from "../../webview/ClineProvider" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // ─── Hoisted mocks ─────────────────────────────────────────────────────────── @@ -161,7 +162,7 @@ describe("Task resume/eviction race (Work #1 (no message) regression)", () => { } mockApiConfig = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", apiKey: "test-api-key", } diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts index 163da0c478..37e228f887 100644 --- a/src/core/task/__tests__/Task.spec.ts +++ b/src/core/task/__tests__/Task.spec.ts @@ -3895,7 +3895,7 @@ describe("Telemetry installments (idle/shutdown flush)", () => { mockProvider.postStateToWebview = vi.fn().mockResolvedValue(undefined) mockApiConfig = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", apiKey: "test-api-key", } diff --git a/src/core/task/__tests__/Task.sticky-profile-race.spec.ts b/src/core/task/__tests__/Task.sticky-profile-race.spec.ts index ea0cd2cffc..d0b4e4cdde 100644 --- a/src/core/task/__tests__/Task.sticky-profile-race.spec.ts +++ b/src/core/task/__tests__/Task.sticky-profile-race.spec.ts @@ -5,6 +5,7 @@ import * as vscode from "vscode" import type { ProviderSettings } from "@roo-code/types" import { Task } from "../Task" import { ClineProvider } from "../../webview/ClineProvider" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" vi.mock("@roo-code/telemetry", () => ({ TelemetryService: { @@ -104,7 +105,7 @@ vi.mock("delay", () => ({ describe("Task - sticky provider profile init race", () => { it("does not overwrite task apiConfigName if set during async initialization", async () => { const apiConfig: ProviderSettings = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", apiKey: "test-api-key", } as any diff --git a/src/core/task/__tests__/Task.throttle.test.ts b/src/core/task/__tests__/Task.throttle.test.ts index 0eac687e64..eaacb32faf 100644 --- a/src/core/task/__tests__/Task.throttle.test.ts +++ b/src/core/task/__tests__/Task.throttle.test.ts @@ -3,6 +3,7 @@ import { RooCodeEventName, ProviderSettings, TokenUsage, ToolUsage } from "@roo- import { Task } from "../Task" import { ClineProvider } from "../../webview/ClineProvider" import { hasToolUsageChanged, hasTokenUsageChanged } from "../../../shared/getApiMetrics" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Mock dependencies vi.mock("../../webview/ClineProvider") @@ -86,7 +87,7 @@ describe("Task token usage throttling", () => { // Mock API configuration mockApiConfiguration = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "test-key", } as ProviderSettings diff --git a/src/core/task/__tests__/apiConversationHistory.spec.ts b/src/core/task/__tests__/apiConversationHistory.spec.ts index 76a3ac69fd..7313e4fa1c 100644 --- a/src/core/task/__tests__/apiConversationHistory.spec.ts +++ b/src/core/task/__tests__/apiConversationHistory.spec.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest" import { prepareApiConversationMessage } from "../apiConversationHistory.js" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" describe("prepareApiConversationMessage", () => { beforeEach(() => { @@ -20,7 +21,7 @@ describe("prepareApiConversationMessage", () => { getResponseId: () => "response-1", getThoughtSignature: () => "signature-1", } as any, - apiConfiguration: { apiProvider: "anthropic", apiModelId: "claude-3-5-sonnet" } as any, + apiConfiguration: { apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet" } as any, apiConversationHistory: [], }) as any @@ -40,7 +41,7 @@ describe("prepareApiConversationMessage", () => { message: { role: "assistant", content: "answer" }, reasoning: "visible reasoning", api: {} as any, - apiConfiguration: { apiProvider: "openrouter", openRouterModelId: "openai/gpt-4" } as any, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4" } as any, apiConversationHistory: [], }) as any @@ -55,7 +56,7 @@ describe("prepareApiConversationMessage", () => { message: { role: "assistant", content: "answer" }, reasoning: "private reasoning", api: {} as any, - apiConfiguration: { apiProvider: "anthropic", apiModelId: "claude-3-5-sonnet" } as any, + apiConfiguration: { apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet" } as any, apiConversationHistory: [], }) as any @@ -72,7 +73,7 @@ describe("prepareApiConversationMessage", () => { api: { getEncryptedContent: () => ({ encrypted_content: "encrypted", id: "reasoning-1" }), } as any, - apiConfiguration: { apiProvider: "openrouter", openRouterModelId: "openai/gpt-4" } as any, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4" } as any, apiConversationHistory: [], }) as any @@ -89,7 +90,7 @@ describe("prepareApiConversationMessage", () => { getThoughtSignature: () => "signature-1", getReasoningDetails: () => [{ type: "reasoning", text: "detail" }], } as any, - apiConfiguration: { apiProvider: "openrouter", openRouterModelId: "openai/gpt-4" } as any, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4" } as any, apiConversationHistory: [], }) as any @@ -107,7 +108,7 @@ describe("prepareApiConversationMessage", () => { content: [{ type: "tool_result", tool_use_id: "wrong-id", content: "done" }], }, api: {} as any, - apiConfiguration: { apiProvider: "openrouter", openRouterModelId: "openai/gpt-4" } as any, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4" } as any, apiConversationHistory: [ { role: "assistant", @@ -130,7 +131,7 @@ describe("prepareApiConversationMessage", () => { ], }, api: {} as any, - apiConfiguration: { apiProvider: "openrouter", openRouterModelId: "openai/gpt-4" } as any, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4" } as any, apiConversationHistory: [{ role: "user", content: "previous user message" } as any], }) as any diff --git a/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts b/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts index 285f22189e..80c5163c8e 100644 --- a/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts +++ b/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts @@ -10,6 +10,7 @@ import { TelemetryService } from "@roo-code/telemetry" import { Task } from "../Task" import { ClineProvider } from "../../webview/ClineProvider" import { ContextProxy } from "../../config/ContextProxy" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Mock delay before any imports that might use it vi.mock("delay", () => ({ @@ -213,7 +214,7 @@ describe("flushPendingToolResultsToHistory", () => { ) as any mockApiConfig = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", apiKey: "test-api-key", } diff --git a/src/core/task/__tests__/grace-retry-errors.spec.ts b/src/core/task/__tests__/grace-retry-errors.spec.ts index 45c86d92ec..9584559c8f 100644 --- a/src/core/task/__tests__/grace-retry-errors.spec.ts +++ b/src/core/task/__tests__/grace-retry-errors.spec.ts @@ -10,6 +10,7 @@ import { TelemetryService } from "@roo-code/telemetry" import { Task } from "../Task" import { ClineProvider } from "../../webview/ClineProvider" import { ContextProxy } from "../../config/ContextProxy" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Mock @roo-code/core vi.mock("@roo-code/core", () => ({ @@ -201,7 +202,7 @@ describe("Grace Retry Error Handling", () => { ) as any mockApiConfig = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", apiKey: "test-api-key", } diff --git a/src/core/task/__tests__/grounding-sources.test.ts b/src/core/task/__tests__/grounding-sources.test.ts index dcb1408baf..d392c90437 100644 --- a/src/core/task/__tests__/grounding-sources.test.ts +++ b/src/core/task/__tests__/grounding-sources.test.ts @@ -155,6 +155,7 @@ vi.mock("../../../utils/fs", () => ({ // Import Task AFTER all vi.mock() calls - Vitest hoists mocks so this works import { Task } from "../Task" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" describe("Task grounding sources handling", () => { let mockProvider: Partial @@ -179,7 +180,7 @@ describe("Task grounding sources handling", () => { } mockApiConfiguration = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, geminiApiKey: "test-key", } as ProviderSettings }) diff --git a/src/core/task/__tests__/reasoning-preservation.test.ts b/src/core/task/__tests__/reasoning-preservation.test.ts index cd4c3958a3..f34cea1bdc 100644 --- a/src/core/task/__tests__/reasoning-preservation.test.ts +++ b/src/core/task/__tests__/reasoning-preservation.test.ts @@ -155,6 +155,7 @@ vi.mock("../../../utils/fs", () => ({ // Import Task AFTER all vi.mock() calls - Vitest hoists mocks so this works import { Task } from "../Task" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" describe("Task reasoning preservation", () => { let mockProvider: Partial @@ -179,7 +180,7 @@ describe("Task reasoning preservation", () => { } mockApiConfiguration = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "test-key", } as ProviderSettings }) diff --git a/src/core/tools/GenerateImageTool.ts b/src/core/tools/GenerateImageTool.ts index b036a71977..99a0a8f5b1 100644 --- a/src/core/tools/GenerateImageTool.ts +++ b/src/core/tools/GenerateImageTool.ts @@ -6,6 +6,7 @@ import { IMAGE_GENERATION_MODEL_IDS, IMAGE_GENERATION_MODELS, getImageGenerationProvider, + providerIdentifiers, } from "@roo-code/types" import { Task } from "../task/Task" import { formatResponse } from "../prompts/responses" @@ -155,7 +156,7 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { // Validate API key for OpenRouter const openRouterApiKey = state?.openRouterImageApiKey - if (imageProvider === "openrouter" && !openRouterApiKey) { + if (imageProvider === providerIdentifiers.openrouter && !openRouterApiKey) { const errorMessage = t("tools:generateImage.openRouterApiKeyRequired") await task.say("error", errorMessage) pushToolResult(formatResponse.toolError(errorMessage)) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 6f70a19946..021a2fba91 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2013,7 +2013,7 @@ export class ClineProvider const newConfiguration: ProviderSettings = { ...apiConfiguration, - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterApiKey: apiKey, openRouterModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId, } @@ -2059,7 +2059,7 @@ export class ClineProvider if (zooProfiles.length === 0) { // No existing zoo-gateway profile — create the canonical default. const newConfiguration: ProviderSettings = { - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, zooSessionToken: token, zooGatewayModelId: apiConfiguration.zooGatewayModelId, zooGatewayBaseUrl: derivedGatewayBaseUrl, @@ -2107,7 +2107,7 @@ export class ClineProvider const newConfiguration: ProviderSettings = { ...apiConfiguration, - apiProvider: "requesty", + apiProvider: providerIdentifiers.requesty, requestyApiKey: code, requestyModelId: apiConfiguration?.requestyModelId || requestyDefaultModelId, } diff --git a/src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts b/src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts index ec64a1adb0..99d254cb9a 100644 --- a/src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts @@ -9,6 +9,7 @@ import { ContextProxy } from "../../config/ContextProxy" import type { Mode } from "../../../shared/modes" import { Task, TaskOptions } from "../../task/Task" import { ClineProvider } from "../ClineProvider" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Mock setup vi.mock("fs/promises", () => ({ @@ -118,7 +119,10 @@ vi.mock("../../task/Task", () => ({ } // Define apiConfiguration as a property so tests can read it Object.defineProperty(mockTask, "apiConfiguration", { - value: options?.apiConfiguration || { apiProvider: "openrouter", openRouterModelId: "openai/gpt-4" }, + value: options?.apiConfiguration || { + apiProvider: providerIdentifiers.openrouter, + openRouterModelId: "openai/gpt-4", + }, writable: true, configurable: true, }) @@ -231,23 +235,26 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { // Mock providerSettingsManager ;(provider as any).providerSettingsManager = { saveConfig: vi.fn().mockResolvedValue("test-id"), - listConfig: vi - .fn() - .mockResolvedValue([ - { name: "test-config", id: "test-id", apiProvider: "openrouter", modelId: "openai/gpt-4" }, - ]), + listConfig: vi.fn().mockResolvedValue([ + { + name: "test-config", + id: "test-id", + apiProvider: providerIdentifiers.openrouter, + modelId: "openai/gpt-4", + }, + ]), setModeConfig: vi.fn(), getModeConfigId: vi.fn().mockResolvedValue(undefined), activateProfile: vi.fn().mockResolvedValue({ name: "test-config", id: "test-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }), getProfile: vi.fn().mockResolvedValue({ name: "test-config", id: "test-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }), } @@ -267,7 +274,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { defaultTaskOptions = { provider, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }, } @@ -281,7 +288,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { const mockTask = new Task({ ...defaultTaskOptions, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }, }) @@ -298,7 +305,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { await provider.upsertProviderProfile( "test-config", { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", // Other settings that might change rateLimitSeconds: 5, @@ -310,7 +317,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { // Verify updateApiConfiguration was called because we force rebuild on explicit save/switch expect(mockTask.updateApiConfiguration).toHaveBeenCalledWith( expect.objectContaining({ - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", rateLimitSeconds: 5, modelTemperature: 0.7, @@ -326,7 +333,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { const mockTask = new Task({ ...defaultTaskOptions, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }, }) @@ -343,7 +350,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { await provider.upsertProviderProfile( "test-config", { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", }, true, @@ -352,7 +359,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { // Verify updateApiConfiguration was called since provider changed expect(mockTask.updateApiConfiguration).toHaveBeenCalledWith( expect.objectContaining({ - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", }), ) @@ -362,7 +369,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { const mockTask = new Task({ ...defaultTaskOptions, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }, }) @@ -379,7 +386,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { await provider.upsertProviderProfile( "test-config", { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "anthropic/claude-3-5-sonnet-20241022", }, true, @@ -388,7 +395,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { // Verify updateApiConfiguration was called since model changed expect(mockTask.updateApiConfiguration).toHaveBeenCalledWith( expect.objectContaining({ - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "anthropic/claude-3-5-sonnet-20241022", }), ) @@ -401,7 +408,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { await provider.upsertProviderProfile( "test-config", { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }, true, @@ -428,7 +435,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { return { name: "first-profile", id: "first-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", } }) @@ -437,7 +444,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { return { name: "second-profile", id: "second-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4.1-mini", } }) @@ -465,7 +472,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { .mockResolvedValueOnce({ name: "second-profile", id: "second-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4.1-mini", }) @@ -489,14 +496,14 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { return { name: "first-profile", id: "first-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", } }) .mockResolvedValueOnce({ name: "second-profile", id: "second-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4.1-mini", }) @@ -535,7 +542,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { return { name: "first-profile", id: "first-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", } }) @@ -564,7 +571,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { const mockTask = new Task({ ...defaultTaskOptions, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }, }) @@ -572,17 +579,17 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { provider["providerSettingsManager"].getModeConfigId = vi.fn().mockResolvedValue("ask-id") provider["providerSettingsManager"].listConfig = vi .fn() - .mockResolvedValue([{ name: "ask-profile", id: "ask-id", apiProvider: "openrouter" }]) + .mockResolvedValue([{ name: "ask-profile", id: "ask-id", apiProvider: providerIdentifiers.openrouter }]) provider["providerSettingsManager"].getProfile = vi.fn().mockResolvedValue({ name: "ask-profile", id: "ask-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4.1-mini", }) provider["providerSettingsManager"].activateProfile = vi.fn().mockResolvedValue({ name: "ask-profile", id: "ask-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4.1-mini", }) const emitSpy = vi.spyOn(provider, "emit") @@ -609,7 +616,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { const mockTask = new Task({ ...defaultTaskOptions, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", modelTemperature: 0.3, }, @@ -627,7 +634,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { ;(provider as any).providerSettingsManager.activateProfile = vi.fn().mockResolvedValue({ name: "test-config", id: "test-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", modelTemperature: 0.9, rateLimitSeconds: 7, @@ -638,7 +645,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { // Verify updateApiConfiguration was called due to forced rebuild on explicit switch expect(mockTask.updateApiConfiguration).toHaveBeenCalledWith( expect.objectContaining({ - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }), ) @@ -652,7 +659,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { const mockTask = new Task({ ...defaultTaskOptions, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }, }) @@ -669,7 +676,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { ;(provider as any).providerSettingsManager.activateProfile = vi.fn().mockResolvedValue({ name: "anthropic-config", id: "anthropic-id", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", }) @@ -678,7 +685,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { // Verify updateApiConfiguration was called expect(mockTask.updateApiConfiguration).toHaveBeenCalledWith( expect.objectContaining({ - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", }), ) @@ -691,7 +698,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { const mockTask = new Task({ ...defaultTaskOptions, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }, }) @@ -708,7 +715,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { ;(provider as any).providerSettingsManager.activateProfile = vi.fn().mockResolvedValue({ name: "test-config", id: "test-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "anthropic/claude-3-5-sonnet-20241022", }) @@ -717,7 +724,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { // Verify updateApiConfiguration was called expect(mockTask.updateApiConfiguration).toHaveBeenCalledWith( expect.objectContaining({ - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "anthropic/claude-3-5-sonnet-20241022", }), ) @@ -732,7 +739,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { const mockTask = new Task({ ...defaultTaskOptions, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }, }) @@ -749,7 +756,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { ;(provider as any).providerSettingsManager.activateProfile = vi.fn().mockResolvedValue({ name: "anthropic-config", id: "anthropic-id", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022", }) await provider.activateProviderProfile({ name: "anthropic-config" }) @@ -763,7 +770,7 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { ;(provider as any).providerSettingsManager.activateProfile = vi.fn().mockResolvedValue({ name: "test-config", id: "test-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4", }) await provider.activateProviderProfile({ name: "test-config" }) @@ -777,18 +784,22 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { describe("getModelId helper", () => { test("correctly extracts model ID from different provider configurations", () => { - expect(getModelId({ apiProvider: "openrouter", openRouterModelId: "openai/gpt-4" })).toBe("openai/gpt-4") - expect(getModelId({ apiProvider: "anthropic", apiModelId: "claude-3-5-sonnet-20241022" })).toBe( - "claude-3-5-sonnet-20241022", + expect(getModelId({ apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4" })).toBe( + "openai/gpt-4", + ) + expect( + getModelId({ apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-5-sonnet-20241022" }), + ).toBe("claude-3-5-sonnet-20241022") + expect(getModelId({ apiProvider: providerIdentifiers.openai, openAiModelId: "gpt-4-turbo" })).toBe( + "gpt-4-turbo", ) - expect(getModelId({ apiProvider: "openai", openAiModelId: "gpt-4-turbo" })).toBe("gpt-4-turbo") - expect(getModelId({ apiProvider: "bedrock", apiModelId: "anthropic.claude-v2" })).toBe( + expect(getModelId({ apiProvider: providerIdentifiers.bedrock, apiModelId: "anthropic.claude-v2" })).toBe( "anthropic.claude-v2", ) }) test("returns undefined when no model ID is present", () => { - expect(getModelId({ apiProvider: "anthropic" })).toBeUndefined() + expect(getModelId({ apiProvider: providerIdentifiers.anthropic })).toBeUndefined() expect(getModelId({})).toBeUndefined() }) }) diff --git a/src/core/webview/__tests__/ClineProvider.flicker-free-cancel.spec.ts b/src/core/webview/__tests__/ClineProvider.flicker-free-cancel.spec.ts index 3513bd3bd5..e0ece4f9f7 100644 --- a/src/core/webview/__tests__/ClineProvider.flicker-free-cancel.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.flicker-free-cancel.spec.ts @@ -6,6 +6,7 @@ import { Task } from "../../task/Task" import { TaskRegistry } from "../../task/TaskRegistry" import { ContextProxy } from "../../config/ContextProxy" import type { ProviderSettings, HistoryItem } from "@roo-code/types" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" type MockTask = Partial & Pick & { @@ -284,7 +285,7 @@ describe("ClineProvider flicker-free cancel", () => { let consoleErrorSpy: ReturnType const mockApiConfig: ProviderSettings = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "test-key", } as ProviderSettings diff --git a/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts b/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts index 07e6b82a64..f42eb401f2 100644 --- a/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts @@ -4,6 +4,7 @@ import * as vscode from "vscode" import { TelemetryService } from "@roo-code/telemetry" import { ClineProvider } from "../ClineProvider" import { ContextProxy } from "../../config/ContextProxy" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" vi.mock("vscode", () => ({ ExtensionContext: vi.fn(), @@ -344,11 +345,13 @@ describe("ClineProvider - Lock API Config Across Modes", () => { const getModeConfigIdSpy = vi .spyOn(provider.providerSettingsManager, "getModeConfigId") .mockResolvedValue("architect-profile-id") - const listConfigSpy = vi - .spyOn(provider.providerSettingsManager, "listConfig") - .mockResolvedValue([ - { name: "architect-profile", id: "architect-profile-id", apiProvider: "anthropic" }, - ]) + const listConfigSpy = vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ + { + name: "architect-profile", + id: "architect-profile-id", + apiProvider: providerIdentifiers.anthropic, + }, + ]) const activateProviderProfileSpy = vi .spyOn(provider, "activateProviderProfile") .mockResolvedValue(undefined) @@ -367,16 +370,16 @@ describe("ClineProvider - Lock API Config Across Modes", () => { .spyOn(provider.providerSettingsManager, "getModeConfigId") .mockResolvedValue("architect-profile-id") vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "architect-profile", id: "architect-profile-id", apiProvider: "anthropic" }, + { name: "architect-profile", id: "architect-profile-id", apiProvider: providerIdentifiers.anthropic }, ]) vi.spyOn(provider.providerSettingsManager, "getProfile").mockResolvedValue({ name: "architect-profile", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, }) const activateProfileSpy = vi.spyOn(provider.providerSettingsManager, "activateProfile").mockResolvedValue({ name: "architect-profile", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, }) await provider.handleModeSwitch("architect") diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index e336ac8fac..fec6cef265 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -537,7 +537,7 @@ describe("ClineProvider", () => { defaultTaskOptions = { provider, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, }, } @@ -700,7 +700,7 @@ describe("ClineProvider", () => { taskHistory: [], shouldShowAnnouncement: false, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, }, customInstructions: undefined, alwaysAllowReadOnly: false, @@ -709,7 +709,7 @@ describe("ClineProvider", () => { codebaseIndexConfig: { codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "", }, @@ -1231,7 +1231,7 @@ describe("ClineProvider", () => { test("getState and getStateToPostToWebview return the complete NanoGPT configuration", async () => { await provider.resolveWebviewView(mockWebviewView) await provider.contextProxy.setProviderSettings({ - apiProvider: "nanogpt", + apiProvider: providerIdentifiers.nanogpt, nanoGptApiKey: "nanogpt-secret", nanoGptModelId: "openai/model", nanoGptRoutingPreference: "latency", @@ -1240,7 +1240,7 @@ describe("ClineProvider", () => { const state = await provider.getState() const postedState = await provider.getStateToPostToWebview() const expectedConfiguration = { - apiProvider: "nanogpt", + apiProvider: providerIdentifiers.nanogpt, nanoGptApiKey: "nanogpt-secret", nanoGptModelId: "openai/model", nanoGptRoutingPreference: "latency", @@ -1478,7 +1478,11 @@ describe("ClineProvider", () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] - const profile: ProviderSettingsEntry = { name: "test-config", id: "test-id", apiProvider: "anthropic" } + const profile: ProviderSettingsEntry = { + name: "test-config", + id: "test-id", + apiProvider: providerIdentifiers.anthropic, + } ;(provider as any).providerSettingsManager = { getModeConfigId: vi.fn().mockResolvedValue("test-id"), @@ -1505,7 +1509,9 @@ describe("ClineProvider", () => { getModeConfigId: vi.fn().mockResolvedValue(undefined), listConfig: vi .fn() - .mockResolvedValue([{ name: "current-config", id: "current-id", apiProvider: "anthropic" }]), + .mockResolvedValue([ + { name: "current-config", id: "current-id", apiProvider: providerIdentifiers.anthropic }, + ]), setModeConfig: vi.fn(), } as any @@ -1522,7 +1528,11 @@ describe("ClineProvider", () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] - const profile: ProviderSettingsEntry = { apiProvider: "anthropic", id: "new-id", name: "new-config" } + const profile: ProviderSettingsEntry = { + apiProvider: providerIdentifiers.anthropic, + id: "new-id", + name: "new-config", + } ;(provider as any).providerSettingsManager = { activateProfile: vi.fn().mockResolvedValue(profile), @@ -1548,7 +1558,7 @@ describe("ClineProvider", () => { const profile: ProviderSettingsEntry = { name: "config-by-id", id: "config-id-123", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, } ;(provider as any).providerSettingsManager = { @@ -1722,7 +1732,11 @@ describe("ClineProvider", () => { const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] ;(provider as any).providerSettingsManager = { - listConfig: vi.fn().mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), + listConfig: vi + .fn() + .mockResolvedValue([ + { name: "test-config", id: "test-id", apiProvider: providerIdentifiers.anthropic }, + ]), saveConfig: vi.fn().mockResolvedValue("test-id"), setModeConfig: vi.fn(), } as any @@ -1731,7 +1745,7 @@ describe("ClineProvider", () => { await messageHandler({ type: "upsertApiConfiguration", text: "test-config", - apiConfiguration: { apiProvider: "anthropic" }, + apiConfiguration: { apiProvider: providerIdentifiers.anthropic }, }) // Should save config as default for current mode @@ -1940,7 +1954,7 @@ describe("ClineProvider", () => { // Test with mcpEnabled: true vi.spyOn(provider, "getState").mockResolvedValueOnce({ apiConfiguration: { - apiProvider: "openrouter" as const, + apiProvider: providerIdentifiers.openrouter, }, mcpEnabled: true, mode: "code" as const, @@ -1964,7 +1978,7 @@ describe("ClineProvider", () => { // Test with mcpEnabled: false vi.spyOn(provider, "getState").mockResolvedValueOnce({ apiConfiguration: { - apiProvider: "openrouter" as const, + apiProvider: providerIdentifiers.openrouter, }, mcpEnabled: false, mode: "code" as const, @@ -2000,7 +2014,7 @@ describe("ClineProvider", () => { // Mock getState to return custom instructions for code mode vi.spyOn(provider, "getState").mockResolvedValue({ apiConfiguration: { - apiProvider: "openrouter" as const, + apiProvider: providerIdentifiers.openrouter, }, customModePrompts: { code: { customInstructions: "Code mode specific instructions" }, @@ -2029,7 +2043,7 @@ describe("ClineProvider", () => { // Mock getState to return architect mode instructions vi.spyOn(provider, "getState").mockResolvedValue({ apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, }, customModePrompts: { architect: { customInstructions: "Architect mode instructions" }, @@ -2064,7 +2078,7 @@ describe("ClineProvider", () => { const profile: ProviderSettingsEntry = { name: "saved-config", id: "saved-config-id", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, } ;(provider as any).providerSettingsManager = { @@ -2095,7 +2109,9 @@ describe("ClineProvider", () => { getModeConfigId: vi.fn().mockResolvedValue(undefined), listConfig: vi .fn() - .mockResolvedValue([{ name: "current-config", id: "current-id", apiProvider: "anthropic" }]), + .mockResolvedValue([ + { name: "current-config", id: "current-id", apiProvider: providerIdentifiers.anthropic }, + ]), setModeConfig: vi.fn(), } as any @@ -2219,10 +2235,14 @@ describe("ClineProvider", () => { getModeConfigId: vi.fn().mockResolvedValue("config-id"), listConfig: vi .fn() - .mockResolvedValue([{ name: "test-config", id: "config-id", apiProvider: "anthropic" }]), - activateProfile: vi - .fn() - .mockResolvedValue({ name: "test-config", id: "config-id", apiProvider: "anthropic" }), + .mockResolvedValue([ + { name: "test-config", id: "config-id", apiProvider: providerIdentifiers.anthropic }, + ]), + activateProfile: vi.fn().mockResolvedValue({ + name: "test-config", + id: "config-id", + apiProvider: providerIdentifiers.anthropic, + }), } // Spy on log method to verify no warning was logged @@ -2354,7 +2374,9 @@ describe("ClineProvider", () => { getModeConfigId: vi.fn().mockResolvedValue("config-id"), listConfig: vi .fn() - .mockResolvedValue([{ name: "test-config", id: "config-id", apiProvider: "anthropic" }]), + .mockResolvedValue([ + { name: "test-config", id: "config-id", apiProvider: providerIdentifiers.anthropic }, + ]), activateProfile: vi.fn().mockRejectedValue(new Error("Failed to load config")), } @@ -2454,7 +2476,9 @@ describe("ClineProvider", () => { setModeConfig: vi.fn().mockRejectedValue(new Error("Failed to update mode config")), listConfig: vi .fn() - .mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), + .mockResolvedValue([ + { name: "test-config", id: "test-id", apiProvider: providerIdentifiers.anthropic }, + ]), } as any // Mock getState to provide necessary data @@ -2467,7 +2491,7 @@ describe("ClineProvider", () => { await messageHandler({ type: "upsertApiConfiguration", text: "test-config", - apiConfiguration: { apiProvider: "anthropic", apiKey: "test-key" }, + apiConfiguration: { apiProvider: providerIdentifiers.anthropic, apiKey: "test-key" }, }) // Verify error was logged and user was notified @@ -2486,11 +2510,13 @@ describe("ClineProvider", () => { saveConfig: vi.fn().mockResolvedValue(undefined), listConfig: vi .fn() - .mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), + .mockResolvedValue([ + { name: "test-config", id: "test-id", apiProvider: providerIdentifiers.anthropic }, + ]), } as any const testApiConfig = { - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, apiKey: "test-key", } @@ -2506,7 +2532,7 @@ describe("ClineProvider", () => { // Verify state updates expect(mockContext.globalState.update).toHaveBeenCalledWith("listApiConfigMeta", [ - { name: "test-config", id: "test-id", apiProvider: "anthropic" }, + { name: "test-config", id: "test-id", apiProvider: providerIdentifiers.anthropic }, ]) expect(mockContext.globalState.update).toHaveBeenCalledWith("currentApiConfigName", "test-config") @@ -2529,7 +2555,9 @@ describe("ClineProvider", () => { saveConfig: vi.fn().mockResolvedValue(undefined), listConfig: vi .fn() - .mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), + .mockResolvedValue([ + { name: "test-config", id: "test-id", apiProvider: providerIdentifiers.anthropic }, + ]), } as any // Setup Task instance with auto-mock from the top of the file @@ -2537,7 +2565,7 @@ describe("ClineProvider", () => { await provider.addClineToStack(mockCline) const testApiConfig = { - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, apiKey: "test-key", } @@ -2556,7 +2584,7 @@ describe("ClineProvider", () => { // Verify state was still updated expect(mockContext.globalState.update).toHaveBeenCalledWith("listApiConfigMeta", [ - { name: "test-config", id: "test-id", apiProvider: "anthropic" }, + { name: "test-config", id: "test-id", apiProvider: providerIdentifiers.anthropic }, ]) expect(mockContext.globalState.update).toHaveBeenCalledWith("currentApiConfigName", "test-config") }) @@ -2570,11 +2598,13 @@ describe("ClineProvider", () => { saveConfig: vi.fn().mockResolvedValue(undefined), listConfig: vi .fn() - .mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), + .mockResolvedValue([ + { name: "test-config", id: "test-id", apiProvider: providerIdentifiers.anthropic }, + ]), } as any const testApiConfig = { - apiProvider: "anthropic" as const, + apiProvider: providerIdentifiers.anthropic, apiKey: "test-key", } @@ -2590,10 +2620,10 @@ describe("ClineProvider", () => { // Verify state updates expect(mockContext.globalState.update).toHaveBeenCalledWith("listApiConfigMeta", [ - { name: "test-config", id: "test-id", apiProvider: "anthropic" }, + { name: "test-config", id: "test-id", apiProvider: providerIdentifiers.anthropic }, ]) expect(updateGlobalStateSpy).toHaveBeenCalledWith("listApiConfigMeta", [ - { name: "test-config", id: "test-id", apiProvider: "anthropic" }, + { name: "test-config", id: "test-id", apiProvider: providerIdentifiers.anthropic }, ]) }) }) @@ -3185,7 +3215,7 @@ describe("getTelemetryProperties", () => { defaultTaskOptions = { provider, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, }, } @@ -3426,21 +3456,21 @@ describe("ClineProvider - Router Models", () => { await messageHandler({ type: "requestRouterModels" }) // Verify getModels was called for each provider with correct options - expect(getModels).toHaveBeenCalledWith({ provider: "openrouter" }) - expect(getModels).toHaveBeenCalledWith({ provider: "requesty", apiKey: "requesty-key" }) - expect(getModels).toHaveBeenCalledWith({ provider: "unbound" }) - expect(getModels).toHaveBeenCalledWith({ provider: "vercel-ai-gateway" }) + expect(getModels).toHaveBeenCalledWith({ provider: providerIdentifiers.openrouter }) + expect(getModels).toHaveBeenCalledWith({ provider: providerIdentifiers.requesty, apiKey: "requesty-key" }) + expect(getModels).toHaveBeenCalledWith({ provider: providerIdentifiers.unbound }) + expect(getModels).toHaveBeenCalledWith({ provider: providerIdentifiers.vercelAiGateway }) expect(getModels).toHaveBeenCalledWith({ - provider: "litellm", + provider: providerIdentifiers.litellm, apiKey: "litellm-key", baseUrl: "http://localhost:4000", }) // Opencode Go's /models endpoint is public, so it is fetched like the other no-auth routers. - expect(getModels).toHaveBeenCalledWith(expect.objectContaining({ provider: "opencode-go" })) + expect(getModels).toHaveBeenCalledWith(expect.objectContaining({ provider: providerIdentifiers.opencodeGo })) // Kenari's /models endpoint is public, so it is fetched like the other no-auth routers. - expect(getModels).toHaveBeenCalledWith(expect.objectContaining({ provider: "kenari" })) + expect(getModels).toHaveBeenCalledWith(expect.objectContaining({ provider: providerIdentifiers.kenari })) // NanoGPT's detailed catalog is public and may be scoped by an optional key. - expect(getModels).toHaveBeenCalledWith({ provider: "nanogpt", apiKey: undefined }) + expect(getModels).toHaveBeenCalledWith({ provider: providerIdentifiers.nanogpt, apiKey: undefined }) // Verify response was sent expect(mockPostMessage).toHaveBeenCalledWith({ @@ -3526,14 +3556,14 @@ describe("ClineProvider - Router Models", () => { type: "singleRouterModelFetchResponse", success: false, error: "Requesty API error", - values: { provider: "requesty" }, + values: { provider: providerIdentifiers.requesty }, }) expect(mockPostMessage).toHaveBeenCalledWith({ type: "singleRouterModelFetchResponse", success: false, error: "LiteLLM connection failed", - values: { provider: "litellm" }, + values: { provider: providerIdentifiers.litellm }, }) }) @@ -3566,7 +3596,7 @@ describe("ClineProvider - Router Models", () => { // Verify LiteLLM was called with values from message expect(getModels).toHaveBeenCalledWith({ - provider: "litellm", + provider: providerIdentifiers.litellm, apiKey: "message-litellm-key", baseUrl: "http://message-url:4000", }) @@ -3595,7 +3625,7 @@ describe("ClineProvider - Router Models", () => { // Verify LiteLLM was NOT called expect(getModels).not.toHaveBeenCalledWith( expect.objectContaining({ - provider: "litellm", + provider: providerIdentifiers.litellm, }), ) @@ -3645,7 +3675,7 @@ describe("ClineProvider - Router Models", () => { }) expect(getModels).toHaveBeenCalledWith({ - provider: "lmstudio", + provider: providerIdentifiers.lmstudio, baseUrl: "http://localhost:1234", }) }) @@ -3743,7 +3773,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { defaultTaskOptions = { provider, apiConfiguration: { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, }, } @@ -4680,7 +4710,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { apiConfiguration: { zooGatewayModelId: "anthropic/claude-sonnet-4" }, } as any) vi.spyOn(provider.contextProxy, "getProviderSettings").mockReturnValue({ - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, } as any) vi.spyOn(provider.contextProxy, "getValues").mockReturnValue({ currentApiConfigName: "Anthropic", @@ -4698,7 +4728,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { expect(upsertSpy).toHaveBeenCalledWith( "Zoo Gateway", expect.objectContaining({ - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, zooSessionToken: "zoo_ext_token", zooGatewayBaseUrl: "https://www.zoocode.dev/api/gateway/v1", }), @@ -4711,7 +4741,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { apiConfiguration: { zooGatewayModelId: "anthropic/claude-sonnet-4" }, } as any) vi.spyOn(provider.contextProxy, "getProviderSettings").mockReturnValue({ - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, } as any) vi.spyOn(provider.contextProxy, "getValues").mockReturnValue({ currentApiConfigName: "Zoo Gateway", @@ -4721,18 +4751,18 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { vi.spyOn(provider, "postStateToWebview").mockResolvedValue(undefined) ;(provider as any).providerSettingsManager = { listConfig: vi.fn().mockResolvedValue([ - { name: "Zoo Gateway", apiProvider: "zoo-gateway" }, - { name: "Backup Zoo", apiProvider: "zoo-gateway" }, + { name: "Zoo Gateway", apiProvider: providerIdentifiers.zooGateway }, + { name: "Backup Zoo", apiProvider: providerIdentifiers.zooGateway }, ]), getProfile: vi .fn() .mockResolvedValueOnce({ - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, zooSessionToken: "old-token", zooGatewayBaseUrl: "https://old.example/api/gateway/v1", }) .mockResolvedValueOnce({ - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, zooSessionToken: "old-token", }), saveConfig, @@ -4794,7 +4824,9 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { const postMessageSpy = vi.spyOn(provider, "postMessageToWebview").mockResolvedValue(undefined) ;(provider as any).providerSettingsManager = { - listConfig: vi.fn().mockResolvedValue([{ name: "Zoo Gateway", apiProvider: "zoo-gateway" }]), + listConfig: vi + .fn() + .mockResolvedValue([{ name: "Zoo Gateway", apiProvider: providerIdentifiers.zooGateway }]), getProfile: vi.fn().mockResolvedValue({ zooSessionToken: "current-token", zooGatewayBaseUrl: "https://www.zoocode.dev/api/gateway/v1", @@ -4813,7 +4845,9 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { const handleSpy = vi.spyOn(provider, "handleZooCodeCallback").mockResolvedValue(undefined) ;(provider as any).providerSettingsManager = { - listConfig: vi.fn().mockResolvedValue([{ name: "Zoo Gateway", apiProvider: "zoo-gateway" }]), + listConfig: vi + .fn() + .mockResolvedValue([{ name: "Zoo Gateway", apiProvider: providerIdentifiers.zooGateway }]), getProfile: vi.fn().mockResolvedValue({ zooSessionToken: "stale-token", zooGatewayBaseUrl: "https://www.zoocode.dev/api/gateway/v1", @@ -4831,7 +4865,9 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { const handleSpy = vi.spyOn(provider, "handleZooCodeCallback").mockResolvedValue(undefined) ;(provider as any).providerSettingsManager = { - listConfig: vi.fn().mockResolvedValue([{ name: "Zoo Gateway", apiProvider: "zoo-gateway" }]), + listConfig: vi + .fn() + .mockResolvedValue([{ name: "Zoo Gateway", apiProvider: providerIdentifiers.zooGateway }]), getProfile: vi.fn().mockResolvedValue({ zooSessionToken: "current-token", zooGatewayBaseUrl: "https://staging.zoocode.dev/api/gateway/v1", diff --git a/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts b/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts index e6d8c9325f..fedfa13030 100644 --- a/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts @@ -6,6 +6,7 @@ import { ClineProvider } from "../ClineProvider" import { ContextProxy } from "../../config/ContextProxy" import { Task } from "../../task/Task" import type { HistoryItem, ProviderName } from "@roo-code/types" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" vi.mock("vscode", () => ({ ExtensionContext: vi.fn(), @@ -318,7 +319,7 @@ describe("ClineProvider - Sticky Mode", () => { // Create a mock task const mockTask = new Task({ provider, - apiConfiguration: { apiProvider: "openrouter" }, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter }, }) // Get the actual taskId from the mock @@ -411,7 +412,7 @@ describe("ClineProvider - Sticky Mode", () => { // Create a mock task with history const mockTask = new Task({ provider, - apiConfiguration: { apiProvider: "openrouter" }, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter }, }) // Get the actual taskId from the mock @@ -534,7 +535,7 @@ describe("ClineProvider - Sticky Mode", () => { // Create a mock task const mockTask = new Task({ provider, - apiConfiguration: { apiProvider: "openrouter" }, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter }, }) // Get the actual taskId from the mock @@ -587,7 +588,7 @@ describe("ClineProvider - Sticky Mode", () => { // Create parent task const parentTask = new Task({ provider, - apiConfiguration: { apiProvider: "openrouter" }, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter }, }) // Get the actual taskId from the mock @@ -636,7 +637,7 @@ describe("ClineProvider - Sticky Mode", () => { // Create a subtask (simulating new_task tool behavior) const subtask = new Task({ provider, - apiConfiguration: { apiProvider: "openrouter" }, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter }, parentTask: parentTask, }) const subtaskId = (subtask as any).taskId || "subtask-id" @@ -672,7 +673,7 @@ describe("ClineProvider - Sticky Mode", () => { // Create a mock task that throws on save const mockTask = new Task({ provider, - apiConfiguration: { apiProvider: "openrouter" }, + apiConfiguration: { apiProvider: providerIdentifiers.openrouter }, }) vi.spyOn(mockTask as any, "saveClineMessages").mockRejectedValue(new Error("Save failed")) @@ -724,8 +725,8 @@ describe("ClineProvider - Sticky Mode", () => { it("should restore API configuration when restoring task from history with mode", async () => { // Setup: Configure different API configs for different modes - const codeApiConfig = { apiProvider: "anthropic" as ProviderName, anthropicApiKey: "code-key" } - const architectApiConfig = { apiProvider: "openai" as ProviderName, openAiApiKey: "architect-key" } + const codeApiConfig = { apiProvider: providerIdentifiers.anthropic, anthropicApiKey: "code-key" } + const architectApiConfig = { apiProvider: providerIdentifiers.openai, openAiApiKey: "architect-key" } // Save API configs await provider.upsertProviderProfile("code-config", codeApiConfig) diff --git a/src/core/webview/__tests__/ClineProvider.sticky-profile.spec.ts b/src/core/webview/__tests__/ClineProvider.sticky-profile.spec.ts index c982cf53c0..7d8493fba3 100644 --- a/src/core/webview/__tests__/ClineProvider.sticky-profile.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.sticky-profile.spec.ts @@ -5,6 +5,7 @@ import { TelemetryService } from "@roo-code/telemetry" import { ClineProvider } from "../ClineProvider" import { ContextProxy } from "../../config/ContextProxy" import type { HistoryItem } from "@roo-code/types" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" vi.mock("vscode", () => ({ ExtensionContext: vi.fn(), @@ -363,12 +364,12 @@ describe("ClineProvider - Sticky Provider Profile", () => { vi.spyOn(provider.providerSettingsManager, "activateProfile").mockResolvedValue({ name: "new-profile", id: "new-profile-id", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, }) // Mock providerSettingsManager.listConfig vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "new-profile", id: "new-profile-id", apiProvider: "anthropic" }, + { name: "new-profile", id: "new-profile-id", apiProvider: providerIdentifiers.anthropic }, ]) // Switch provider profile @@ -428,12 +429,12 @@ describe("ClineProvider - Sticky Provider Profile", () => { vi.spyOn(provider.providerSettingsManager, "activateProfile").mockResolvedValue({ name: "new-profile", id: "new-profile-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, }) // Mock providerSettingsManager.listConfig vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "new-profile", id: "new-profile-id", apiProvider: "openrouter" }, + { name: "new-profile", id: "new-profile-id", apiProvider: providerIdentifiers.openrouter }, ]) // Switch provider profile @@ -471,11 +472,11 @@ describe("ClineProvider - Sticky Provider Profile", () => { vi.spyOn(provider.providerSettingsManager, "activateProfile").mockResolvedValue({ name: "new-profile", id: "new-profile-id", - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, }) vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "new-profile", id: "new-profile-id", apiProvider: "openrouter" }, + { name: "new-profile", id: "new-profile-id", apiProvider: providerIdentifiers.openrouter }, ]) await provider.activateProviderProfile({ name: "new-profile" }) @@ -513,7 +514,7 @@ describe("ClineProvider - Sticky Provider Profile", () => { // Mock providerSettingsManager.listConfig vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "saved-profile", id: "saved-profile-id", apiProvider: "anthropic" }, + { name: "saved-profile", id: "saved-profile-id", apiProvider: providerIdentifiers.anthropic }, ]) // Initialize task with history item @@ -579,7 +580,7 @@ describe("ClineProvider - Sticky Provider Profile", () => { const logSpy = vi.spyOn(provider, "log") vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "saved-profile", id: "saved-profile-id", apiProvider: "anthropic" }, + { name: "saved-profile", id: "saved-profile-id", apiProvider: providerIdentifiers.anthropic }, ]) await provider.createTaskWithHistoryItem(historyItem) @@ -613,7 +614,7 @@ describe("ClineProvider - Sticky Provider Profile", () => { vi.spyOn(provider.providerSettingsManager, "getModeConfigId").mockResolvedValue("mode-config-id") vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "mode-profile", id: "mode-config-id", apiProvider: "anthropic" }, + { name: "mode-profile", id: "mode-config-id", apiProvider: providerIdentifiers.anthropic }, ]) await provider.createTaskWithHistoryItem(historyItem) @@ -683,8 +684,8 @@ describe("ClineProvider - Sticky Provider Profile", () => { // Mock providerSettingsManager methods vi.spyOn(provider.providerSettingsManager, "getModeConfigId").mockResolvedValue("mode-config-id") vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "mode-preferred-profile", id: "mode-config-id", apiProvider: "anthropic" }, - { name: "task-specific-profile", id: "task-profile-id", apiProvider: "openai" }, + { name: "mode-preferred-profile", id: "mode-config-id", apiProvider: providerIdentifiers.anthropic }, + { name: "task-specific-profile", id: "task-profile-id", apiProvider: providerIdentifiers.openai }, ]) // Initialize task with history item @@ -768,12 +769,12 @@ describe("ClineProvider - Sticky Provider Profile", () => { vi.spyOn(provider.providerSettingsManager, "activateProfile").mockResolvedValue({ name: "new-profile", id: "new-profile-id", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, }) // Mock providerSettingsManager.listConfig vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "new-profile", id: "new-profile-id", apiProvider: "anthropic" }, + { name: "new-profile", id: "new-profile-id", apiProvider: providerIdentifiers.anthropic }, ]) // Trigger a profile switch @@ -869,14 +870,14 @@ describe("ClineProvider - Sticky Provider Profile", () => { vi.spyOn(provider.providerSettingsManager, "activateProfile").mockResolvedValue({ name: "profile-c", id: "profile-c-id", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, }) // Mock providerSettingsManager.listConfig vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "profile-a", id: "profile-a-id", apiProvider: "anthropic" }, - { name: "profile-b", id: "profile-b-id", apiProvider: "openai" }, - { name: "profile-c", id: "profile-c-id", apiProvider: "anthropic" }, + { name: "profile-a", id: "profile-a-id", apiProvider: providerIdentifiers.anthropic }, + { name: "profile-b", id: "profile-b-id", apiProvider: providerIdentifiers.openai }, + { name: "profile-c", id: "profile-c-id", apiProvider: providerIdentifiers.anthropic }, ]) // Switch task 1's profile to profile C @@ -928,12 +929,12 @@ describe("ClineProvider - Sticky Provider Profile", () => { vi.spyOn(provider.providerSettingsManager, "activateProfile").mockResolvedValue({ name: "new-profile", id: "new-profile-id", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, }) // Mock providerSettingsManager.listConfig vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "new-profile", id: "new-profile-id", apiProvider: "anthropic" }, + { name: "new-profile", id: "new-profile-id", apiProvider: providerIdentifiers.anthropic }, ]) // Mock log to verify error is logged @@ -996,7 +997,7 @@ describe("ClineProvider - Sticky Provider Profile", () => { // Mock providerSettingsManager.listConfig to return the profile vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValue([ - { name: "failing-profile", id: "failing-profile-id", apiProvider: "anthropic" }, + { name: "failing-profile", id: "failing-profile-id", apiProvider: providerIdentifiers.anthropic }, ]) // Mock activateProviderProfile to throw error diff --git a/src/core/webview/__tests__/messageEnhancer.test.ts b/src/core/webview/__tests__/messageEnhancer.test.ts index 562824bf7c..964c4ebdef 100644 --- a/src/core/webview/__tests__/messageEnhancer.test.ts +++ b/src/core/webview/__tests__/messageEnhancer.test.ts @@ -4,6 +4,7 @@ import { TelemetryService } from "@roo-code/telemetry" import { MessageEnhancer } from "../messageEnhancer" import * as singleCompletionHandlerModule from "../../../utils/single-completion-handler" import { ProviderSettingsManager } from "../../config/ProviderSettingsManager" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Mock dependencies vi.mock("../../../utils/single-completion-handler") @@ -14,7 +15,7 @@ describe("MessageEnhancer", () => { let mockSingleCompletionHandler: ReturnType Promise>> const mockApiConfiguration: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, apiKey: "test-key", apiModelId: "gpt-4", } @@ -32,7 +33,7 @@ describe("MessageEnhancer", () => { mockProviderSettingsManager = { getProfile: vi.fn().mockResolvedValue({ name: "Enhancement Config", - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "enhancement-key", apiModelId: "claude-3", }), @@ -94,7 +95,7 @@ describe("MessageEnhancer", () => { // Verify the enhancement config was used instead of default const expectedConfig = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiKey: "enhancement-key", apiModelId: "claude-3", } diff --git a/src/core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts index 1ba578a894..5a4b3e7be3 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts @@ -1,6 +1,11 @@ import { describe, it, expect, vi, beforeEach } from "vitest" -import { kimiCodeAuthMethodSchema, providerIdentifiers, RouterModelsMessageType } from "@roo-code/types" +import { + kimiCodeAuthMethodSchema, + providerIdentifiers, + retiredProviderIdentifiers, + RouterModelsMessageType, +} from "@roo-code/types" import { webviewMessageHandler } from "../webviewMessageHandler" import type { ClineProvider } from "../ClineProvider" @@ -104,7 +109,7 @@ describe("webviewMessageHandler - requestRouterModels provider filter", () => { type: RouterModelsMessageType.singleRouterModelFetchResponse, success: false, error: "Roo Code Router has been removed. Please select and configure a different provider.", - values: { provider: "roo" }, + values: { provider: retiredProviderIdentifiers.roo }, }) }) diff --git a/src/core/webview/__tests__/webviewMessageHandler.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.spec.ts index bc92522790..3070ca532a 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.spec.ts @@ -197,6 +197,7 @@ vi.mock("../../mentions/resolveImageMentions", () => ({ import { resolveImageMentions } from "../../mentions/resolveImageMentions" import { Terminal } from "../../../integrations/terminal/Terminal" import { TerminalRegistry } from "../../../integrations/terminal/TerminalRegistry" +import { providerIdentifiers, retiredProviderIdentifiers } from "@roo-code/types/provider-identifiers" describe("webviewMessageHandler - requestLmStudioModels", () => { beforeEach(() => { @@ -232,7 +233,10 @@ describe("webviewMessageHandler - requestLmStudioModels", () => { type: "requestLmStudioModels", }) - expect(mockGetModels).toHaveBeenCalledWith({ provider: "lmstudio", baseUrl: "http://localhost:1234" }) + expect(mockGetModels).toHaveBeenCalledWith({ + provider: providerIdentifiers.lmstudio, + baseUrl: "http://localhost:1234", + }) expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ type: "lmStudioModels", @@ -332,7 +336,10 @@ describe("webviewMessageHandler - requestOllamaModels", () => { type: "requestOllamaModels", }) - expect(mockGetModels).toHaveBeenCalledWith({ provider: "ollama", baseUrl: "http://localhost:1234" }) + expect(mockGetModels).toHaveBeenCalledWith({ + provider: providerIdentifiers.ollama, + baseUrl: "http://localhost:1234", + }) expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ type: "ollamaModels", @@ -413,14 +420,14 @@ describe("webviewMessageHandler - requestOllamaModels", () => { // Should use the URL from message values, not the saved state expect(mockFlushModels).toHaveBeenCalledWith( { - provider: "ollama", + provider: providerIdentifiers.ollama, baseUrl: "https://ollama.example.com", apiKey: "secret-key", }, true, ) expect(mockGetModels).toHaveBeenCalledWith({ - provider: "ollama", + provider: providerIdentifiers.ollama, baseUrl: "https://ollama.example.com", apiKey: "secret-key", }) @@ -468,25 +475,27 @@ describe("webviewMessageHandler - requestRouterModels", () => { }) // Verify getModels was called for each provider - expect(mockGetModels).toHaveBeenCalledWith({ provider: "openrouter" }) - expect(mockGetModels).toHaveBeenCalledWith({ provider: "requesty", apiKey: "requesty-key" }) + expect(mockGetModels).toHaveBeenCalledWith({ provider: providerIdentifiers.openrouter }) + expect(mockGetModels).toHaveBeenCalledWith({ provider: providerIdentifiers.requesty, apiKey: "requesty-key" }) expect(mockGetModels).toHaveBeenCalledWith( expect.objectContaining({ - provider: "unbound", + provider: providerIdentifiers.unbound, }), ) - expect(mockGetModels).toHaveBeenCalledWith({ provider: "vercel-ai-gateway" }) + expect(mockGetModels).toHaveBeenCalledWith({ provider: providerIdentifiers.vercelAiGateway }) expect(mockGetModels).toHaveBeenCalledWith({ - provider: "litellm", + provider: providerIdentifiers.litellm, apiKey: "litellm-key", baseUrl: "http://localhost:4000", }) // Opencode Go's /models endpoint is public, so it is fetched like the other no-auth routers. - expect(mockGetModels).toHaveBeenCalledWith(expect.objectContaining({ provider: "opencode-go" })) + expect(mockGetModels).toHaveBeenCalledWith( + expect.objectContaining({ provider: providerIdentifiers.opencodeGo }), + ) // Kenari's /models endpoint is public, so it is fetched like the other no-auth routers. - expect(mockGetModels).toHaveBeenCalledWith(expect.objectContaining({ provider: "kenari" })) + expect(mockGetModels).toHaveBeenCalledWith(expect.objectContaining({ provider: providerIdentifiers.kenari })) // NanoGPT's detailed catalog is public and may optionally be scoped by a key. - expect(mockGetModels).toHaveBeenCalledWith({ provider: "nanogpt", apiKey: undefined }) + expect(mockGetModels).toHaveBeenCalledWith({ provider: providerIdentifiers.nanogpt, apiKey: undefined }) // Verify response was sent expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ @@ -533,7 +542,7 @@ describe("webviewMessageHandler - requestRouterModels", () => { await webviewMessageHandler(mockClineProvider, { type: "requestRouterModels" }) // Must be fetched despite no configured key, forwarding apiKey: undefined. - expect(mockGetModels).toHaveBeenCalledWith({ provider: "opencode-go", apiKey: undefined }) + expect(mockGetModels).toHaveBeenCalledWith({ provider: providerIdentifiers.opencodeGo, apiKey: undefined }) const routerModelsCall = (mockClineProvider.postMessageToWebview as any).mock.calls.find( ([msg]: [{ type: string }]) => msg.type === "routerModels", @@ -557,13 +566,16 @@ describe("webviewMessageHandler - requestRouterModels", () => { await webviewMessageHandler(mockClineProvider, { type: "requestRouterModels", values: { - provider: "opencode-go", + provider: providerIdentifiers.opencodeGo, opencodeGoApiKey: "fresh-key", }, }) - expect(mockFlushModels).toHaveBeenCalledWith({ provider: "opencode-go", apiKey: "fresh-key" }, true) - expect(mockGetModels).toHaveBeenCalledWith({ provider: "opencode-go", apiKey: "fresh-key" }) + expect(mockFlushModels).toHaveBeenCalledWith( + { provider: providerIdentifiers.opencodeGo, apiKey: "fresh-key" }, + true, + ) + expect(mockGetModels).toHaveBeenCalledWith({ provider: providerIdentifiers.opencodeGo, apiKey: "fresh-key" }) expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ type: "routerModels", routerModels: { @@ -571,7 +583,7 @@ describe("webviewMessageHandler - requestRouterModels", () => { "opencode/model": expect.objectContaining({ description: "Opencode model" }), }, }, - values: { provider: "opencode-go" }, + values: { provider: providerIdentifiers.opencodeGo }, }) }) @@ -591,13 +603,16 @@ describe("webviewMessageHandler - requestRouterModels", () => { await webviewMessageHandler(mockClineProvider, { type: "requestRouterModels", values: { - provider: "kenari", + provider: providerIdentifiers.kenari, kenariApiKey: "fresh-kenari-key", }, }) - expect(mockFlushModels).toHaveBeenCalledWith({ provider: "kenari", apiKey: "fresh-kenari-key" }, true) - expect(mockGetModels).toHaveBeenCalledWith({ provider: "kenari", apiKey: "fresh-kenari-key" }) + expect(mockFlushModels).toHaveBeenCalledWith( + { provider: providerIdentifiers.kenari, apiKey: "fresh-kenari-key" }, + true, + ) + expect(mockGetModels).toHaveBeenCalledWith({ provider: providerIdentifiers.kenari, apiKey: "fresh-kenari-key" }) expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ type: "routerModels", routerModels: { @@ -605,7 +620,7 @@ describe("webviewMessageHandler - requestRouterModels", () => { "glm-5-2": expect.objectContaining({ description: "Kenari model" }), }, }, - values: { provider: "kenari" }, + values: { provider: providerIdentifiers.kenari }, }) }) @@ -617,10 +632,10 @@ describe("webviewMessageHandler - requestRouterModels", () => { await webviewMessageHandler(mockClineProvider, { type: "requestRouterModels", - values: { provider: "nanogpt" }, + values: { provider: providerIdentifiers.nanogpt }, }) - expect(mockGetModels).toHaveBeenCalledWith({ provider: "nanogpt", apiKey: undefined }) + expect(mockGetModels).toHaveBeenCalledWith({ provider: providerIdentifiers.nanogpt, apiKey: undefined }) expect(mockFlushModels).not.toHaveBeenCalled() }) @@ -634,11 +649,14 @@ describe("webviewMessageHandler - requestRouterModels", () => { await webviewMessageHandler(mockClineProvider, { type: "requestRouterModels", - values: { provider: "nanogpt", nanoGptApiKey: "unsaved-key" }, + values: { provider: providerIdentifiers.nanogpt, nanoGptApiKey: "unsaved-key" }, }) - expect(mockFlushModels).toHaveBeenCalledWith({ provider: "nanogpt", apiKey: "unsaved-key" }, true) - expect(mockGetModels).toHaveBeenCalledWith({ provider: "nanogpt", apiKey: "unsaved-key" }) + expect(mockFlushModels).toHaveBeenCalledWith( + { provider: providerIdentifiers.nanogpt, apiKey: "unsaved-key" }, + true, + ) + expect(mockGetModels).toHaveBeenCalledWith({ provider: providerIdentifiers.nanogpt, apiKey: "unsaved-key" }) }) it("uses the saved NanoGPT key for manual refresh", async () => { @@ -649,11 +667,14 @@ describe("webviewMessageHandler - requestRouterModels", () => { await webviewMessageHandler(mockClineProvider, { type: "requestRouterModels", - values: { provider: "nanogpt", refresh: true }, + values: { provider: providerIdentifiers.nanogpt, refresh: true }, }) - expect(mockFlushModels).toHaveBeenCalledWith({ provider: "nanogpt", apiKey: "saved-key" }, true) - expect(mockGetModels).toHaveBeenCalledWith({ provider: "nanogpt", apiKey: "saved-key" }) + expect(mockFlushModels).toHaveBeenCalledWith( + { provider: providerIdentifiers.nanogpt, apiKey: "saved-key" }, + true, + ) + expect(mockGetModels).toHaveBeenCalledWith({ provider: providerIdentifiers.nanogpt, apiKey: "saved-key" }) }) it("handles LiteLLM models with values from message when config is missing", async () => { @@ -686,7 +707,7 @@ describe("webviewMessageHandler - requestRouterModels", () => { // Verify LiteLLM was called with values from message expect(mockGetModels).toHaveBeenCalledWith({ - provider: "litellm", + provider: providerIdentifiers.litellm, apiKey: "message-litellm-key", baseUrl: "http://message-url:4000", }) @@ -720,7 +741,7 @@ describe("webviewMessageHandler - requestRouterModels", () => { // Verify LiteLLM was NOT called expect(mockGetModels).not.toHaveBeenCalledWith( expect.objectContaining({ - provider: "litellm", + provider: providerIdentifiers.litellm, }), ) @@ -779,14 +800,14 @@ describe("webviewMessageHandler - requestRouterModels", () => { type: "singleRouterModelFetchResponse", success: false, error: "Requesty API error", - values: { provider: "requesty" }, + values: { provider: providerIdentifiers.requesty }, }) expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ type: "singleRouterModelFetchResponse", success: false, error: "LiteLLM connection failed", - values: { provider: "litellm" }, + values: { provider: providerIdentifiers.litellm }, }) // Verify final routerModels response includes successful providers and empty objects for failed ones @@ -832,35 +853,35 @@ describe("webviewMessageHandler - requestRouterModels", () => { type: "singleRouterModelFetchResponse", success: false, error: "Structured error message", - values: { provider: "openrouter" }, + values: { provider: providerIdentifiers.openrouter }, }) expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ type: "singleRouterModelFetchResponse", success: false, error: "Requesty API error", - values: { provider: "requesty" }, + values: { provider: providerIdentifiers.requesty }, }) expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ type: "singleRouterModelFetchResponse", success: false, error: "Unbound error", - values: { provider: "unbound" }, + values: { provider: providerIdentifiers.unbound }, }) expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ type: "singleRouterModelFetchResponse", success: false, error: "Vercel AI Gateway error", - values: { provider: "vercel-ai-gateway" }, + values: { provider: providerIdentifiers.vercelAiGateway }, }) expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ type: "singleRouterModelFetchResponse", success: false, error: "LiteLLM connection failed", - values: { provider: "litellm" }, + values: { provider: providerIdentifiers.litellm }, }) }) @@ -873,7 +894,7 @@ describe("webviewMessageHandler - requestRouterModels", () => { type: "singleRouterModelFetchResponse", success: false, error: "Roo Code Router has been removed. Please select and configure a different provider.", - values: { provider: "roo" }, + values: { provider: retiredProviderIdentifiers.roo }, }) }) @@ -891,7 +912,7 @@ describe("webviewMessageHandler - requestRouterModels", () => { // Verify message values take precedence over saved config (current unsaved field state wins) expect(mockGetModels).toHaveBeenCalledWith({ - provider: "litellm", + provider: providerIdentifiers.litellm, apiKey: "message-key", // From message.values baseUrl: "http://message-url", // From message.values }) @@ -1631,23 +1652,23 @@ describe("zooCodeSignOut", () => { ;(mockClineProvider as any).contextProxy = { ...mockClineProvider.contextProxy, - getProviderSettings: vi.fn().mockReturnValue({ apiProvider: "zoo-gateway" }), + getProviderSettings: vi.fn().mockReturnValue({ apiProvider: providerIdentifiers.zooGateway }), getValues: vi.fn().mockReturnValue({ currentApiConfigName: "Zoo Gateway" }), } ;(mockClineProvider as any).providerSettingsManager = { listConfig: vi.fn().mockResolvedValue([ - { name: "Zoo Gateway", apiProvider: "zoo-gateway" }, - { name: "Backup Zoo", apiProvider: "zoo-gateway" }, + { name: "Zoo Gateway", apiProvider: providerIdentifiers.zooGateway }, + { name: "Backup Zoo", apiProvider: providerIdentifiers.zooGateway }, ]), getProfile: vi .fn() .mockResolvedValueOnce({ - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, zooSessionToken: "token-active", zooGatewayModelId: "anthropic/claude-sonnet-4", }) .mockResolvedValueOnce({ - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, zooSessionToken: "token-backup", }), saveConfig, @@ -1674,13 +1695,15 @@ describe("zooCodeSignOut", () => { ;(mockClineProvider as any).contextProxy = { ...mockClineProvider.contextProxy, - getProviderSettings: vi.fn().mockReturnValue({ apiProvider: "zoo-gateway" }), + getProviderSettings: vi.fn().mockReturnValue({ apiProvider: providerIdentifiers.zooGateway }), getValues: vi.fn().mockReturnValue({ currentApiConfigName: "Zoo Gateway" }), } ;(mockClineProvider as any).providerSettingsManager = { - listConfig: vi.fn().mockResolvedValue([{ name: "Zoo Gateway", apiProvider: "zoo-gateway" }]), + listConfig: vi + .fn() + .mockResolvedValue([{ name: "Zoo Gateway", apiProvider: providerIdentifiers.zooGateway }]), getProfile: vi.fn().mockResolvedValue({ - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, zooGatewayModelId: "anthropic/claude-sonnet-4", }), saveConfig: vi.fn(), diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index e75620e6c1..b481e2ec1d 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -23,6 +23,7 @@ import { checkoutRestorePayloadSchema, getCompletionCheckpoint, providerIdentifiers, + retiredProviderIdentifiers, LmStudioModelsMessageType, OllamaModelsMessageType, OpenAiModelsMessageType, @@ -1412,7 +1413,7 @@ export const webviewMessageHandler = async ( type: RouterModelsMessageType.singleRouterModelFetchResponse, success: false, error: getRouterRemovalMessage(), - values: { provider: "roo" }, + values: { provider: retiredProviderIdentifiers.roo }, }) break } diff --git a/src/eslint-rules/no-raw-provider-identifiers.mjs b/src/eslint-rules/no-raw-provider-identifiers.mjs new file mode 100644 index 0000000000..7520cc6414 --- /dev/null +++ b/src/eslint-rules/no-raw-provider-identifiers.mjs @@ -0,0 +1,122 @@ +import { providerIdentifiers, retiredProviderIdentifiers } from "@roo-code/types/provider-identifiers" + +const providerReplacementsByValue = new Map([ + ...Object.entries(providerIdentifiers).map(([member, value]) => [value, `providerIdentifiers.${member}`]), + ...Object.entries(retiredProviderIdentifiers).map(([member, value]) => [ + value, + `retiredProviderIdentifiers.${member}`, + ]), +]) +const typescriptExpressionWrappers = new Set([ + "TSAsExpression", + "TSNonNullExpression", + "TSSatisfiesExpression", + "TSTypeAssertion", +]) + +function getStaticName(node) { + if (node?.type === "Identifier") { + return node.name + } + + if (node?.type === "MemberExpression") { + if (!node.computed && node.property.type === "Identifier") { + return node.property.name + } + + if (node.computed && node.property.type === "Literal" && typeof node.property.value === "string") { + return node.property.value + } + } + + if (node?.type === "Literal" && typeof node.value === "string") { + return node.value + } + + return undefined +} + +function isProviderLike(node) { + return getStaticName(node)?.toLowerCase().includes("provider") ?? false +} + +function getRawProvider(node) { + while (typescriptExpressionWrappers.has(node?.type)) { + node = node.expression + } + + if (node?.type === "Literal" && typeof node.value === "string") { + const replacement = providerReplacementsByValue.get(node.value) + return replacement ? { replacement, value: node.value } : undefined + } + + if (node?.type === "TemplateLiteral" && node.expressions.length === 0) { + const value = node.quasis[0]?.value.cooked + const replacement = value ? providerReplacementsByValue.get(value) : undefined + return replacement ? { replacement, value } : undefined + } + + return undefined +} + +export const noRawProviderIdentifiers = { + meta: { + type: "problem", + docs: { description: "Require canonical provider identifiers in provider-like contexts" }, + schema: [], + messages: { + useCanonical: + 'Use {{replacement}} instead of the raw provider identifier "{{value}}".', + }, + }, + create(context) { + function reportIfRawProvider(node) { + const provider = getRawProvider(node) + if (provider) { + context.report({ node, messageId: "useCanonical", data: provider }) + } + } + + return { + Property(node) { + if (isProviderLike(node.key)) { + reportIfRawProvider(node.value) + } + }, + PropertyDefinition(node) { + if (isProviderLike(node.key)) { + reportIfRawProvider(node.value) + } + }, + VariableDeclarator(node) { + if (isProviderLike(node.id)) { + reportIfRawProvider(node.init) + } + }, + AssignmentExpression(node) { + if (isProviderLike(node.left)) { + reportIfRawProvider(node.right) + } + }, + BinaryExpression(node) { + if (!["===", "!==", "==", "!="].includes(node.operator)) { + return + } + + if (isProviderLike(node.left)) { + reportIfRawProvider(node.right) + } + if (isProviderLike(node.right)) { + reportIfRawProvider(node.left) + } + }, + SwitchStatement(node) { + if (isProviderLike(node.discriminant)) { + for (const switchCase of node.cases) { + reportIfRawProvider(switchCase.test) + } + } + }, + } + }, +} diff --git a/src/eslint-rules/no-raw-provider-identifiers.test.mjs b/src/eslint-rules/no-raw-provider-identifiers.test.mjs new file mode 100644 index 0000000000..c4efaf90ff --- /dev/null +++ b/src/eslint-rules/no-raw-provider-identifiers.test.mjs @@ -0,0 +1,39 @@ +import { RuleTester } from "eslint" + +import { noRawProviderIdentifiers } from "./no-raw-provider-identifiers.mjs" + +const ruleTester = new RuleTester({ + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + }, +}) + +ruleTester.run("no-raw-provider-identifiers", noRawProviderIdentifiers, { + valid: [ + "const apiProvider = retiredProviderIdentifiers.roo", + "const provider = retiredProviderIdentifiers.groq", + ], + invalid: [ + { + code: 'const apiProvider = "roo"', + errors: [ + { + message: + 'Use retiredProviderIdentifiers.roo instead of the raw provider identifier "roo".', + type: "Literal", + }, + ], + }, + { + code: "const persistedProvider = `groq`", + errors: [ + { + message: + 'Use retiredProviderIdentifiers.groq instead of the raw provider identifier "groq".', + type: "TemplateLiteral", + }, + ], + }, + ], +}) diff --git a/src/eslint.config.mjs b/src/eslint.config.mjs index 65965eb8d5..27341c6770 100644 --- a/src/eslint.config.mjs +++ b/src/eslint.config.mjs @@ -1,5 +1,7 @@ import { config } from "@roo-code/config-eslint/base" +import { noRawProviderIdentifiers } from "./eslint-rules/no-raw-provider-identifiers.mjs" + /** @type {import("eslint").Linter.Config} */ export default [ ...config, @@ -31,6 +33,22 @@ export default [ "no-undef": "off", }, }, + { + files: ["**/*.ts", "**/*.tsx"], + ignores: [ + "**/fixtures/**", + ], + plugins: { + zoo: { + rules: { + "no-raw-provider-identifiers": noRawProviderIdentifiers, + }, + }, + }, + rules: { + "zoo/no-raw-provider-identifiers": "error", + }, + }, { // Ratchet: enforce no-floating-promises directory by directory. Each // directory is added here once its floating promises are resolved. diff --git a/src/package.json b/src/package.json index 180e3f7e2e..55035dc206 100644 --- a/src/package.json +++ b/src/package.json @@ -529,6 +529,7 @@ }, "devDependencies": { "@ai-sdk/openai-compatible": "2.0.56", + "@typescript-eslint/parser": "8.32.1", "@roo-code/build": "workspace:^", "@roo-code/config-eslint": "workspace:^", "@roo-code/config-typescript": "workspace:^", diff --git a/src/services/code-index/__tests__/config-manager.spec.ts b/src/services/code-index/__tests__/config-manager.spec.ts index 665c83314f..6a496b6809 100644 --- a/src/services/code-index/__tests__/config-manager.spec.ts +++ b/src/services/code-index/__tests__/config-manager.spec.ts @@ -13,6 +13,7 @@ vi.mock("../../../shared/embeddingModels") // Import mocked functions import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../../shared/embeddingModels" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Type the mocked functions const mockedGetDefaultModelId = vi.mocked(getDefaultModelId) @@ -102,7 +103,7 @@ describe("CodeIndexConfigManager", () => { expect(result.currentConfig).toEqual({ isConfigured: false, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: undefined, openAiOptions: { openAiNativeApiKey: "" }, ollamaOptions: { ollamaBaseUrl: "" }, @@ -118,7 +119,7 @@ describe("CodeIndexConfigManager", () => { const mockGlobalState = { codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "text-embedding-3-large", } @@ -134,7 +135,7 @@ describe("CodeIndexConfigManager", () => { expect(result.currentConfig).toMatchObject({ isConfigured: true, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-large", openAiOptions: { openAiNativeApiKey: "test-openai-key" }, ollamaOptions: { ollamaBaseUrl: "" }, @@ -301,7 +302,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-large", }) setupSecretMocks({ @@ -314,7 +315,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexEmbedderBaseUrl: "http://ollama.local", codebaseIndexEmbedderModelId: "nomic-embed-text", }) @@ -328,7 +329,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -342,7 +343,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-large", }) @@ -363,7 +364,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -376,7 +377,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-ada-002", }) @@ -396,7 +397,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -414,7 +415,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -439,7 +440,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://old-qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -453,7 +454,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://new-qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) @@ -466,7 +467,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -480,7 +481,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "unknown-model", }) @@ -493,7 +494,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexEmbedderBaseUrl: "http://old-ollama.local", codebaseIndexEmbedderModelId: "nomic-embed-text", }) @@ -504,7 +505,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexEmbedderBaseUrl: "http://new-ollama.local", codebaseIndexEmbedderModelId: "nomic-embed-text", }) @@ -753,7 +754,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, }) setupSecretMocks({}) @@ -763,7 +764,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexEmbedderBaseUrl: "http://ollama.local", }) @@ -777,7 +778,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, }) setupSecretMocks({}) @@ -787,7 +788,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-large", }) @@ -800,7 +801,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", codebaseIndexSearchMinScore: 0.8, // User setting }) @@ -816,7 +817,7 @@ describe("CodeIndexConfigManager", () => { it("should fall back to model-specific threshold when user setting is undefined", async () => { // Mock the model score threshold mockedGetModelScoreThreshold.mockImplementation((provider, modelId) => { - if (provider === "ollama" && modelId === "nomic-embed-code") { + if (provider === providerIdentifiers.ollama && modelId === "nomic-embed-code") { return 0.15 } return undefined @@ -825,7 +826,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexEmbedderModelId: "nomic-embed-code", // No codebaseIndexSearchMinScore - user hasn't configured it }) @@ -839,7 +840,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "unknown-model", // Model not in profiles // No codebaseIndexSearchMinScore }) @@ -857,7 +858,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexEmbedderModelId: "nomic-embed-code", codebaseIndexSearchMinScore: 0, // User explicitly sets 0 }) @@ -903,7 +904,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, // No modelId specified // No codebaseIndexSearchMinScore }) @@ -920,7 +921,7 @@ describe("CodeIndexConfigManager", () => { it("should handle priority correctly: user > model > default", async () => { // Mock the model score threshold mockedGetModelScoreThreshold.mockImplementation((provider, modelId) => { - if (provider === "ollama" && modelId === "nomic-embed-code") { + if (provider === providerIdentifiers.ollama && modelId === "nomic-embed-code") { return 0.15 } return undefined @@ -930,7 +931,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexEmbedderModelId: "nomic-embed-code", // Has 0.15 threshold codebaseIndexSearchMinScore: 0.9, // User overrides }) @@ -942,7 +943,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexEmbedderModelId: "nomic-embed-code", // No user setting }) @@ -955,7 +956,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "custom-unknown-model", // No user setting, unknown model }) @@ -972,7 +973,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", codebaseIndexSearchMaxResults: 150, // User setting }) @@ -984,7 +985,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", // No user setting }) @@ -997,7 +998,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", codebaseIndexSearchMaxResults: 10, // Minimum allowed }) @@ -1010,7 +1011,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", codebaseIndexSearchMaxResults: 200, // Maximum allowed }) @@ -1028,7 +1029,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({}) @@ -1039,7 +1040,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", codebaseIndexSearchMinScore: 0.5, // Changed unrelated setting }) @@ -1054,7 +1055,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, // Always enabled now codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, }) setupSecretMocks({}) @@ -1076,7 +1077,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, }) setupSecretMocks({ codeIndexOpenAiKey: "", @@ -1103,7 +1104,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -1117,7 +1118,7 @@ describe("CodeIndexConfigManager", () => { const mockPrevConfig = { enabled: true, configured: true, - embedderProvider: "openai" as const, + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-large", // Different model with different dimensions openAiKey: "test-key", ollamaBaseUrl: undefined, @@ -1149,7 +1150,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -1183,7 +1184,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -1216,7 +1217,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, }) setupSecretMocks({ codeIndexOpenAiKey: "test-key", @@ -1231,7 +1232,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexEmbedderBaseUrl: "http://ollama.local", }) @@ -1306,7 +1307,7 @@ describe("CodeIndexConfigManager", () => { return { codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "gemini", + codebaseIndexEmbedderProvider: providerIdentifiers.gemini, } } return undefined @@ -1326,7 +1327,7 @@ describe("CodeIndexConfigManager", () => { return { codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "gemini", + codebaseIndexEmbedderProvider: providerIdentifiers.gemini, } } return undefined @@ -1343,7 +1344,7 @@ describe("CodeIndexConfigManager", () => { it("should return false when required values are missing", async () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, }) await configManager.loadConfiguration() @@ -1356,7 +1357,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-large", }) setupSecretMocks({ @@ -1371,7 +1372,7 @@ describe("CodeIndexConfigManager", () => { const config = configManager.getConfig() expect(config).toMatchObject({ isConfigured: true, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-large", openAiOptions: { openAiNativeApiKey: "test-openai-key" }, ollamaOptions: { ollamaBaseUrl: undefined }, @@ -1410,7 +1411,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -1430,7 +1431,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, // Always enabled now codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -1453,7 +1454,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://qdrant.local", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", }) setupSecretMocks({ @@ -1475,7 +1476,7 @@ describe("CodeIndexConfigManager", () => { // Initial state: disabled mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: false, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexQdrantUrl: "http://localhost:6333", }) mockContextProxy.getSecret.mockReturnValue(undefined) @@ -1487,7 +1488,7 @@ describe("CodeIndexConfigManager", () => { // Update the internal state to enabled with proper configuration mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexQdrantUrl: "http://localhost:6333", }) mockContextProxy.getSecret.mockImplementation((key: string) => { @@ -1505,7 +1506,7 @@ describe("CodeIndexConfigManager", () => { // Initial state: enabled and configured mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexQdrantUrl: "http://localhost:6333", }) mockContextProxy.getSecret.mockImplementation((key: string) => { @@ -1517,7 +1518,7 @@ describe("CodeIndexConfigManager", () => { const previousSnapshot: PreviousConfigSnapshot = { enabled: true, configured: true, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, openAiKey: "test-key", qdrantUrl: "http://localhost:6333", } @@ -1525,7 +1526,7 @@ describe("CodeIndexConfigManager", () => { // Update to disabled mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: false, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexQdrantUrl: "http://localhost:6333", }) mockContextProxy.getSecret.mockImplementation((key: string) => { @@ -1543,7 +1544,7 @@ describe("CodeIndexConfigManager", () => { // Initial state: enabled and configured mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexQdrantUrl: "http://localhost:6333", }) mockContextProxy.getSecret.mockImplementation((key: string) => { @@ -1572,7 +1573,7 @@ describe("CodeIndexConfigManager", () => { const previousSnapshot: PreviousConfigSnapshot = { enabled: false, configured: false, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, } // Same config, still disabled @@ -1584,7 +1585,7 @@ describe("CodeIndexConfigManager", () => { // Initial state: enabled with openai mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexOllamaBaseUrl: "http://localhost:11434", codebaseIndexQdrantUrl: "http://localhost:6333", }) @@ -1594,7 +1595,7 @@ describe("CodeIndexConfigManager", () => { const previousSnapshot: PreviousConfigSnapshot = { enabled: true, configured: true, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, openAiKey: "test-key", qdrantUrl: "http://localhost:6333", } @@ -1607,7 +1608,7 @@ describe("CodeIndexConfigManager", () => { // Initial state: disabled with openai mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: false, - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, }) mockContextProxy.getSecret.mockReturnValue(undefined) configManager = new CodeIndexConfigManager(mockContextProxy) @@ -1615,7 +1616,7 @@ describe("CodeIndexConfigManager", () => { const previousSnapshot: PreviousConfigSnapshot = { enabled: false, configured: false, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, } // Provider changed but feature is disabled @@ -1635,7 +1636,7 @@ describe("CodeIndexConfigManager", () => { it("should load configuration and return proper structure", async () => { const mockConfigValues = { codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-ada-002", codebaseIndexQdrantUrl: "http://localhost:6333", codebaseIndexSearchMinScore: 0.5, @@ -1665,7 +1666,7 @@ describe("CodeIndexConfigManager", () => { // Initial state: disabled mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: false, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexQdrantUrl: "http://localhost:6333", }) mockContextProxy.getSecret.mockReturnValue(undefined) @@ -1677,7 +1678,7 @@ describe("CodeIndexConfigManager", () => { // Change to enabled with proper configuration mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexQdrantUrl: "http://localhost:6333", }) mockContextProxy.getSecret.mockImplementation((key: string) => { @@ -1694,7 +1695,7 @@ describe("CodeIndexConfigManager", () => { it("should return the current configuration", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexQdrantUrl: "http://localhost:6333", }) mockContextProxy.getSecret.mockImplementation((key: string) => { @@ -1715,7 +1716,7 @@ describe("CodeIndexConfigManager", () => { it("should return true when OpenAI provider is properly configured", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexQdrantUrl: "http://localhost:6333", }) mockContextProxy.getSecret.mockImplementation((key: string) => { @@ -1730,7 +1731,7 @@ describe("CodeIndexConfigManager", () => { it("should return false when OpenAI provider is missing API key", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexQdrantUrl: "http://localhost:6333", }) mockContextProxy.getSecret.mockReturnValue(undefined) @@ -1742,7 +1743,7 @@ describe("CodeIndexConfigManager", () => { it("should return true when Ollama provider is properly configured", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderProvider: providerIdentifiers.ollama, codebaseIndexEmbedderBaseUrl: "http://localhost:11434", codebaseIndexQdrantUrl: "http://localhost:6333", }) @@ -1755,7 +1756,7 @@ describe("CodeIndexConfigManager", () => { it("should return false when Qdrant URL is missing", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, }) mockContextProxy.getSecret.mockImplementation((key: string) => { if (key === "codeIndexOpenAiKey") return "test-key" @@ -1801,7 +1802,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", codebaseIndexEmbedderModelDimension: 2048, // Custom dimension should be ignored codebaseIndexQdrantUrl: "http://localhost:6333", @@ -1874,7 +1875,7 @@ describe("CodeIndexConfigManager", () => { mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, // No modelId specified codebaseIndexQdrantUrl: "http://localhost:6333", }) @@ -1919,7 +1920,7 @@ describe("CodeIndexConfigManager", () => { it("should correctly handle OpenRouter mistral model dimensions across restarts", async () => { // Mock getModelDimension to return correct dimensions for OpenRouter models mockedGetModelDimension.mockImplementation((provider, modelId) => { - if (provider === "openrouter") { + if (provider === providerIdentifiers.openrouter) { if (modelId === "mistralai/codestral-embed-2505") return 1536 if (modelId === "mistralai/mistral-embed-2312") return 1024 if (modelId === "openai/text-embedding-3-large") return 3072 @@ -1930,7 +1931,7 @@ describe("CodeIndexConfigManager", () => { // Initial configuration with OpenRouter and Mistral model mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openrouter", + codebaseIndexEmbedderProvider: providerIdentifiers.openrouter, codebaseIndexEmbedderModelId: "mistralai/codestral-embed-2505", codebaseIndexQdrantUrl: "http://localhost:6333", }) @@ -1959,7 +1960,7 @@ describe("CodeIndexConfigManager", () => { it("should not require restart for OpenRouter when same model dimensions are used", async () => { // Mock both models to have same dimension mockedGetModelDimension.mockImplementation((provider, modelId) => { - if (provider === "openrouter") { + if (provider === providerIdentifiers.openrouter) { if (modelId === "mistralai/codestral-embed-2505") return 1536 if (modelId === "openai/text-embedding-3-small") return 1536 } @@ -1969,7 +1970,7 @@ describe("CodeIndexConfigManager", () => { // Initial state with OpenRouter and Mistral model mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openrouter", + codebaseIndexEmbedderProvider: providerIdentifiers.openrouter, codebaseIndexEmbedderModelId: "mistralai/codestral-embed-2505", codebaseIndexQdrantUrl: "http://localhost:6333", }) @@ -1984,7 +1985,7 @@ describe("CodeIndexConfigManager", () => { // Change to another model with same dimension mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openrouter", + codebaseIndexEmbedderProvider: providerIdentifiers.openrouter, codebaseIndexEmbedderModelId: "openai/text-embedding-3-small", // Same 1536 dimension codebaseIndexQdrantUrl: "http://localhost:6333", }) @@ -1997,7 +1998,7 @@ describe("CodeIndexConfigManager", () => { it("should require restart for OpenRouter when model dimensions change", async () => { // Mock models with different dimensions mockedGetModelDimension.mockImplementation((provider, modelId) => { - if (provider === "openrouter") { + if (provider === providerIdentifiers.openrouter) { if (modelId === "mistralai/codestral-embed-2505") return 1536 if (modelId === "mistralai/mistral-embed-2312") return 1024 } @@ -2007,7 +2008,7 @@ describe("CodeIndexConfigManager", () => { // Initial state with 1536-dimension model mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openrouter", + codebaseIndexEmbedderProvider: providerIdentifiers.openrouter, codebaseIndexEmbedderModelId: "mistralai/codestral-embed-2505", codebaseIndexQdrantUrl: "http://localhost:6333", }) @@ -2022,7 +2023,7 @@ describe("CodeIndexConfigManager", () => { // Change to model with different dimension mockContextProxy.getGlobalState.mockReturnValue({ codebaseIndexEnabled: true, - codebaseIndexEmbedderProvider: "openrouter", + codebaseIndexEmbedderProvider: providerIdentifiers.openrouter, codebaseIndexEmbedderModelId: "mistralai/mistral-embed-2312", // Different 1024 dimension codebaseIndexQdrantUrl: "http://localhost:6333", }) diff --git a/src/services/code-index/__tests__/manager.spec.ts b/src/services/code-index/__tests__/manager.spec.ts index edcd8c22f4..627163f900 100644 --- a/src/services/code-index/__tests__/manager.spec.ts +++ b/src/services/code-index/__tests__/manager.spec.ts @@ -2,6 +2,7 @@ import { CodeIndexManager } from "../manager" import { CodeIndexServiceFactory } from "../service-factory" import type { MockedClass } from "vitest" import * as path from "path" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Helper: create a mock vscode.Uri from an fsPath function mockUri(fsPath: string, scheme = "file") { @@ -181,7 +182,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { isFeatureEnabled: true, getConfig: vi.fn().mockReturnValue({ isConfigured: true, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-small", openAiOptions: { openAiNativeApiKey: "test-key" }, qdrantUrl: "http://localhost:6333", @@ -250,7 +251,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { isFeatureEnabled: true, getConfig: vi.fn().mockReturnValue({ isConfigured: true, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-small", openAiOptions: { openAiNativeApiKey: "test-key" }, qdrantUrl: "http://localhost:6333", @@ -380,7 +381,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { isFeatureEnabled: true, getConfig: vitest.fn().mockReturnValue({ isConfigured: true, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-small", openAiOptions: { openAiNativeApiKey: "test-key" }, qdrantUrl: "http://localhost:6333", @@ -477,7 +478,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { isFeatureEnabled: true, getConfig: vi.fn().mockReturnValue({ isConfigured: true, - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-small", openAiOptions: { openAiNativeApiKey: "test-key" }, qdrantUrl: "http://localhost:6333", @@ -584,7 +585,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { getGlobalState: vi.fn().mockReturnValue({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://localhost:6333", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderModelId: "text-embedding-3-small", codebaseIndexEmbedderModelDimension: 1536, codebaseIndexSearchMaxResults: 10, diff --git a/src/services/code-index/__tests__/service-factory.spec.ts b/src/services/code-index/__tests__/service-factory.spec.ts index aafc198d85..aaf248d47a 100644 --- a/src/services/code-index/__tests__/service-factory.spec.ts +++ b/src/services/code-index/__tests__/service-factory.spec.ts @@ -38,6 +38,7 @@ const MockedQdrantVectorStore = QdrantVectorStore as MockedClass const mockGetModelDimension = getModelDimension as MockedFunction @@ -63,7 +64,7 @@ describe("CodeIndexServiceFactory", () => { // Arrange const testModelId = "text-embedding-3-large" const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: testModelId, openAiOptions: { openAiNativeApiKey: "test-api-key", @@ -85,7 +86,7 @@ describe("CodeIndexServiceFactory", () => { // Arrange const testModelId = "nomic-embed-text:latest" const testConfig = { - embedderProvider: "ollama", + embedderProvider: providerIdentifiers.ollama, modelId: testModelId, ollamaOptions: { ollamaBaseUrl: "http://localhost:11434", @@ -106,7 +107,7 @@ describe("CodeIndexServiceFactory", () => { it("should handle undefined model ID for OpenAI embedder", () => { // Arrange const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: undefined, openAiOptions: { openAiNativeApiKey: "test-api-key", @@ -127,7 +128,7 @@ describe("CodeIndexServiceFactory", () => { it("should handle undefined model ID for Ollama embedder", () => { // Arrange const testConfig = { - embedderProvider: "ollama", + embedderProvider: providerIdentifiers.ollama, modelId: undefined, ollamaOptions: { ollamaBaseUrl: "http://localhost:11434", @@ -148,7 +149,7 @@ describe("CodeIndexServiceFactory", () => { it("should throw error when OpenAI API key is missing", () => { // Arrange const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-large", openAiOptions: { openAiNativeApiKey: undefined, @@ -163,7 +164,7 @@ describe("CodeIndexServiceFactory", () => { it("should throw error when Ollama base URL is missing", () => { // Arrange const testConfig = { - embedderProvider: "ollama", + embedderProvider: providerIdentifiers.ollama, modelId: "nomic-embed-text:latest", ollamaOptions: { ollamaBaseUrl: undefined, @@ -270,7 +271,7 @@ describe("CodeIndexServiceFactory", () => { it("should create GeminiEmbedder with default model when no modelId specified", () => { // Arrange const testConfig = { - embedderProvider: "gemini", + embedderProvider: providerIdentifiers.gemini, geminiOptions: { apiKey: "test-gemini-api-key", }, @@ -287,7 +288,7 @@ describe("CodeIndexServiceFactory", () => { it("should create GeminiEmbedder with specified modelId", () => { // Arrange const testConfig = { - embedderProvider: "gemini", + embedderProvider: providerIdentifiers.gemini, modelId: "gemini-embedding-001", geminiOptions: { apiKey: "test-gemini-api-key", @@ -306,7 +307,7 @@ describe("CodeIndexServiceFactory", () => { // Arrange - service-factory passes the config modelId directly; // GeminiEmbedder handles the migration internally const testConfig = { - embedderProvider: "gemini", + embedderProvider: providerIdentifiers.gemini, modelId: "text-embedding-004", geminiOptions: { apiKey: "test-gemini-api-key", @@ -324,7 +325,7 @@ describe("CodeIndexServiceFactory", () => { it("should throw error when Gemini API key is missing", () => { // Arrange const testConfig = { - embedderProvider: "gemini", + embedderProvider: providerIdentifiers.gemini, geminiOptions: { apiKey: undefined, }, @@ -338,7 +339,7 @@ describe("CodeIndexServiceFactory", () => { it("should throw error when Gemini options are missing", () => { // Arrange const testConfig = { - embedderProvider: "gemini", + embedderProvider: providerIdentifiers.gemini, geminiOptions: undefined, } mockConfigManager.getConfig.mockReturnValue(testConfig as any) @@ -381,7 +382,7 @@ describe("CodeIndexServiceFactory", () => { // Arrange const testModelId = "text-embedding-3-large" const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: testModelId, qdrantUrl: "http://localhost:6333", qdrantApiKey: "test-key", @@ -406,7 +407,7 @@ describe("CodeIndexServiceFactory", () => { // Arrange const testModelId = "nomic-embed-text:latest" const testConfig = { - embedderProvider: "ollama", + embedderProvider: providerIdentifiers.ollama, modelId: testModelId, qdrantUrl: "http://localhost:6333", qdrantApiKey: "test-key", @@ -592,7 +593,7 @@ describe("CodeIndexServiceFactory", () => { it("should use model-specific dimension for Gemini provider", () => { // Arrange const testConfig = { - embedderProvider: "gemini", + embedderProvider: providerIdentifiers.gemini, modelId: "gemini-embedding-001", qdrantUrl: "http://localhost:6333", qdrantApiKey: "test-key", @@ -616,7 +617,7 @@ describe("CodeIndexServiceFactory", () => { it("should use default model dimension for Gemini when modelId not specified", () => { // Arrange const testConfig = { - embedderProvider: "gemini", + embedderProvider: providerIdentifiers.gemini, qdrantUrl: "http://localhost:6333", qdrantApiKey: "test-key", } @@ -641,7 +642,7 @@ describe("CodeIndexServiceFactory", () => { it("should use default model when config.modelId is undefined", () => { // Arrange const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: undefined, qdrantUrl: "http://localhost:6333", qdrantApiKey: "test-key", @@ -665,7 +666,7 @@ describe("CodeIndexServiceFactory", () => { it("should throw error when vector dimension cannot be determined", () => { // Arrange const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "unknown-model", qdrantUrl: "http://localhost:6333", qdrantApiKey: "test-key", @@ -680,7 +681,7 @@ describe("CodeIndexServiceFactory", () => { it("should throw error when Qdrant URL is missing", () => { // Arrange const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-small", qdrantUrl: undefined, qdrantApiKey: "test-key", @@ -716,7 +717,7 @@ describe("CodeIndexServiceFactory", () => { it("should validate OpenAI embedder successfully", async () => { // Arrange const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-small", openAiOptions: { openAiNativeApiKey: "test-api-key", @@ -740,7 +741,7 @@ describe("CodeIndexServiceFactory", () => { it("should return validation error from OpenAI embedder", async () => { // Arrange const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-small", openAiOptions: { openAiNativeApiKey: "invalid-key", @@ -769,7 +770,7 @@ describe("CodeIndexServiceFactory", () => { it("should validate Ollama embedder successfully", async () => { // Arrange const testConfig = { - embedderProvider: "ollama", + embedderProvider: providerIdentifiers.ollama, modelId: "nomic-embed-text", ollamaOptions: { ollamaBaseUrl: "http://localhost:11434", @@ -818,7 +819,7 @@ describe("CodeIndexServiceFactory", () => { it("should validate Gemini embedder successfully", async () => { // Arrange const testConfig = { - embedderProvider: "gemini", + embedderProvider: providerIdentifiers.gemini, geminiOptions: { apiKey: "test-gemini-api-key", }, @@ -841,7 +842,7 @@ describe("CodeIndexServiceFactory", () => { it("should handle validation exceptions", async () => { // Arrange const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-small", openAiOptions: { openAiNativeApiKey: "test-api-key", @@ -869,7 +870,7 @@ describe("CodeIndexServiceFactory", () => { it("should return error for invalid embedder configuration", async () => { // Arrange const testConfig = { - embedderProvider: "openai", + embedderProvider: providerIdentifiers.openai, modelId: "text-embedding-3-small", openAiOptions: { openAiNativeApiKey: undefined, // Missing API key diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts index abac552561..dc6f72d34a 100644 --- a/src/services/code-index/config-manager.ts +++ b/src/services/code-index/config-manager.ts @@ -4,6 +4,7 @@ import { EmbedderProvider } from "./interfaces/manager" import { CodeIndexConfig, PreviousConfigSnapshot } from "./interfaces/config" import { DEFAULT_SEARCH_MIN_SCORE, DEFAULT_MAX_SEARCH_RESULTS } from "./constants" import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../shared/embeddingModels" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" /** * Manages configuration state and validation for the code indexing feature. @@ -11,7 +12,7 @@ import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from ".. */ export class CodeIndexConfigManager { private codebaseIndexEnabled: boolean = false - private embedderProvider: EmbedderProvider = "openai" + private embedderProvider: EmbedderProvider = providerIdentifiers.openai private modelId?: string private modelDimension?: number private openAiOptions?: ApiHandlerOptions @@ -48,7 +49,7 @@ export class CodeIndexConfigManager { const codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? { codebaseIndexEnabled: false, codebaseIndexQdrantUrl: "http://localhost:6333", - codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderProvider: providerIdentifiers.openai, codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "", codebaseIndexSearchMinScore: undefined, @@ -106,24 +107,24 @@ export class CodeIndexConfigManager { this.openAiOptions = { openAiNativeApiKey: openAiKey } // Set embedder provider with support for openai-compatible - if (codebaseIndexEmbedderProvider === "ollama") { - this.embedderProvider = "ollama" + if (codebaseIndexEmbedderProvider === providerIdentifiers.ollama) { + this.embedderProvider = providerIdentifiers.ollama } else if (codebaseIndexEmbedderProvider === "openai-compatible") { this.embedderProvider = "openai-compatible" - } else if (codebaseIndexEmbedderProvider === "gemini") { - this.embedderProvider = "gemini" - } else if (codebaseIndexEmbedderProvider === "mistral") { - this.embedderProvider = "mistral" - } else if (codebaseIndexEmbedderProvider === "vercel-ai-gateway") { - this.embedderProvider = "vercel-ai-gateway" + } else if (codebaseIndexEmbedderProvider === providerIdentifiers.gemini) { + this.embedderProvider = providerIdentifiers.gemini + } else if (codebaseIndexEmbedderProvider === providerIdentifiers.mistral) { + this.embedderProvider = providerIdentifiers.mistral + } else if (codebaseIndexEmbedderProvider === providerIdentifiers.vercelAiGateway) { + this.embedderProvider = providerIdentifiers.vercelAiGateway } else if ((codebaseIndexEmbedderProvider as string) === "bedrock") { - this.embedderProvider = "bedrock" - } else if (codebaseIndexEmbedderProvider === "openrouter") { - this.embedderProvider = "openrouter" + this.embedderProvider = providerIdentifiers.bedrock + } else if (codebaseIndexEmbedderProvider === providerIdentifiers.openrouter) { + this.embedderProvider = providerIdentifiers.openrouter } else if (codebaseIndexEmbedderProvider === "semble") { this.embedderProvider = "semble" } else { - this.embedderProvider = "openai" + this.embedderProvider = providerIdentifiers.openai } this.modelId = codebaseIndexEmbedderModelId || undefined @@ -238,11 +239,11 @@ export class CodeIndexConfigManager { return true } - if (this.embedderProvider === "openai") { + if (this.embedderProvider === providerIdentifiers.openai) { const openAiKey = this.openAiOptions?.openAiNativeApiKey const qdrantUrl = this.qdrantUrl return !!(openAiKey && qdrantUrl) - } else if (this.embedderProvider === "ollama") { + } else if (this.embedderProvider === providerIdentifiers.ollama) { // Ollama model ID has a default, so only base URL is strictly required for config const ollamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl const qdrantUrl = this.qdrantUrl @@ -253,28 +254,28 @@ export class CodeIndexConfigManager { const qdrantUrl = this.qdrantUrl const isConfigured = !!(baseUrl && apiKey && qdrantUrl) return isConfigured - } else if (this.embedderProvider === "gemini") { + } else if (this.embedderProvider === providerIdentifiers.gemini) { const apiKey = this.geminiOptions?.apiKey const qdrantUrl = this.qdrantUrl const isConfigured = !!(apiKey && qdrantUrl) return isConfigured - } else if (this.embedderProvider === "mistral") { + } else if (this.embedderProvider === providerIdentifiers.mistral) { const apiKey = this.mistralOptions?.apiKey const qdrantUrl = this.qdrantUrl const isConfigured = !!(apiKey && qdrantUrl) return isConfigured - } else if (this.embedderProvider === "vercel-ai-gateway") { + } else if (this.embedderProvider === providerIdentifiers.vercelAiGateway) { const apiKey = this.vercelAiGatewayOptions?.apiKey const qdrantUrl = this.qdrantUrl const isConfigured = !!(apiKey && qdrantUrl) return isConfigured - } else if (this.embedderProvider === "bedrock") { + } else if (this.embedderProvider === providerIdentifiers.bedrock) { // Only region is required for Bedrock (profile is optional) const region = this.bedrockOptions?.region const qdrantUrl = this.qdrantUrl const isConfigured = !!(region && qdrantUrl) return isConfigured - } else if (this.embedderProvider === "openrouter") { + } else if (this.embedderProvider === providerIdentifiers.openrouter) { const apiKey = this.openRouterOptions?.apiKey const qdrantUrl = this.qdrantUrl const isConfigured = !!(apiKey && qdrantUrl) diff --git a/src/services/code-index/embedders/__tests__/openrouter.spec.ts b/src/services/code-index/embedders/__tests__/openrouter.spec.ts index 088e9c7185..3f8d526e19 100644 --- a/src/services/code-index/embedders/__tests__/openrouter.spec.ts +++ b/src/services/code-index/embedders/__tests__/openrouter.spec.ts @@ -4,6 +4,7 @@ import { OpenAI } from "openai" import { OpenRouterEmbedder, OPENROUTER_DEFAULT_PROVIDER_NAME } from "../openrouter" import { getModelDimension, getDefaultModelId } from "../../../../shared/embeddingModels" import { clearAllMocks, restoreGlobals } from "../../../../test-utils/reset" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Mock the OpenAI SDK vi.mock("openai") @@ -340,7 +341,7 @@ describe("OpenRouterEmbedder", () => { }) it("should validate configuration with specificProvider", async () => { - const specificProvider = "openai" + const specificProvider = providerIdentifiers.openai const embedderWithProvider = new OpenRouterEmbedder(mockApiKey, undefined, undefined, specificProvider) const testEmbedding = new Float32Array([0.25, 0.5]) diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts index 96b6d80c90..beab32ad02 100644 --- a/src/services/code-index/service-factory.ts +++ b/src/services/code-index/service-factory.ts @@ -26,6 +26,7 @@ import { ICodeParser, IEmbedder, IFileWatcher, IVectorStore } from "./interfaces import { CodeIndexConfigManager } from "./config-manager" import { CacheManager } from "./cache-manager" import { BATCH_SEGMENT_THRESHOLD } from "./constants" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" /** * Factory class responsible for creating and configuring code indexing service dependencies. @@ -55,7 +56,7 @@ export class CodeIndexServiceFactory { ) } - if (provider === "openai") { + if (provider === providerIdentifiers.openai) { const apiKey = config.openAiOptions?.openAiNativeApiKey if (!apiKey) { @@ -65,7 +66,7 @@ export class CodeIndexServiceFactory { ...config.openAiOptions, openAiEmbeddingModelId: config.modelId, }) - } else if (provider === "ollama") { + } else if (provider === providerIdentifiers.ollama) { if (!config.ollamaOptions?.ollamaBaseUrl) { throw new Error(t("embeddings:serviceFactory.ollamaConfigMissing")) } @@ -82,28 +83,28 @@ export class CodeIndexServiceFactory { config.openAiCompatibleOptions.apiKey, config.modelId, ) - } else if (provider === "gemini") { + } else if (provider === providerIdentifiers.gemini) { if (!config.geminiOptions?.apiKey) { throw new Error(t("embeddings:serviceFactory.geminiConfigMissing")) } return new GeminiEmbedder(config.geminiOptions.apiKey, config.modelId) - } else if (provider === "mistral") { + } else if (provider === providerIdentifiers.mistral) { if (!config.mistralOptions?.apiKey) { throw new Error(t("embeddings:serviceFactory.mistralConfigMissing")) } return new MistralEmbedder(config.mistralOptions.apiKey, config.modelId) - } else if (provider === "vercel-ai-gateway") { + } else if (provider === providerIdentifiers.vercelAiGateway) { if (!config.vercelAiGatewayOptions?.apiKey) { throw new Error(t("embeddings:serviceFactory.vercelAiGatewayConfigMissing")) } return new VercelAiGatewayEmbedder(config.vercelAiGatewayOptions.apiKey, config.modelId) - } else if (provider === "bedrock") { + } else if (provider === providerIdentifiers.bedrock) { // Only region is required for Bedrock (profile is optional) if (!config.bedrockOptions?.region) { throw new Error(t("embeddings:serviceFactory.bedrockConfigMissing")) } return new BedrockEmbedder(config.bedrockOptions.region, config.bedrockOptions.profile, config.modelId) - } else if (provider === "openrouter") { + } else if (provider === providerIdentifiers.openrouter) { if (!config.openRouterOptions?.apiKey) { throw new Error(t("embeddings:serviceFactory.openRouterConfigMissing")) } diff --git a/src/shared/__tests__/ProfileValidator.spec.ts b/src/shared/__tests__/ProfileValidator.spec.ts index ace96c9fd0..865fa5cf51 100644 --- a/src/shared/__tests__/ProfileValidator.spec.ts +++ b/src/shared/__tests__/ProfileValidator.spec.ts @@ -79,7 +79,7 @@ describe("ProfileValidator", () => { providers: {}, } const profile: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, openAiModelId: "gpt-4", } @@ -107,7 +107,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, openAiModelId: "gpt-4", } @@ -122,7 +122,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, openAiModelId: "any-model-id", } @@ -137,7 +137,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, } expect(ProfileValidator.isProfileAllowed(profile, allowList)).toBe(false) @@ -151,7 +151,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, openAiModelId: "gpt-4", } @@ -166,7 +166,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, openAiModelId: "gpt-4", } @@ -181,7 +181,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, openAiModelId: "gpt-4", } @@ -196,7 +196,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, openAiModelId: "gpt-4", } @@ -211,7 +211,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-3-opus", } @@ -226,7 +226,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "ollama", + apiProvider: providerIdentifiers.ollama, ollamaModelId: "llama3", } @@ -304,7 +304,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "lmstudio", + apiProvider: providerIdentifiers.lmstudio, lmStudioModelId: "lmstudio-model", } @@ -319,7 +319,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openrouter-model", } @@ -334,7 +334,7 @@ describe("ProfileValidator", () => { }, } const profile: ProviderSettings = { - apiProvider: "requesty", + apiProvider: providerIdentifiers.requesty, requestyModelId: "requesty-model", } @@ -361,7 +361,7 @@ describe("ProfileValidator", () => { providers: {}, } const profile: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, openAiModelId: "gpt-4", } diff --git a/src/shared/__tests__/api.spec.ts b/src/shared/__tests__/api.spec.ts index 0363c27cdf..0f8b16f6cf 100644 --- a/src/shared/__tests__/api.spec.ts +++ b/src/shared/__tests__/api.spec.ts @@ -1,6 +1,7 @@ import { type ModelInfo, type ProviderSettings, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types" import { getModelMaxOutputTokens, shouldUseReasoningBudget, shouldUseReasoningEffort } from "../api" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" describe("getModelMaxOutputTokens", () => { const mockModel: ModelInfo = { @@ -11,7 +12,7 @@ describe("getModelMaxOutputTokens", () => { test("should return model maxTokens when maxTokens is within 20% of context window", () => { const settings: ProviderSettings = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, } // mockModel has maxTokens: 8192 and contextWindow: 200000 @@ -33,7 +34,7 @@ describe("getModelMaxOutputTokens", () => { } const settings: ProviderSettings = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, enableReasoningEffort: true, modelMaxTokens: 32000, } @@ -72,7 +73,7 @@ describe("getModelMaxOutputTokens", () => { } const settings: ProviderSettings = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, enableReasoningEffort: false, // Not using reasoning } @@ -93,7 +94,7 @@ describe("getModelMaxOutputTokens", () => { getModelMaxOutputTokens({ modelId: "claude-opus-4-7", model, - settings: { apiProvider: "anthropic", enableReasoningEffort: false }, + settings: { apiProvider: providerIdentifiers.anthropic, enableReasoningEffort: false }, }), ).toBe(ANTHROPIC_DEFAULT_MAX_TOKENS) @@ -101,7 +102,11 @@ describe("getModelMaxOutputTokens", () => { getModelMaxOutputTokens({ modelId: "claude-opus-4-7", model, - settings: { apiProvider: "anthropic", enableReasoningEffort: true, modelMaxTokens: 32_768 }, + settings: { + apiProvider: providerIdentifiers.anthropic, + enableReasoningEffort: true, + modelMaxTokens: 32_768, + }, }), ).toBe(32_768) }) @@ -122,7 +127,7 @@ describe("getModelMaxOutputTokens", () => { getModelMaxOutputTokens({ modelId: "claude-opus-4-8", model, - settings: { apiProvider: "anthropic", enableReasoningEffort: false }, + settings: { apiProvider: providerIdentifiers.anthropic, enableReasoningEffort: false }, }), ).toBe(ANTHROPIC_DEFAULT_MAX_TOKENS) @@ -130,7 +135,11 @@ describe("getModelMaxOutputTokens", () => { getModelMaxOutputTokens({ modelId: "claude-opus-4-8", model, - settings: { apiProvider: "anthropic", enableReasoningEffort: true, modelMaxTokens: 32_768 }, + settings: { + apiProvider: providerIdentifiers.anthropic, + enableReasoningEffort: true, + modelMaxTokens: 32_768, + }, }), ).toBe(32_768) }) @@ -149,7 +158,7 @@ describe("getModelMaxOutputTokens", () => { getModelMaxOutputTokens({ modelId: "claude-fable-5", model, - settings: { apiProvider: "anthropic", enableReasoningEffort: false }, + settings: { apiProvider: providerIdentifiers.anthropic, enableReasoningEffort: false }, }), ).toBe(ANTHROPIC_DEFAULT_MAX_TOKENS) @@ -157,7 +166,11 @@ describe("getModelMaxOutputTokens", () => { getModelMaxOutputTokens({ modelId: "claude-fable-5", model, - settings: { apiProvider: "anthropic", enableReasoningEffort: true, modelMaxTokens: 32_768 }, + settings: { + apiProvider: providerIdentifiers.anthropic, + enableReasoningEffort: true, + modelMaxTokens: 32_768, + }, }), ).toBe(32_768) }) @@ -176,7 +189,7 @@ describe("getModelMaxOutputTokens", () => { getModelMaxOutputTokens({ modelId: "claude-sonnet-5", model, - settings: { apiProvider: "anthropic", enableReasoningEffort: false }, + settings: { apiProvider: providerIdentifiers.anthropic, enableReasoningEffort: false }, }), ).toBe(ANTHROPIC_DEFAULT_MAX_TOKENS) @@ -184,7 +197,11 @@ describe("getModelMaxOutputTokens", () => { getModelMaxOutputTokens({ modelId: "claude-sonnet-5", model, - settings: { apiProvider: "anthropic", enableReasoningEffort: true, modelMaxTokens: 32_768 }, + settings: { + apiProvider: providerIdentifiers.anthropic, + enableReasoningEffort: true, + modelMaxTokens: 32_768, + }, }), ).toBe(32_768) }) @@ -203,7 +220,7 @@ describe("getModelMaxOutputTokens", () => { getModelMaxOutputTokens({ modelId: "claude-opus-5", model, - settings: { apiProvider: "anthropic", enableReasoningEffort: false }, + settings: { apiProvider: providerIdentifiers.anthropic, enableReasoningEffort: false }, }), ).toBe(ANTHROPIC_DEFAULT_MAX_TOKENS) @@ -211,7 +228,11 @@ describe("getModelMaxOutputTokens", () => { getModelMaxOutputTokens({ modelId: "claude-opus-5", model, - settings: { apiProvider: "anthropic", enableReasoningEffort: true, modelMaxTokens: 32_768 }, + settings: { + apiProvider: providerIdentifiers.anthropic, + enableReasoningEffort: true, + modelMaxTokens: 32_768, + }, }), ).toBe(32_768) }) @@ -226,7 +247,7 @@ describe("getModelMaxOutputTokens", () => { } const settings: ProviderSettings = { - apiProvider: "gemini", + apiProvider: providerIdentifiers.gemini, enableReasoningEffort: false, // Not using reasoning } @@ -242,7 +263,7 @@ describe("getModelMaxOutputTokens", () => { } const settings: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, } const result = getModelMaxOutputTokens({ @@ -263,7 +284,7 @@ describe("getModelMaxOutputTokens", () => { } const settings: ProviderSettings = { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, } const result = getModelMaxOutputTokens({ @@ -283,7 +304,7 @@ describe("getModelMaxOutputTokens", () => { } const settings: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, } const result = getModelMaxOutputTokens({ @@ -303,7 +324,7 @@ describe("getModelMaxOutputTokens", () => { } const settings: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, } // Test various GPT-5 model IDs @@ -330,7 +351,7 @@ describe("getModelMaxOutputTokens", () => { } const settings: ProviderSettings = { - apiProvider: "zai", + apiProvider: providerIdentifiers.zai, modelMaxTokens: 64_000, // user override, above 20% of the context window (40k) } @@ -348,7 +369,7 @@ describe("getModelMaxOutputTokens", () => { } const settings: ProviderSettings = { - apiProvider: "zai", + apiProvider: providerIdentifiers.zai, modelMaxTokens: 999_999, // beyond the model ceiling } @@ -364,7 +385,7 @@ describe("getModelMaxOutputTokens", () => { } const settings: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, } // Test non-GPT-5 model IDs @@ -411,7 +432,7 @@ describe("getModelMaxOutputTokens", () => { const result = getModelMaxOutputTokens({ modelId: "gpt-5-turbo", model, - settings: { apiProvider: "openai" }, + settings: { apiProvider: providerIdentifiers.openai }, format: "openai", }) @@ -430,7 +451,7 @@ describe("getModelMaxOutputTokens", () => { const result = getModelMaxOutputTokens({ modelId: "glm-5.1", model, - settings: { apiProvider: "zai" }, + settings: { apiProvider: providerIdentifiers.zai }, format: "openai", }) @@ -447,7 +468,7 @@ describe("getModelMaxOutputTokens", () => { const result = getModelMaxOutputTokens({ modelId: "glm-5.1", model, - settings: { apiProvider: "openai" }, + settings: { apiProvider: providerIdentifiers.openai }, format: "openai", }) diff --git a/src/shared/__tests__/checkExistApiConfig.spec.ts b/src/shared/__tests__/checkExistApiConfig.spec.ts index ab92beea36..570e5c8223 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 { providerIdentifiers, type ProviderSettings } from "@roo-code/types" +import { providerIdentifiers, retiredProviderIdentifiers, type ProviderSettings } from "@roo-code/types" import { checkExistKey } from "../checkExistApiConfig" @@ -61,7 +61,7 @@ describe("checkExistKey", () => { it("should return true for fake-ai provider without API key", () => { const config: ProviderSettings = { - apiProvider: "fake-ai", + apiProvider: providerIdentifiers.fakeAi, } expect(checkExistKey(config)).toBe(true) }) @@ -86,14 +86,14 @@ describe("checkExistKey", () => { it("should return false for roo provider without API key", () => { const config: ProviderSettings = { - apiProvider: "roo", + apiProvider: retiredProviderIdentifiers.roo, } expect(checkExistKey(config)).toBe(false) }) it("should return true for kimi-code provider with OAuth auth method", () => { const config: ProviderSettings = { - apiProvider: "kimi-code", + apiProvider: providerIdentifiers.kimiCode, kimiCodeAuthMethod: "oauth", } expect(checkExistKey(config)).toBe(true) @@ -105,14 +105,14 @@ describe("checkExistKey", () => { it("should return true for kimi-code provider without auth method (defaults to OAuth)", () => { const config: ProviderSettings = { - apiProvider: "kimi-code", + apiProvider: providerIdentifiers.kimiCode, } expect(checkExistKey(config)).toBe(true) }) it("should return true for kimi-code provider with api-key auth and key present", () => { const config: ProviderSettings = { - apiProvider: "kimi-code", + apiProvider: providerIdentifiers.kimiCode, kimiCodeAuthMethod: "api-key", kimiCodeApiKey: "test-key", } @@ -121,7 +121,7 @@ describe("checkExistKey", () => { it("should return false for kimi-code provider with api-key auth but no key", () => { const config: ProviderSettings = { - apiProvider: "kimi-code", + apiProvider: providerIdentifiers.kimiCode, kimiCodeAuthMethod: "api-key", } expect(checkExistKey(config)).toBe(false) @@ -129,7 +129,7 @@ describe("checkExistKey", () => { it("should return false for zoo-gateway without session token or auth", () => { const config: ProviderSettings = { - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, zooGatewayModelId: "alibaba/qwen-3.6-max-preview", } expect(checkExistKey(config)).toBe(false) @@ -142,7 +142,7 @@ describe("checkExistKey", () => { it("should return true for zoo-gateway when profile has zooSessionToken", () => { const config: ProviderSettings = { - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, zooSessionToken: "zoo_ext_test_token", } expect(checkExistKey(config)).toBe(true) @@ -150,7 +150,7 @@ describe("checkExistKey", () => { it("should return true for zoo-gateway when Zoo Code session auth is active", () => { const config: ProviderSettings = { - apiProvider: "zoo-gateway", + apiProvider: providerIdentifiers.zooGateway, zooGatewayModelId: "alibaba/qwen-3.6-max-preview", } expect(checkExistKey(config, true)).toBe(true) @@ -158,7 +158,7 @@ describe("checkExistKey", () => { it("should ignore zooCodeIsAuthenticated for non-zoo-gateway providers", () => { const config: ProviderSettings = { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, } expect(checkExistKey(config, true)).toBe(false) }) diff --git a/src/shared/embeddingModels.ts b/src/shared/embeddingModels.ts index a89e2c9488..3c59b681d2 100644 --- a/src/shared/embeddingModels.ts +++ b/src/shared/embeddingModels.ts @@ -3,6 +3,7 @@ */ import type { EmbedderProvider, EmbeddingModelProfiles } from "@roo-code/types" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Example profiles - expand this list as needed export const EMBEDDING_MODEL_PROFILES: EmbeddingModelProfiles = { @@ -157,11 +158,11 @@ export function getModelQueryPrefix(provider: EmbedderProvider, modelId: string) */ export function getDefaultModelId(provider: EmbedderProvider): string { switch (provider) { - case "openai": + case providerIdentifiers.openai: case "openai-compatible": return "text-embedding-3-small" - case "ollama": { + case providerIdentifiers.ollama: { // Choose a sensible default for Ollama, e.g., the first one listed or a specific one const ollamaModels = EMBEDDING_MODEL_PROFILES.ollama const defaultOllamaModel = ollamaModels && Object.keys(ollamaModels)[0] @@ -174,18 +175,18 @@ export function getDefaultModelId(provider: EmbedderProvider): string { return "unknown-default" // Placeholder specific model ID } - case "gemini": + case providerIdentifiers.gemini: return "gemini-embedding-001" - case "mistral": + case providerIdentifiers.mistral: return "codestral-embed-2505" - case "vercel-ai-gateway": + case providerIdentifiers.vercelAiGateway: return "openai/text-embedding-3-large" - case "bedrock": + case providerIdentifiers.bedrock: return "amazon.titan-embed-text-v2:0" - case "openrouter": + case providerIdentifiers.openrouter: return "openai/text-embedding-3-large" case "semble": diff --git a/src/utils/__tests__/autoImportSettings.spec.ts b/src/utils/__tests__/autoImportSettings.spec.ts index 80347cae2b..010cb273b6 100644 --- a/src/utils/__tests__/autoImportSettings.spec.ts +++ b/src/utils/__tests__/autoImportSettings.spec.ts @@ -77,6 +77,7 @@ import { autoImportSettings } from "../autoImportSettings" import * as vscode from "vscode" import fsPromises from "fs/promises" import { fileExistsAtPath } from "../fs" +import { providerIdentifiers, retiredProviderIdentifiers } from "@roo-code/types/provider-identifiers" describe("autoImportSettings", () => { let mockProviderSettingsManager: any @@ -193,7 +194,7 @@ describe("autoImportSettings", () => { currentApiConfigName: "test-config", apiConfigs: { "test-config": { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, anthropicApiKey: "test-key", }, }, @@ -235,13 +236,13 @@ describe("autoImportSettings", () => { currentApiConfigName: "test-config", apiConfigs: { "test-config": { - apiProvider: "anthropic", + apiProvider: providerIdentifiers.anthropic, anthropicApiKey: "test-key", }, }, }, globalSettings: { - imageGenerationProvider: "roo", + imageGenerationProvider: retiredProviderIdentifiers.roo, customInstructions: "Test instructions", }, } diff --git a/src/utils/__tests__/enhance-prompt.spec.ts b/src/utils/__tests__/enhance-prompt.spec.ts index 7e8c702984..755e9df7d7 100644 --- a/src/utils/__tests__/enhance-prompt.spec.ts +++ b/src/utils/__tests__/enhance-prompt.spec.ts @@ -5,6 +5,7 @@ import type { ProviderSettings } from "@roo-code/types" import { singleCompletionHandler } from "../single-completion-handler" import { buildApiHandler, SingleCompletionHandler } from "../../api" import { supportPrompt } from "../../shared/support-prompt" +import { providerIdentifiers } from "@roo-code/types/provider-identifiers" // Mock the API handler vi.mock("../../api", () => ({ @@ -13,7 +14,7 @@ vi.mock("../../api", () => ({ describe("enhancePrompt", () => { const mockApiConfig: ProviderSettings = { - apiProvider: "openai", + apiProvider: providerIdentifiers.openai, openAiApiKey: "test-key", openAiBaseUrl: "https://api.openai.com/v1", enableReasoningEffort: false, @@ -98,7 +99,7 @@ describe("enhancePrompt", () => { it("uses appropriate model based on provider", async () => { const openRouterConfig: ProviderSettings = { - apiProvider: "openrouter", + apiProvider: providerIdentifiers.openrouter, openRouterApiKey: "test-key", openRouterModelId: "test-model", enableReasoningEffort: false, diff --git a/src/vitest.config.ts b/src/vitest.config.ts index c0c8310e24..42986c454c 100644 --- a/src/vitest.config.ts +++ b/src/vitest.config.ts @@ -19,7 +19,7 @@ export default defineConfig({ coverage: { provider: "v8", reporter: ["text", "lcov"], - include: ["src/**/*.ts", "src/**/*.tsx"], + include: ["src/**/*.ts", "src/**/*.tsx", "eslint-rules/**/*.mjs"], exclude: [ "**/*.test.ts", "**/*.test.tsx",