diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 4dfeacbf07..8434158541 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -27,53 +27,126 @@ import { internationalZAiModels, } from "./providers/index.js" +/** + * constants + */ + +export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3 + +/** + * DynamicProvider + * + * Dynamic provider requires external API calls in order to get the model list. + */ + +export const dynamicProviders = [ + "openrouter", + "vercel-ai-gateway", + "huggingface", + "litellm", + "deepinfra", + "io-intelligence", + "requesty", + "unbound", + "glama", +] as const + +export type DynamicProvider = (typeof dynamicProviders)[number] + +export const isDynamicProvider = (key: string): key is DynamicProvider => + dynamicProviders.includes(key as DynamicProvider) + +/** + * LocalProvider + * + * Local providers require localhost API calls in order to get the model list. + */ + +export const localProviders = ["ollama", "lmstudio"] as const + +export type LocalProvider = (typeof localProviders)[number] + +export const isLocalProvider = (key: string): key is LocalProvider => localProviders.includes(key as LocalProvider) + +/** + * InternalProvider + * + * Internal providers require internal VSCode API calls in order to get the + * model list. + */ + +export const internalProviders = ["vscode-lm"] as const + +export type InternalProvider = (typeof internalProviders)[number] + +export const isInternalProvider = (key: string): key is InternalProvider => + internalProviders.includes(key as InternalProvider) + +/** + * CustomProvider + * + * Custom providers are completely configurable within Roo Code settings. + */ + +export const customProviders = ["openai"] as const + +export type CustomProvider = (typeof customProviders)[number] + +export const isCustomProvider = (key: string): key is CustomProvider => customProviders.includes(key as CustomProvider) + +/** + * FauxProvider + * + * Faux providers do not make external inference calls and therefore do not have + * model lists. + */ + +export const fauxProviders = ["fake-ai", "human-relay"] as const + +export type FauxProvider = (typeof fauxProviders)[number] + +export const isFauxProvider = (key: string): key is FauxProvider => fauxProviders.includes(key as FauxProvider) + /** * ProviderName */ export const providerNames = [ + ...dynamicProviders, + ...localProviders, + ...internalProviders, + ...customProviders, + ...fauxProviders, "anthropic", - "claude-code", - "glama", - "openrouter", "bedrock", - "vertex", - "openai", - "ollama", - "vscode-lm", - "lmstudio", + "cerebras", + "chutes", + "claude-code", + "doubao", + "deepseek", + "featherless", + "fireworks", "gemini", "gemini-cli", - "openai-native", + "groq", "mistral", "moonshot", - "deepseek", - "deepinfra", - "doubao", + "openai-native", "qwen-code", - "unbound", - "requesty", - "human-relay", - "fake-ai", - "xai", - "groq", - "chutes", - "litellm", - "huggingface", - "cerebras", + "roo", "sambanova", + "vertex", + "xai", "zai", - "fireworks", - "featherless", - "io-intelligence", - "roo", - "vercel-ai-gateway", ] as const export const providerNamesSchema = z.enum(providerNames) export type ProviderName = z.infer +export const isProviderName = (key: unknown): key is ProviderName => + typeof key === "string" && providerNames.includes(key as ProviderName) + /** * ProviderSettingsEntry */ @@ -91,11 +164,6 @@ export type ProviderSettingsEntry = z.infer * ProviderSettings */ -/** - * Default value for consecutive mistake limit - */ -export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3 - const baseProviderSettingsSchema = z.object({ includeMaxTokens: z.boolean().optional(), diffEnabled: z.boolean().optional(), @@ -124,7 +192,7 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({ apiKey: z.string().optional(), anthropicBaseUrl: z.string().optional(), anthropicUseAuthToken: z.boolean().optional(), - anthropicBeta1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window + anthropicBeta1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window. }) const claudeCodeSchema = apiModelIdProviderModelSchema.extend({ @@ -160,7 +228,7 @@ const bedrockSchema = apiModelIdProviderModelSchema.extend({ awsModelContextWindow: z.number().optional(), awsBedrockEndpointEnabled: z.boolean().optional(), awsBedrockEndpoint: z.string().optional(), - awsBedrock1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window + awsBedrock1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window. }) const vertexSchema = apiModelIdProviderModelSchema.extend({ @@ -190,6 +258,7 @@ const ollamaSchema = baseProviderSettingsSchema.extend({ ollamaModelId: z.string().optional(), ollamaBaseUrl: z.string().optional(), ollamaApiKey: z.string().optional(), + ollamaNumCtx: z.number().int().min(128).optional(), }) const vsCodeLmSchema = baseProviderSettingsSchema.extend({ @@ -308,9 +377,13 @@ const sambaNovaSchema = apiModelIdProviderModelSchema.extend({ sambaNovaApiKey: z.string().optional(), }) +export const zaiApiLineSchema = z.enum(["international_coding", "international", "china_coding", "china"]) + +export type ZaiApiLine = z.infer + const zaiSchema = apiModelIdProviderModelSchema.extend({ zaiApiKey: z.string().optional(), - zaiApiLine: z.union([z.literal("china"), z.literal("international")]).optional(), + zaiApiLine: zaiApiLineSchema.optional(), }) const fireworksSchema = apiModelIdProviderModelSchema.extend({ @@ -331,7 +404,7 @@ const qwenCodeSchema = apiModelIdProviderModelSchema.extend({ }) const rooSchema = apiModelIdProviderModelSchema.extend({ - // No additional fields needed - uses cloud authentication + // No additional fields needed - uses cloud authentication. }) const vercelAiGatewaySchema = baseProviderSettingsSchema.extend({ @@ -436,7 +509,11 @@ export type ProviderSettingsWithId = z.infer[] = [ +/** + * ModelIdKey + */ + +export const modelIdKeys = [ "apiModelId", "glamaModelId", "openRouterModelId", @@ -451,13 +528,63 @@ export const MODEL_ID_KEYS: Partial[] = [ "ioIntelligenceModelId", "vercelAiGatewayModelId", "deepInfraModelId", -] +] as const satisfies readonly (keyof ProviderSettings)[] + +export type ModelIdKey = (typeof modelIdKeys)[number] export const getModelId = (settings: ProviderSettings): string | undefined => { - const modelIdKey = MODEL_ID_KEYS.find((key) => settings[key]) - return modelIdKey ? (settings[modelIdKey] as string) : undefined + const modelIdKey = modelIdKeys.find((key) => settings[key]) + return modelIdKey ? settings[modelIdKey] : undefined +} + +/** + * TypicalProvider + */ + +export type TypicalProvider = Exclude + +export const isTypicalProvider = (key: unknown): key is TypicalProvider => + isProviderName(key) && !isInternalProvider(key) && !isCustomProvider(key) && !isFauxProvider(key) + +export const modelIdKeysByProvider: Record = { + anthropic: "apiModelId", + "claude-code": "apiModelId", + glama: "glamaModelId", + openrouter: "openRouterModelId", + bedrock: "apiModelId", + vertex: "apiModelId", + "openai-native": "openAiModelId", + ollama: "ollamaModelId", + lmstudio: "lmStudioModelId", + gemini: "apiModelId", + "gemini-cli": "apiModelId", + mistral: "apiModelId", + moonshot: "apiModelId", + deepseek: "apiModelId", + deepinfra: "deepInfraModelId", + doubao: "apiModelId", + "qwen-code": "apiModelId", + unbound: "unboundModelId", + requesty: "requestyModelId", + xai: "apiModelId", + groq: "apiModelId", + chutes: "apiModelId", + litellm: "litellmModelId", + huggingface: "huggingFaceModelId", + cerebras: "apiModelId", + sambanova: "apiModelId", + zai: "apiModelId", + fireworks: "apiModelId", + featherless: "apiModelId", + "io-intelligence": "ioIntelligenceModelId", + roo: "apiModelId", + "vercel-ai-gateway": "vercelAiGatewayModelId", } +/** + * ANTHROPIC_STYLE_PROVIDERS + */ + // Providers that use Anthropic-style API protocol. export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code", "bedrock"] @@ -478,6 +605,10 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str return "openai" } +/** + * MODELS_BY_PROVIDER + */ + export const MODELS_BY_PROVIDER: Record< Exclude, { id: ProviderName; label: string; models: string[] } @@ -575,19 +706,3 @@ export const MODELS_BY_PROVIDER: Record< deepinfra: { id: "deepinfra", label: "DeepInfra", models: [] }, "vercel-ai-gateway": { id: "vercel-ai-gateway", label: "Vercel AI Gateway", models: [] }, } - -export const dynamicProviders = [ - "glama", - "huggingface", - "litellm", - "openrouter", - "requesty", - "unbound", - "deepinfra", - "vercel-ai-gateway", -] as const satisfies readonly ProviderName[] - -export type DynamicProvider = (typeof dynamicProviders)[number] - -export const isDynamicProvider = (key: string): key is DynamicProvider => - dynamicProviders.includes(key as DynamicProvider) diff --git a/packages/types/src/providers/anthropic.ts b/packages/types/src/providers/anthropic.ts index 2cb38537a4..1c0977bd06 100644 --- a/packages/types/src/providers/anthropic.ts +++ b/packages/types/src/providers/anthropic.ts @@ -6,6 +6,28 @@ export type AnthropicModelId = keyof typeof anthropicModels export const anthropicDefaultModelId: AnthropicModelId = "claude-sonnet-4-20250514" export const anthropicModels = { + "claude-4.5-sonnet": { + maxTokens: 64_000, // Overridden to 8k if `enableReasoningEffort` is false. + contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07' + supportsImages: true, + supportsComputerUse: true, + supportsPromptCache: true, + inputPrice: 3.0, // $3 per million input tokens (≤200K context) + outputPrice: 15.0, // $15 per million output tokens (≤200K context) + cacheWritesPrice: 3.75, // $3.75 per million tokens + cacheReadsPrice: 0.3, // $0.30 per million tokens + supportsReasoningBudget: true, + // Tiered pricing for extended context (requires beta flag 'context-1m-2025-08-07') + tiers: [ + { + contextWindow: 1_000_000, // 1M tokens with beta flag + inputPrice: 6.0, // $6 per million input tokens (>200K context) + outputPrice: 22.5, // $22.50 per million output tokens (>200K context) + cacheWritesPrice: 7.5, // $7.50 per million tokens (>200K context) + cacheReadsPrice: 0.6, // $0.60 per million tokens (>200K context) + }, + ], + }, "claude-sonnet-4-20250514": { maxTokens: 64_000, // Overridden to 8k if `enableReasoningEffort` is false. contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07' diff --git a/packages/types/src/providers/bedrock.ts b/packages/types/src/providers/bedrock.ts index 67215e7796..fb5072e60c 100644 --- a/packages/types/src/providers/bedrock.ts +++ b/packages/types/src/providers/bedrock.ts @@ -67,6 +67,21 @@ export const bedrockModels = { maxCachePoints: 1, cachableFields: ["system"], }, + "anthropic.claude-4.5-sonnet-v1:0": { + maxTokens: 8192, + contextWindow: 200_000, + supportsImages: true, + supportsComputerUse: true, + supportsPromptCache: true, + supportsReasoningBudget: true, + inputPrice: 3.0, + outputPrice: 15.0, + cacheWritesPrice: 3.75, + cacheReadsPrice: 0.3, + minTokensPerCachePoint: 1024, + maxCachePoints: 4, + cachableFields: ["system", "messages", "tools"], + }, "anthropic.claude-sonnet-4-20250514-v1:0": { maxTokens: 8192, contextWindow: 200_000, diff --git a/packages/types/src/providers/claude-code.ts b/packages/types/src/providers/claude-code.ts index d9b658319a..fb44b57658 100644 --- a/packages/types/src/providers/claude-code.ts +++ b/packages/types/src/providers/claude-code.ts @@ -40,6 +40,14 @@ export function getClaudeCodeModelId(baseModelId: ClaudeCodeModelId, useVertex = } export const claudeCodeModels = { + "claude-4.5-sonnet": { + ...anthropicModels["claude-4.5-sonnet"], + supportsImages: false, + supportsPromptCache: true, // Claude Code does report cache tokens + supportsReasoningEffort: false, + supportsReasoningBudget: false, + requiredReasoningBudget: false, + }, "claude-sonnet-4-20250514": { ...anthropicModels["claude-sonnet-4-20250514"], supportsImages: false, diff --git a/packages/types/src/providers/vertex.ts b/packages/types/src/providers/vertex.ts index 8010fccf8e..a066a4102d 100644 --- a/packages/types/src/providers/vertex.ts +++ b/packages/types/src/providers/vertex.ts @@ -163,6 +163,18 @@ export const vertexModels = { inputPrice: 1.25, outputPrice: 5, }, + "claude-4.5-sonnet": { + maxTokens: 8192, + contextWindow: 200_000, + supportsImages: true, + supportsComputerUse: true, + supportsPromptCache: true, + inputPrice: 3.0, + outputPrice: 15.0, + cacheWritesPrice: 3.75, + cacheReadsPrice: 0.3, + supportsReasoningBudget: true, + }, "claude-sonnet-4@20250514": { maxTokens: 8192, contextWindow: 200_000, diff --git a/packages/types/src/providers/zai.ts b/packages/types/src/providers/zai.ts index f724744827..b3838c1406 100644 --- a/packages/types/src/providers/zai.ts +++ b/packages/types/src/providers/zai.ts @@ -1,4 +1,5 @@ import type { ModelInfo } from "../model.js" +import { ZaiApiLine } from "../provider-settings.js" // Z AI // https://docs.z.ai/guides/llm/glm-4.5 @@ -103,3 +104,14 @@ export const mainlandZAiModels = { } as const satisfies Record export const ZAI_DEFAULT_TEMPERATURE = 0 + +export const zaiApiLineConfigs = { + international_coding: { + name: "International Coding Plan", + baseUrl: "https://api.z.ai/api/coding/paas/v4", + isChina: false, + }, + international: { name: "International Standard", baseUrl: "https://api.z.ai/api/paas/v4", isChina: false }, + china_coding: { name: "China Coding Plan", baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4", isChina: true }, + china: { name: "China Standard", baseUrl: "https://open.bigmodel.cn/api/paas/v4", isChina: true }, +} satisfies Record diff --git a/src/api/providers/__tests__/anthropic.spec.ts b/src/api/providers/__tests__/anthropic.spec.ts index b1d0a2f6b3..7b74c87dfc 100644 --- a/src/api/providers/__tests__/anthropic.spec.ts +++ b/src/api/providers/__tests__/anthropic.spec.ts @@ -264,5 +264,29 @@ describe("AnthropicHandler", () => { expect(result.reasoningBudget).toBeUndefined() expect(result.temperature).toBe(0) }) + + it("should handle Claude 4.5 Sonnet model correctly", () => { + const handler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-4.5-sonnet", + }) + const model = handler.getModel() + expect(model.id).toBe("claude-4.5-sonnet") + expect(model.info.maxTokens).toBe(64000) + expect(model.info.contextWindow).toBe(200000) + expect(model.info.supportsReasoningBudget).toBe(true) + }) + + it("should enable 1M context for Claude 4.5 Sonnet when beta flag is set", () => { + const handler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-4.5-sonnet", + anthropicBeta1MContext: true, + }) + const model = handler.getModel() + expect(model.info.contextWindow).toBe(1000000) + expect(model.info.inputPrice).toBe(6.0) + expect(model.info.outputPrice).toBe(22.5) + }) }) }) diff --git a/src/api/providers/__tests__/zai.spec.ts b/src/api/providers/__tests__/zai.spec.ts index a16aa9fcdf..7928a4298d 100644 --- a/src/api/providers/__tests__/zai.spec.ts +++ b/src/api/providers/__tests__/zai.spec.ts @@ -41,7 +41,11 @@ describe("ZAiHandler", () => { it("should use the correct international Z AI base URL", () => { new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international" }) - expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.z.ai/api/paas/v4" })) + expect(OpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: "https://api.z.ai/api/paas/v4", + }), + ) }) it("should use the provided API key for international", () => { @@ -109,7 +113,11 @@ describe("ZAiHandler", () => { describe("Default behavior", () => { it("should default to international when no zaiApiLine is specified", () => { const handlerDefault = new ZAiHandler({ zaiApiKey: "test-zai-api-key" }) - expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.z.ai/api/paas/v4" })) + expect(OpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: "https://api.z.ai/api/coding/paas/v4", + }), + ) const model = handlerDefault.getModel() expect(model.id).toBe(internationalZAiDefaultModelId) diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index cb48492b60..80ef572b5b 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -45,12 +45,16 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa const cacheControl: CacheControlEphemeral = { type: "ephemeral" } let { id: modelId, betas = [], maxTokens, temperature, reasoning: thinking } = this.getModel() - // Add 1M context beta flag if enabled for Claude Sonnet 4 - if (modelId === "claude-sonnet-4-20250514" && this.options.anthropicBeta1MContext) { + // Add 1M context beta flag if enabled for Claude Sonnet 4 and 4.5 + if ( + (modelId === "claude-sonnet-4-20250514" || modelId === "claude-4.5-sonnet") && + this.options.anthropicBeta1MContext + ) { betas.push("context-1m-2025-08-07") } switch (modelId) { + case "claude-4.5-sonnet": case "claude-sonnet-4-20250514": case "claude-opus-4-1-20250805": case "claude-opus-4-20250514": @@ -110,6 +114,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa // Then check for models that support prompt caching switch (modelId) { + case "claude-4.5-sonnet": case "claude-sonnet-4-20250514": case "claude-opus-4-1-20250805": case "claude-opus-4-20250514": @@ -243,8 +248,8 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa let id = modelId && modelId in anthropicModels ? (modelId as AnthropicModelId) : anthropicDefaultModelId let info: ModelInfo = anthropicModels[id] - // If 1M context beta is enabled for Claude Sonnet 4, update the model info - if (id === "claude-sonnet-4-20250514" && this.options.anthropicBeta1MContext) { + // If 1M context beta is enabled for Claude Sonnet 4 or 4.5, update the model info + if ((id === "claude-sonnet-4-20250514" || id === "claude-4.5-sonnet") && this.options.anthropicBeta1MContext) { // Use the tier pricing for 1M context const tier = info.tiers?.[0] if (tier) { diff --git a/src/api/providers/zai.ts b/src/api/providers/zai.ts index e37e37f01b..ce5aab9dd9 100644 --- a/src/api/providers/zai.ts +++ b/src/api/providers/zai.ts @@ -6,6 +6,7 @@ import { type InternationalZAiModelId, type MainlandZAiModelId, ZAI_DEFAULT_TEMPERATURE, + zaiApiLineConfigs, } from "@roo-code/types" import type { ApiHandlerOptions } from "../../shared/api" @@ -14,14 +15,14 @@ import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" export class ZAiHandler extends BaseOpenAiCompatibleProvider { constructor(options: ApiHandlerOptions) { - const isChina = options.zaiApiLine === "china" + const isChina = zaiApiLineConfigs[options.zaiApiLine ?? "international_coding"].isChina const models = isChina ? mainlandZAiModels : internationalZAiModels const defaultModelId = isChina ? mainlandZAiDefaultModelId : internationalZAiDefaultModelId super({ ...options, providerName: "Z AI", - baseURL: isChina ? "https://open.bigmodel.cn/api/paas/v4" : "https://api.z.ai/api/paas/v4", + baseURL: zaiApiLineConfigs[options.zaiApiLine ?? "international_coding"].baseUrl, apiKey: options.zaiApiKey ?? "not-provided", defaultProviderModelId: defaultModelId, providerModels: models, diff --git a/webview-ui/src/components/settings/providers/Anthropic.tsx b/webview-ui/src/components/settings/providers/Anthropic.tsx index ede2b90208..224d1965b6 100644 --- a/webview-ui/src/components/settings/providers/Anthropic.tsx +++ b/webview-ui/src/components/settings/providers/Anthropic.tsx @@ -22,7 +22,8 @@ export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: Anthro const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) // Check if the current model supports 1M context beta - const supports1MContextBeta = selectedModel?.id === "claude-sonnet-4-20250514" + const supports1MContextBeta = + selectedModel?.id === "claude-sonnet-4-20250514" || selectedModel?.id === "claude-4.5-sonnet" const handleInputChange = useCallback( ( diff --git a/webview-ui/src/components/settings/providers/ZAi.tsx b/webview-ui/src/components/settings/providers/ZAi.tsx index bc23f28346..c7f44510c1 100644 --- a/webview-ui/src/components/settings/providers/ZAi.tsx +++ b/webview-ui/src/components/settings/providers/ZAi.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react" import { VSCodeTextField, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react" -import type { ProviderSettings } from "@roo-code/types" +import { zaiApiLineConfigs, zaiApiLineSchema, type ProviderSettings } from "@roo-code/types" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" @@ -33,15 +33,17 @@ export const ZAi = ({ apiConfiguration, setApiConfigurationField }: ZAiProps) =>
- - api.z.ai - - - open.bigmodel.cn - + {zaiApiLineSchema.options.map((zaiApiLine) => { + const config = zaiApiLineConfigs[zaiApiLine] + return ( + + {config.name} ({config.baseUrl}) + + ) + })}
{t("settings:providers.zaiEntrypointDescription")} @@ -62,7 +64,7 @@ export const ZAi = ({ apiConfiguration, setApiConfigurationField }: ZAiProps) => {!apiConfiguration?.zaiApiKey && (