diff --git a/apps/cli/src/lib/utils/__tests__/context-window.test.ts b/apps/cli/src/lib/utils/__tests__/context-window.test.ts index 49f7b10832..0fd5f8c209 100644 --- a/apps/cli/src/lib/utils/__tests__/context-window.test.ts +++ b/apps/cli/src/lib/utils/__tests__/context-window.test.ts @@ -1,4 +1,4 @@ -import { providerIdentifiers, type ProviderSettings } from "@roo-code/types" +import { providerIdentifiers, retiredProviderIdentifiers, type ProviderSettings } from "@roo-code/types" import { DEFAULT_CONTEXT_WINDOW, getContextWindow } from "../context-window.js" @@ -24,6 +24,60 @@ describe("getContextWindow", () => { expect(getContextWindow(routerModels, config)).toBe(123_456) }) + it.each([ + providerIdentifiers.anthropic, + providerIdentifiers.bedrock, + providerIdentifiers.deepseek, + providerIdentifiers.fireworks, + providerIdentifiers.gemini, + providerIdentifiers.mistral, + providerIdentifiers.openaiNative, + providerIdentifiers.vertex, + providerIdentifiers.xai, + ] as const)("uses the shared apiModelId field for %s", (provider) => { + const config: ProviderSettings = { apiProvider: provider, apiModelId: "selected-model" } + const routerModels = { [provider]: { "selected-model": { contextWindow: 64_000 } } } + + expect(getContextWindow(routerModels, config)).toBe(64_000) + }) + + it("ignores another provider's model field", () => { + const config: ProviderSettings = { + apiProvider: providerIdentifiers.ollama, + openRouterModelId: "openrouter-model", + ollamaModelId: "ollama-model", + } + const routerModels = { + [providerIdentifiers.ollama]: { "ollama-model": { contextWindow: 32_000 } }, + } + + expect(getContextWindow(routerModels, config)).toBe(32_000) + }) + + describe("VS Code LM", () => { + const provider = providerIdentifiers.vscodeLm + const routerModels = { [provider]: { "selected-model": { contextWindow: 123_456 } } } + + it("takes the model from the saved selector", () => { + const config: ProviderSettings = { + apiProvider: provider, + vsCodeLmModelSelector: { vendor: "copilot", id: "selected-model" }, + } + + expect(getContextWindow(routerModels, config)).toBe(123_456) + }) + + it("ignores apiModelId, which the selector replaces", () => { + const config: ProviderSettings = { + apiProvider: provider, + apiModelId: "other-model", + vsCodeLmModelSelector: { vendor: "copilot", id: "selected-model" }, + } + + expect(getContextWindow(routerModels, config)).toBe(123_456) + }) + }) + describe("GitHub Copilot", () => { const provider = providerIdentifiers.githubCopilot const routerModels = { [provider]: { "selected-model": { contextWindow: 123_456 } } } @@ -66,19 +120,41 @@ describe("getContextWindow", () => { }) }) - it("uses apiModelId for providers without a specialized model field", () => { + it("returns the default when the selected model is unavailable", () => { + const routerModels = { [providerIdentifiers.openrouter]: { "other-model": { contextWindow: 1_000 } } } + const config: ProviderSettings = { + apiProvider: providerIdentifiers.openrouter, + openRouterModelId: "missing-model", + } + + expect(getContextWindow(routerModels, config)).toBe(DEFAULT_CONTEXT_WINDOW) + }) + + it("returns the default when no provider is selected", () => { + expect(getContextWindow({}, {})).toBe(DEFAULT_CONTEXT_WINDOW) + }) + + it("returns the default when router models are unavailable for a retired provider", () => { const config: ProviderSettings = { - apiProvider: providerIdentifiers.anthropic, + apiProvider: retiredProviderIdentifiers.groq, apiModelId: "selected-model", } - const routerModels = { - [providerIdentifiers.anthropic]: { "selected-model": { contextWindow: 64_000 } }, + + expect(getContextWindow({}, config)).toBe(DEFAULT_CONTEXT_WINDOW) + }) + + it("resolves the model for a retired provider instead of throwing", () => { + const config: ProviderSettings = { + apiProvider: retiredProviderIdentifiers.groq, + apiModelId: "selected-model", } + const routerModels = { [retiredProviderIdentifiers.groq]: { "selected-model": { contextWindow: 48_000 } } } - expect(getContextWindow(routerModels, config)).toBe(64_000) + expect(getContextWindow(routerModels, config)).toBe(48_000) }) - it("returns the default when the selected model is unavailable", () => { - expect(getContextWindow({}, { apiProvider: providerIdentifiers.openrouter })).toBe(DEFAULT_CONTEXT_WINDOW) + it("returns the default when router models or configuration is missing", () => { + expect(getContextWindow(null, { apiProvider: providerIdentifiers.anthropic })).toBe(DEFAULT_CONTEXT_WINDOW) + expect(getContextWindow({}, null)).toBe(DEFAULT_CONTEXT_WINDOW) }) }) diff --git a/apps/cli/src/lib/utils/context-window.ts b/apps/cli/src/lib/utils/context-window.ts index 811c402a02..0e00db1047 100644 --- a/apps/cli/src/lib/utils/context-window.ts +++ b/apps/cli/src/lib/utils/context-window.ts @@ -1,4 +1,4 @@ -import { providerIdentifiers, retiredProviderIdentifiers, type ProviderSettings } from "@roo-code/types" +import { getModelId, type ProviderSettings } from "@roo-code/types" import type { RouterModels } from "@/ui/store.js" @@ -17,7 +17,7 @@ export function getContextWindow(routerModels: RouterModels | null, apiConfigura } const provider = apiConfiguration.apiProvider - const modelId = getModelIdForProvider(apiConfiguration) + const modelId = getModelId(apiConfiguration) if (!provider || !modelId) { return DEFAULT_CONTEXT_WINDOW @@ -29,75 +29,4 @@ export function getContextWindow(routerModels: RouterModels | null, apiConfigura return modelInfo?.contextWindow ?? DEFAULT_CONTEXT_WINDOW } -/** - * Gets the model ID from the API configuration based on the provider type. - * - * Different providers store their model ID in different fields of ProviderSettings. - */ -function getModelIdForProvider(config: ProviderSettings): string | undefined { - switch (config.apiProvider) { - case providerIdentifiers.openrouter: - return config.openRouterModelId - case providerIdentifiers.ollama: - return config.ollamaModelId - case providerIdentifiers.lmstudio: - return config.lmStudioModelId - case providerIdentifiers.openai: - return config.openAiModelId - case providerIdentifiers.requesty: - return config.requestyModelId - case providerIdentifiers.unbound: - return config.unboundModelId - case providerIdentifiers.litellm: - return config.litellmModelId - case providerIdentifiers.ioIntelligence: - return config.ioIntelligenceModelId - case providerIdentifiers.vercelAiGateway: - return config.vercelAiGatewayModelId - case providerIdentifiers.opencodeGo: - return config.opencodeGoModelId - case providerIdentifiers.kenari: - return config.kenariModelId - case providerIdentifiers.nanogpt: - return config.nanoGptModelId - case providerIdentifiers.zooGateway: - return config.zooGatewayModelId - case providerIdentifiers.githubCopilot: - return config.vsCodeLmModelSelector?.id - case providerIdentifiers.anthropic: - case providerIdentifiers.bedrock: - case providerIdentifiers.baseten: - case providerIdentifiers.deepseek: - case providerIdentifiers.fireworks: - case providerIdentifiers.friendli: - case providerIdentifiers.gemini: - case providerIdentifiers.geminiCli: - case providerIdentifiers.mistral: - case providerIdentifiers.moonshot: - case providerIdentifiers.kimiCode: - case providerIdentifiers.minimax: - case providerIdentifiers.mimo: - case providerIdentifiers.openaiCodex: - case providerIdentifiers.openaiNative: - case providerIdentifiers.poe: - case providerIdentifiers.qwenCode: - case providerIdentifiers.sambanova: - case providerIdentifiers.vertex: - case providerIdentifiers.xai: - case providerIdentifiers.zai: - case retiredProviderIdentifiers.cerebras: - case retiredProviderIdentifiers.chutes: - case retiredProviderIdentifiers.deepinfra: - case retiredProviderIdentifiers.doubao: - case retiredProviderIdentifiers.featherless: - case retiredProviderIdentifiers.groq: - case retiredProviderIdentifiers.huggingface: - case retiredProviderIdentifiers.roo: - case providerIdentifiers.vscodeLm: - case providerIdentifiers.fakeAi: - case undefined: - return config.apiModelId - } -} - export { DEFAULT_CONTEXT_WINDOW }