Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 84 additions & 8 deletions apps/cli/src/lib/utils/__tests__/context-window.test.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this test catch a regression where getContextWindow falls back to apiModelId when the selector has no id, for example getModelId(config) ?? config.apiModelId? Here the selector id is set, so the fallback never runs. A case with apiModelId set and no selector, expecting DEFAULT_CONTEXT_WINDOW, would pin the behavior this PR changes for vscodeLm.

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 } } }
Expand Down Expand Up @@ -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)
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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)
})
})
75 changes: 2 additions & 73 deletions apps/cli/src/lib/utils/context-window.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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
Expand All @@ -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 }
Loading