provider-settings.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. import { z } from "zod"
  2. import { modelInfoSchema, reasoningEffortWithMinimalSchema, verbosityLevelsSchema } from "./model.js"
  3. import { codebaseIndexProviderSchema } from "./codebase-index.js"
  4. import {
  5. anthropicModels,
  6. bedrockModels,
  7. cerebrasModels,
  8. chutesModels,
  9. claudeCodeModels,
  10. deepSeekModels,
  11. doubaoModels,
  12. featherlessModels,
  13. fireworksModels,
  14. geminiModels,
  15. groqModels,
  16. ioIntelligenceModels,
  17. mistralModels,
  18. moonshotModels,
  19. openAiNativeModels,
  20. qwenCodeModels,
  21. rooModels,
  22. sambaNovaModels,
  23. vertexModels,
  24. vscodeLlmModels,
  25. xaiModels,
  26. internationalZAiModels,
  27. } from "./providers/index.js"
  28. /**
  29. * ProviderName
  30. */
  31. export const providerNames = [
  32. "anthropic",
  33. "claude-code",
  34. "glama",
  35. "openrouter",
  36. "bedrock",
  37. "vertex",
  38. "openai",
  39. "ollama",
  40. "vscode-lm",
  41. "lmstudio",
  42. "gemini",
  43. "gemini-cli",
  44. "openai-native",
  45. "mistral",
  46. "moonshot",
  47. "deepseek",
  48. "doubao",
  49. "qwen-code",
  50. "unbound",
  51. "requesty",
  52. "human-relay",
  53. "fake-ai",
  54. "xai",
  55. "groq",
  56. "chutes",
  57. "litellm",
  58. "huggingface",
  59. "cerebras",
  60. "sambanova",
  61. "zai",
  62. "fireworks",
  63. "featherless",
  64. "io-intelligence",
  65. "roo",
  66. ] as const
  67. export const providerNamesSchema = z.enum(providerNames)
  68. export type ProviderName = z.infer<typeof providerNamesSchema>
  69. /**
  70. * ProviderSettingsEntry
  71. */
  72. export const providerSettingsEntrySchema = z.object({
  73. id: z.string(),
  74. name: z.string(),
  75. apiProvider: providerNamesSchema.optional(),
  76. })
  77. export type ProviderSettingsEntry = z.infer<typeof providerSettingsEntrySchema>
  78. /**
  79. * ProviderSettings
  80. */
  81. /**
  82. * Default value for consecutive mistake limit
  83. */
  84. export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3
  85. const baseProviderSettingsSchema = z.object({
  86. includeMaxTokens: z.boolean().optional(),
  87. diffEnabled: z.boolean().optional(),
  88. todoListEnabled: z.boolean().optional(),
  89. fuzzyMatchThreshold: z.number().optional(),
  90. modelTemperature: z.number().nullish(),
  91. rateLimitSeconds: z.number().optional(),
  92. consecutiveMistakeLimit: z.number().min(0).optional(),
  93. // Model reasoning.
  94. enableReasoningEffort: z.boolean().optional(),
  95. reasoningEffort: reasoningEffortWithMinimalSchema.optional(),
  96. modelMaxTokens: z.number().optional(),
  97. modelMaxThinkingTokens: z.number().optional(),
  98. // Model verbosity.
  99. verbosity: verbosityLevelsSchema.optional(),
  100. })
  101. // Several of the providers share common model config properties.
  102. const apiModelIdProviderModelSchema = baseProviderSettingsSchema.extend({
  103. apiModelId: z.string().optional(),
  104. })
  105. const anthropicSchema = apiModelIdProviderModelSchema.extend({
  106. apiKey: z.string().optional(),
  107. anthropicBaseUrl: z.string().optional(),
  108. anthropicUseAuthToken: z.boolean().optional(),
  109. anthropicBeta1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window
  110. })
  111. const claudeCodeSchema = apiModelIdProviderModelSchema.extend({
  112. claudeCodePath: z.string().optional(),
  113. claudeCodeMaxOutputTokens: z.number().int().min(1).max(200000).optional(),
  114. })
  115. const glamaSchema = baseProviderSettingsSchema.extend({
  116. glamaModelId: z.string().optional(),
  117. glamaApiKey: z.string().optional(),
  118. })
  119. const openRouterSchema = baseProviderSettingsSchema.extend({
  120. openRouterApiKey: z.string().optional(),
  121. openRouterModelId: z.string().optional(),
  122. openRouterBaseUrl: z.string().optional(),
  123. openRouterSpecificProvider: z.string().optional(),
  124. openRouterUseMiddleOutTransform: z.boolean().optional(),
  125. })
  126. const bedrockSchema = apiModelIdProviderModelSchema.extend({
  127. awsAccessKey: z.string().optional(),
  128. awsSecretKey: z.string().optional(),
  129. awsSessionToken: z.string().optional(),
  130. awsRegion: z.string().optional(),
  131. awsUseCrossRegionInference: z.boolean().optional(),
  132. awsUsePromptCache: z.boolean().optional(),
  133. awsProfile: z.string().optional(),
  134. awsUseProfile: z.boolean().optional(),
  135. awsApiKey: z.string().optional(),
  136. awsUseApiKey: z.boolean().optional(),
  137. awsCustomArn: z.string().optional(),
  138. awsModelContextWindow: z.number().optional(),
  139. awsBedrockEndpointEnabled: z.boolean().optional(),
  140. awsBedrockEndpoint: z.string().optional(),
  141. awsBedrock1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window
  142. })
  143. const vertexSchema = apiModelIdProviderModelSchema.extend({
  144. vertexKeyFile: z.string().optional(),
  145. vertexJsonCredentials: z.string().optional(),
  146. vertexProjectId: z.string().optional(),
  147. vertexRegion: z.string().optional(),
  148. enableUrlContext: z.boolean().optional(),
  149. enableGrounding: z.boolean().optional(),
  150. })
  151. const openAiSchema = baseProviderSettingsSchema.extend({
  152. openAiBaseUrl: z.string().optional(),
  153. openAiApiKey: z.string().optional(),
  154. openAiLegacyFormat: z.boolean().optional(),
  155. openAiR1FormatEnabled: z.boolean().optional(),
  156. openAiModelId: z.string().optional(),
  157. openAiCustomModelInfo: modelInfoSchema.nullish(),
  158. openAiUseAzure: z.boolean().optional(),
  159. azureApiVersion: z.string().optional(),
  160. openAiStreamingEnabled: z.boolean().optional(),
  161. openAiHostHeader: z.string().optional(), // Keep temporarily for backward compatibility during migration.
  162. openAiHeaders: z.record(z.string(), z.string()).optional(),
  163. })
  164. const ollamaSchema = baseProviderSettingsSchema.extend({
  165. ollamaModelId: z.string().optional(),
  166. ollamaBaseUrl: z.string().optional(),
  167. })
  168. const vsCodeLmSchema = baseProviderSettingsSchema.extend({
  169. vsCodeLmModelSelector: z
  170. .object({
  171. vendor: z.string().optional(),
  172. family: z.string().optional(),
  173. version: z.string().optional(),
  174. id: z.string().optional(),
  175. })
  176. .optional(),
  177. })
  178. const lmStudioSchema = baseProviderSettingsSchema.extend({
  179. lmStudioModelId: z.string().optional(),
  180. lmStudioBaseUrl: z.string().optional(),
  181. lmStudioDraftModelId: z.string().optional(),
  182. lmStudioSpeculativeDecodingEnabled: z.boolean().optional(),
  183. })
  184. const geminiSchema = apiModelIdProviderModelSchema.extend({
  185. geminiApiKey: z.string().optional(),
  186. googleGeminiBaseUrl: z.string().optional(),
  187. enableUrlContext: z.boolean().optional(),
  188. enableGrounding: z.boolean().optional(),
  189. })
  190. const geminiCliSchema = apiModelIdProviderModelSchema.extend({
  191. geminiCliOAuthPath: z.string().optional(),
  192. geminiCliProjectId: z.string().optional(),
  193. })
  194. const openAiNativeSchema = apiModelIdProviderModelSchema.extend({
  195. openAiNativeApiKey: z.string().optional(),
  196. openAiNativeBaseUrl: z.string().optional(),
  197. })
  198. const mistralSchema = apiModelIdProviderModelSchema.extend({
  199. mistralApiKey: z.string().optional(),
  200. mistralCodestralUrl: z.string().optional(),
  201. })
  202. const deepSeekSchema = apiModelIdProviderModelSchema.extend({
  203. deepSeekBaseUrl: z.string().optional(),
  204. deepSeekApiKey: z.string().optional(),
  205. })
  206. const doubaoSchema = apiModelIdProviderModelSchema.extend({
  207. doubaoBaseUrl: z.string().optional(),
  208. doubaoApiKey: z.string().optional(),
  209. })
  210. const moonshotSchema = apiModelIdProviderModelSchema.extend({
  211. moonshotBaseUrl: z
  212. .union([z.literal("https://api.moonshot.ai/v1"), z.literal("https://api.moonshot.cn/v1")])
  213. .optional(),
  214. moonshotApiKey: z.string().optional(),
  215. })
  216. const unboundSchema = baseProviderSettingsSchema.extend({
  217. unboundApiKey: z.string().optional(),
  218. unboundModelId: z.string().optional(),
  219. })
  220. const requestySchema = baseProviderSettingsSchema.extend({
  221. requestyBaseUrl: z.string().optional(),
  222. requestyApiKey: z.string().optional(),
  223. requestyModelId: z.string().optional(),
  224. })
  225. const humanRelaySchema = baseProviderSettingsSchema
  226. const fakeAiSchema = baseProviderSettingsSchema.extend({
  227. fakeAi: z.unknown().optional(),
  228. })
  229. const xaiSchema = apiModelIdProviderModelSchema.extend({
  230. xaiApiKey: z.string().optional(),
  231. })
  232. const groqSchema = apiModelIdProviderModelSchema.extend({
  233. groqApiKey: z.string().optional(),
  234. })
  235. const huggingFaceSchema = baseProviderSettingsSchema.extend({
  236. huggingFaceApiKey: z.string().optional(),
  237. huggingFaceModelId: z.string().optional(),
  238. huggingFaceInferenceProvider: z.string().optional(),
  239. })
  240. const chutesSchema = apiModelIdProviderModelSchema.extend({
  241. chutesApiKey: z.string().optional(),
  242. })
  243. const litellmSchema = baseProviderSettingsSchema.extend({
  244. litellmBaseUrl: z.string().optional(),
  245. litellmApiKey: z.string().optional(),
  246. litellmModelId: z.string().optional(),
  247. litellmUsePromptCache: z.boolean().optional(),
  248. })
  249. const cerebrasSchema = apiModelIdProviderModelSchema.extend({
  250. cerebrasApiKey: z.string().optional(),
  251. })
  252. const sambaNovaSchema = apiModelIdProviderModelSchema.extend({
  253. sambaNovaApiKey: z.string().optional(),
  254. })
  255. const zaiSchema = apiModelIdProviderModelSchema.extend({
  256. zaiApiKey: z.string().optional(),
  257. zaiApiLine: z.union([z.literal("china"), z.literal("international")]).optional(),
  258. })
  259. const fireworksSchema = apiModelIdProviderModelSchema.extend({
  260. fireworksApiKey: z.string().optional(),
  261. })
  262. const featherlessSchema = apiModelIdProviderModelSchema.extend({
  263. featherlessApiKey: z.string().optional(),
  264. })
  265. const ioIntelligenceSchema = apiModelIdProviderModelSchema.extend({
  266. ioIntelligenceModelId: z.string().optional(),
  267. ioIntelligenceApiKey: z.string().optional(),
  268. })
  269. const qwenCodeSchema = apiModelIdProviderModelSchema.extend({
  270. qwenCodeOauthPath: z.string().optional(),
  271. })
  272. const rooSchema = apiModelIdProviderModelSchema.extend({
  273. // No additional fields needed - uses cloud authentication
  274. })
  275. const defaultSchema = z.object({
  276. apiProvider: z.undefined(),
  277. })
  278. export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProvider", [
  279. anthropicSchema.merge(z.object({ apiProvider: z.literal("anthropic") })),
  280. claudeCodeSchema.merge(z.object({ apiProvider: z.literal("claude-code") })),
  281. glamaSchema.merge(z.object({ apiProvider: z.literal("glama") })),
  282. openRouterSchema.merge(z.object({ apiProvider: z.literal("openrouter") })),
  283. bedrockSchema.merge(z.object({ apiProvider: z.literal("bedrock") })),
  284. vertexSchema.merge(z.object({ apiProvider: z.literal("vertex") })),
  285. openAiSchema.merge(z.object({ apiProvider: z.literal("openai") })),
  286. ollamaSchema.merge(z.object({ apiProvider: z.literal("ollama") })),
  287. vsCodeLmSchema.merge(z.object({ apiProvider: z.literal("vscode-lm") })),
  288. lmStudioSchema.merge(z.object({ apiProvider: z.literal("lmstudio") })),
  289. geminiSchema.merge(z.object({ apiProvider: z.literal("gemini") })),
  290. geminiCliSchema.merge(z.object({ apiProvider: z.literal("gemini-cli") })),
  291. openAiNativeSchema.merge(z.object({ apiProvider: z.literal("openai-native") })),
  292. mistralSchema.merge(z.object({ apiProvider: z.literal("mistral") })),
  293. deepSeekSchema.merge(z.object({ apiProvider: z.literal("deepseek") })),
  294. doubaoSchema.merge(z.object({ apiProvider: z.literal("doubao") })),
  295. moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })),
  296. unboundSchema.merge(z.object({ apiProvider: z.literal("unbound") })),
  297. requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })),
  298. humanRelaySchema.merge(z.object({ apiProvider: z.literal("human-relay") })),
  299. fakeAiSchema.merge(z.object({ apiProvider: z.literal("fake-ai") })),
  300. xaiSchema.merge(z.object({ apiProvider: z.literal("xai") })),
  301. groqSchema.merge(z.object({ apiProvider: z.literal("groq") })),
  302. huggingFaceSchema.merge(z.object({ apiProvider: z.literal("huggingface") })),
  303. chutesSchema.merge(z.object({ apiProvider: z.literal("chutes") })),
  304. litellmSchema.merge(z.object({ apiProvider: z.literal("litellm") })),
  305. cerebrasSchema.merge(z.object({ apiProvider: z.literal("cerebras") })),
  306. sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })),
  307. zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })),
  308. fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })),
  309. featherlessSchema.merge(z.object({ apiProvider: z.literal("featherless") })),
  310. ioIntelligenceSchema.merge(z.object({ apiProvider: z.literal("io-intelligence") })),
  311. qwenCodeSchema.merge(z.object({ apiProvider: z.literal("qwen-code") })),
  312. rooSchema.merge(z.object({ apiProvider: z.literal("roo") })),
  313. defaultSchema,
  314. ])
  315. export const providerSettingsSchema = z.object({
  316. apiProvider: providerNamesSchema.optional(),
  317. ...anthropicSchema.shape,
  318. ...claudeCodeSchema.shape,
  319. ...glamaSchema.shape,
  320. ...openRouterSchema.shape,
  321. ...bedrockSchema.shape,
  322. ...vertexSchema.shape,
  323. ...openAiSchema.shape,
  324. ...ollamaSchema.shape,
  325. ...vsCodeLmSchema.shape,
  326. ...lmStudioSchema.shape,
  327. ...geminiSchema.shape,
  328. ...geminiCliSchema.shape,
  329. ...openAiNativeSchema.shape,
  330. ...mistralSchema.shape,
  331. ...deepSeekSchema.shape,
  332. ...doubaoSchema.shape,
  333. ...moonshotSchema.shape,
  334. ...unboundSchema.shape,
  335. ...requestySchema.shape,
  336. ...humanRelaySchema.shape,
  337. ...fakeAiSchema.shape,
  338. ...xaiSchema.shape,
  339. ...groqSchema.shape,
  340. ...huggingFaceSchema.shape,
  341. ...chutesSchema.shape,
  342. ...litellmSchema.shape,
  343. ...cerebrasSchema.shape,
  344. ...sambaNovaSchema.shape,
  345. ...zaiSchema.shape,
  346. ...fireworksSchema.shape,
  347. ...featherlessSchema.shape,
  348. ...ioIntelligenceSchema.shape,
  349. ...qwenCodeSchema.shape,
  350. ...rooSchema.shape,
  351. ...codebaseIndexProviderSchema.shape,
  352. })
  353. export type ProviderSettings = z.infer<typeof providerSettingsSchema>
  354. export const providerSettingsWithIdSchema = providerSettingsSchema.extend({ id: z.string().optional() })
  355. export const discriminatedProviderSettingsWithIdSchema = providerSettingsSchemaDiscriminated.and(
  356. z.object({ id: z.string().optional() }),
  357. )
  358. export type ProviderSettingsWithId = z.infer<typeof providerSettingsWithIdSchema>
  359. export const PROVIDER_SETTINGS_KEYS = providerSettingsSchema.keyof().options
  360. export const MODEL_ID_KEYS: Partial<keyof ProviderSettings>[] = [
  361. "apiModelId",
  362. "glamaModelId",
  363. "openRouterModelId",
  364. "openAiModelId",
  365. "ollamaModelId",
  366. "lmStudioModelId",
  367. "lmStudioDraftModelId",
  368. "unboundModelId",
  369. "requestyModelId",
  370. "litellmModelId",
  371. "huggingFaceModelId",
  372. "ioIntelligenceModelId",
  373. ]
  374. export const getModelId = (settings: ProviderSettings): string | undefined => {
  375. const modelIdKey = MODEL_ID_KEYS.find((key) => settings[key])
  376. return modelIdKey ? (settings[modelIdKey] as string) : undefined
  377. }
  378. // Providers that use Anthropic-style API protocol.
  379. export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code", "bedrock"]
  380. export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => {
  381. if (provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider)) {
  382. return "anthropic"
  383. }
  384. if (provider && provider === "vertex" && modelId && modelId.toLowerCase().includes("claude")) {
  385. return "anthropic"
  386. }
  387. return "openai"
  388. }
  389. export const MODELS_BY_PROVIDER: Record<
  390. Exclude<ProviderName, "fake-ai" | "human-relay" | "gemini-cli" | "lmstudio" | "openai" | "ollama">,
  391. { id: ProviderName; label: string; models: string[] }
  392. > = {
  393. anthropic: {
  394. id: "anthropic",
  395. label: "Anthropic",
  396. models: Object.keys(anthropicModels),
  397. },
  398. bedrock: {
  399. id: "bedrock",
  400. label: "Amazon Bedrock",
  401. models: Object.keys(bedrockModels),
  402. },
  403. cerebras: {
  404. id: "cerebras",
  405. label: "Cerebras",
  406. models: Object.keys(cerebrasModels),
  407. },
  408. chutes: {
  409. id: "chutes",
  410. label: "Chutes AI",
  411. models: Object.keys(chutesModels),
  412. },
  413. "claude-code": { id: "claude-code", label: "Claude Code", models: Object.keys(claudeCodeModels) },
  414. deepseek: {
  415. id: "deepseek",
  416. label: "DeepSeek",
  417. models: Object.keys(deepSeekModels),
  418. },
  419. doubao: { id: "doubao", label: "Doubao", models: Object.keys(doubaoModels) },
  420. featherless: {
  421. id: "featherless",
  422. label: "Featherless",
  423. models: Object.keys(featherlessModels),
  424. },
  425. fireworks: {
  426. id: "fireworks",
  427. label: "Fireworks",
  428. models: Object.keys(fireworksModels),
  429. },
  430. gemini: {
  431. id: "gemini",
  432. label: "Google Gemini",
  433. models: Object.keys(geminiModels),
  434. },
  435. groq: { id: "groq", label: "Groq", models: Object.keys(groqModels) },
  436. "io-intelligence": {
  437. id: "io-intelligence",
  438. label: "IO Intelligence",
  439. models: Object.keys(ioIntelligenceModels),
  440. },
  441. mistral: {
  442. id: "mistral",
  443. label: "Mistral",
  444. models: Object.keys(mistralModels),
  445. },
  446. moonshot: {
  447. id: "moonshot",
  448. label: "Moonshot",
  449. models: Object.keys(moonshotModels),
  450. },
  451. "openai-native": {
  452. id: "openai-native",
  453. label: "OpenAI",
  454. models: Object.keys(openAiNativeModels),
  455. },
  456. "qwen-code": { id: "qwen-code", label: "Qwen Code", models: Object.keys(qwenCodeModels) },
  457. roo: { id: "roo", label: "Roo", models: Object.keys(rooModels) },
  458. sambanova: {
  459. id: "sambanova",
  460. label: "SambaNova",
  461. models: Object.keys(sambaNovaModels),
  462. },
  463. vertex: {
  464. id: "vertex",
  465. label: "GCP Vertex AI",
  466. models: Object.keys(vertexModels),
  467. },
  468. "vscode-lm": {
  469. id: "vscode-lm",
  470. label: "VS Code LM API",
  471. models: Object.keys(vscodeLlmModels),
  472. },
  473. xai: { id: "xai", label: "xAI (Grok)", models: Object.keys(xaiModels) },
  474. zai: { id: "zai", label: "Zai", models: Object.keys(internationalZAiModels) },
  475. // Dynamic providers; models pulled from the respective APIs.
  476. glama: { id: "glama", label: "Glama", models: [] },
  477. huggingface: { id: "huggingface", label: "Hugging Face", models: [] },
  478. litellm: { id: "litellm", label: "LiteLLM", models: [] },
  479. openrouter: { id: "openrouter", label: "OpenRouter", models: [] },
  480. requesty: { id: "requesty", label: "Requesty", models: [] },
  481. unbound: { id: "unbound", label: "Unbound", models: [] },
  482. }
  483. export const dynamicProviders = [
  484. "glama",
  485. "huggingface",
  486. "litellm",
  487. "openrouter",
  488. "requesty",
  489. "unbound",
  490. ] as const satisfies readonly ProviderName[]
  491. export type DynamicProvider = (typeof dynamicProviders)[number]
  492. export const isDynamicProvider = (key: string): key is DynamicProvider =>
  493. dynamicProviders.includes(key as DynamicProvider)