diff --git a/packages/types/src/__tests__/provider-identifiers.test.ts b/packages/types/src/__tests__/provider-identifiers.test.ts new file mode 100644 index 0000000000..477f26dc17 --- /dev/null +++ b/packages/types/src/__tests__/provider-identifiers.test.ts @@ -0,0 +1,123 @@ +import { + isProviderName, + isRetiredProvider, + providerIdentifiers, + providerNames, + providerNamesSchema, + providerNamesWithRetiredSchema, + retiredProviderIdentifiers, + retiredProviderNames, + retiredProviderNamesSchema, +} from "../index.js" + +const expectedProviderIdentifiers = [ + "openrouter", + "vercel-ai-gateway", + "zoo-gateway", + "litellm", + "requesty", + "unbound", + "poe", + "deepseek", + "opencode-go", + "kenari", + "ollama", + "lmstudio", + "vscode-lm", + "openai", + "fake-ai", + "anthropic", + "bedrock", + "baseten", + "fireworks", + "friendli", + "gemini", + "gemini-cli", + "mistral", + "moonshot", + "minimax", + "mimo", + "openai-codex", + "openai-native", + "qwen-code", + "sambanova", + "vertex", + "xai", + "zai", +] + +const expectedRetiredProviderIdentifiers = [ + "cerebras", + "chutes", + "deepinfra", + "doubao", + "featherless", + "groq", + "huggingface", + "io-intelligence", + "roo", +] + +describe("provider identifiers", () => { + it("preserves active provider serialized values", () => { + const identifiers = Object.values(providerIdentifiers) + + expect(identifiers).toEqual(expectedProviderIdentifiers) + expect(new Set(identifiers).size).toBe(identifiers.length) + }) + + it("preserves retired provider serialized values", () => { + const identifiers = Object.values(retiredProviderIdentifiers) + + expect(identifiers).toEqual(expectedRetiredProviderIdentifiers) + expect(new Set(identifiers).size).toBe(identifiers.length) + }) + + it("preserves provider-settings compatibility exports", () => { + const identifiers = Object.values(providerIdentifiers) + const retiredIdentifiers = Object.values(retiredProviderIdentifiers) + + expect(providerNames).toEqual(identifiers) + expect(retiredProviderNames).toEqual(retiredIdentifiers) + }) + + it("keeps active and retired providers separate", () => { + const activeIdentifiers = new Set(Object.values(providerIdentifiers)) + const retiredIdentifiers = Object.values(retiredProviderIdentifiers) + + expect(retiredIdentifiers.every((identifier) => !activeIdentifiers.has(identifier))).toBe(true) + }) + + it("derives runtime validation from the canonical registries", () => { + for (const identifier of expectedProviderIdentifiers) { + expect(providerNamesSchema.safeParse(identifier).success).toBe(true) + expect(providerNamesWithRetiredSchema.safeParse(identifier).success).toBe(true) + } + + for (const identifier of expectedRetiredProviderIdentifiers) { + expect(providerNamesSchema.safeParse(identifier).success).toBe(false) + expect(retiredProviderNamesSchema.safeParse(identifier).success).toBe(true) + expect(providerNamesWithRetiredSchema.safeParse(identifier).success).toBe(true) + } + + expect(providerNamesSchema.safeParse("unknown-provider").success).toBe(false) + expect(retiredProviderNamesSchema.safeParse("unknown-provider").success).toBe(false) + expect(providerNamesWithRetiredSchema.safeParse("unknown-provider").success).toBe(false) + }) + + it("preserves provider-settings type guards", () => { + for (const identifier of expectedProviderIdentifiers) { + expect(isProviderName(identifier)).toBe(true) + expect(isRetiredProvider(identifier)).toBe(false) + } + + for (const identifier of expectedRetiredProviderIdentifiers) { + expect(isProviderName(identifier)).toBe(false) + expect(isRetiredProvider(identifier)).toBe(true) + } + + expect(isProviderName("unknown-provider")).toBe(false) + expect(isProviderName(undefined)).toBe(false) + expect(isRetiredProvider("unknown-provider")).toBe(false) + }) +}) diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index eaa419b1af..82588ae537 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -18,6 +18,7 @@ export * from "./mcp.js" export * from "./message.js" export * from "./mode.js" export * from "./model.js" +export * from "./provider-identifiers.js" export * from "./provider-settings.js" export * from "./task.js" export * from "./todo.js" diff --git a/packages/types/src/provider-identifiers.ts b/packages/types/src/provider-identifiers.ts new file mode 100644 index 0000000000..30bfb9d17f --- /dev/null +++ b/packages/types/src/provider-identifiers.ts @@ -0,0 +1,57 @@ +/** + * Canonical provider identifiers. + * + * Values in this registry are persisted in settings and must remain stable. + */ +export const providerIdentifiers = { + openrouter: "openrouter", + vercelAiGateway: "vercel-ai-gateway", + zooGateway: "zoo-gateway", + litellm: "litellm", + requesty: "requesty", + unbound: "unbound", + poe: "poe", + deepseek: "deepseek", + opencodeGo: "opencode-go", + kenari: "kenari", + ollama: "ollama", + lmstudio: "lmstudio", + vscodeLm: "vscode-lm", + openai: "openai", + fakeAi: "fake-ai", + anthropic: "anthropic", + bedrock: "bedrock", + baseten: "baseten", + fireworks: "fireworks", + friendli: "friendli", + gemini: "gemini", + geminiCli: "gemini-cli", + mistral: "mistral", + moonshot: "moonshot", + minimax: "minimax", + mimo: "mimo", + openaiCodex: "openai-codex", + openaiNative: "openai-native", + qwenCode: "qwen-code", + sambanova: "sambanova", + vertex: "vertex", + xai: "xai", + zai: "zai", +} as const + +export type ProviderIdentifier = (typeof providerIdentifiers)[keyof typeof providerIdentifiers] + +/** Provider identifiers retained only for compatibility with existing settings. */ +export const retiredProviderIdentifiers = { + cerebras: "cerebras", + chutes: "chutes", + deepinfra: "deepinfra", + doubao: "doubao", + featherless: "featherless", + groq: "groq", + huggingface: "huggingface", + ioIntelligence: "io-intelligence", + roo: "roo", +} as const + +export type RetiredProviderIdentifier = (typeof retiredProviderIdentifiers)[keyof typeof retiredProviderIdentifiers] diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index fb03531c33..bde5751e66 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -2,6 +2,12 @@ import { z } from "zod" import { modelInfoSchema, reasoningEffortSettingSchema, verbosityLevelsSchema, serviceTierSchema } from "./model.js" import { codebaseIndexProviderSchema } from "./codebase-index.js" +import { + providerIdentifiers, + retiredProviderIdentifiers, + type ProviderIdentifier, + type RetiredProviderIdentifier, +} from "./provider-identifiers.js" import { anthropicModels, basetenModels, @@ -110,32 +116,7 @@ export const isFauxProvider = (key: string): key is FauxProvider => fauxProvider * ProviderName */ -export const providerNames = [ - ...dynamicProviders, - ...localProviders, - ...internalProviders, - ...customProviders, - ...fauxProviders, - "anthropic", - "bedrock", - "baseten", - "deepseek", - "fireworks", - "friendli", - "gemini", - "gemini-cli", - "mistral", - "moonshot", - "minimax", - "mimo", - "openai-codex", - "openai-native", - "qwen-code", - "sambanova", - "vertex", - "xai", - "zai", -] as const +export const providerNames = Object.values(providerIdentifiers) as [ProviderIdentifier, ...ProviderIdentifier[]] export const providerNamesSchema = z.enum(providerNames) @@ -148,17 +129,10 @@ export const isProviderName = (key: unknown): key is ProviderName => * RetiredProviderName */ -export const retiredProviderNames = [ - "cerebras", - "chutes", - "deepinfra", - "doubao", - "featherless", - "groq", - "huggingface", - "io-intelligence", - "roo", -] as const +export const retiredProviderNames = Object.values(retiredProviderIdentifiers) as [ + RetiredProviderIdentifier, + ...RetiredProviderIdentifier[], +] export const retiredProviderNamesSchema = z.enum(retiredProviderNames)