cli-providers.mjs 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. #!/usr/bin/env node
  2. /**
  3. * CLI Provider Definition Generator
  4. * ==================================
  5. *
  6. * This script generates Go code for the CLI version of Cline by extracting provider
  7. * metadata from the TypeScript source (src/shared/api.ts) and converting it to Go
  8. * structs. It serves as the bridge between the VSCode extension's TypeScript API
  9. * definitions and the CLI's Go-based setup wizard.
  10. *
  11. * Purpose:
  12. * --------
  13. * - Extract provider configurations, API key requirements, and model definitions
  14. * - Filter to only include whitelisted providers (ENABLED_PROVIDERS constant)
  15. * - Generate type-safe Go code with embedded JSON data
  16. * - Keep the CLI binary lean by excluding unused providers
  17. *
  18. * What it generates:
  19. * ------------------
  20. * - cli/pkg/generated/providers.go - Go structs and constants for provider metadata
  21. * - Includes: Provider constants, config fields, model definitions, helper functions
  22. *
  23. * How it works:
  24. * -------------
  25. * 1. Parses TypeScript API definitions from src/shared/api.ts
  26. * 2. Extracts provider IDs, configuration fields, and model information
  27. * 3. Filters config fields and models to only include ENABLED_PROVIDERS
  28. * 4. Generates Go code with JSON-embedded data for runtime access
  29. * 5. Includes comprehensive documentation in the generated file
  30. *
  31. * Data Filtering:
  32. * ---------------
  33. * - Provider list: Filtered to ENABLED_PROVIDERS (currently 9 of 36 providers)
  34. * - Config fields: Only includes fields where category matches a whitelisted provider
  35. * - Model definitions: Only includes model maps for whitelisted providers
  36. * - Result: Non-whitelisted provider data never makes it into the CLI binary
  37. *
  38. * Usage:
  39. * ------
  40. * npm run cli-providers
  41. *
  42. * To modify which providers are included:
  43. * 1. Edit the ENABLED_PROVIDERS array below
  44. * 2. Run: npm run cli-providers
  45. * 3. Verify the output in cli/pkg/generated/providers.go
  46. *
  47. * Dependencies:
  48. * -------------
  49. * - api-secrets-parser.mjs - Helper module for parsing API key fields
  50. * - src/shared/api.ts - Source of truth for provider definitions
  51. *
  52. * Output:
  53. * -------
  54. * The generated Go file includes:
  55. * - Type definitions (ConfigField, ModelInfo, ProviderDefinition)
  56. * - Provider constants and AllProviders array
  57. * - Embedded JSON data for config fields and model definitions
  58. * - Helper functions for querying provider metadata
  59. * - Comprehensive documentation for developers
  60. */
  61. import chalk from "chalk"
  62. import * as fs from "fs/promises"
  63. import * as path from "path"
  64. import { fileURLToPath } from "url"
  65. const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url))
  66. const ROOT_DIR = path.resolve(SCRIPT_DIR, "..")
  67. const API_DEFINITIONS_FILE = path.resolve(ROOT_DIR, "src", "shared", "api.ts")
  68. const GO_OUTPUT_FILE = path.resolve(ROOT_DIR, "cli", "pkg", "generated", "providers.go")
  69. /**
  70. * ENABLED_PROVIDERS - Controls which providers are included in the CLI build
  71. *
  72. * This list determines which providers from src/shared/api.ts will be included
  73. * in the generated Go code for the CLI version. This allows us to keep the CLI
  74. * lean by only including the most commonly used providers.
  75. *
  76. * To add or remove providers:
  77. * 1. Add/remove the provider ID from this array (must match ApiProvider values)
  78. * 2. Run: npm run cli-providers (or node scripts/cli-providers.mjs)
  79. * 3. Verify the output in cli/pkg/generated/providers.go
  80. *
  81. * Provider IDs must match exactly as defined in the ApiProvider type in api.ts
  82. */
  83. const ENABLED_PROVIDERS = [
  84. "anthropic", // Anthropic Claude models
  85. "openai", // OpenAI-compatible providers
  86. "openai-native", // OpenAI official API
  87. "openrouter", // OpenRouter meta-provider
  88. "xai", // X AI (Grok)
  89. "bedrock", // AWS Bedrock
  90. "gemini", // Google Gemini
  91. "ollama", // Ollama local models
  92. "cerebras", // Cerebras models
  93. "oca", // Oracle Code Assist
  94. "nousResearch", // NousResearch provider
  95. ]
  96. /**
  97. * Extract default model IDs from TypeScript source
  98. * Uses multiple regex patterns to catch different variable declaration styles
  99. */
  100. function extractDefaultModelIds(content) {
  101. const defaultIds = {}
  102. // Multiple regex patterns to handle different TypeScript patterns
  103. const patterns = [
  104. // Pattern 1: With type annotation - export const anthropicDefaultModelId: AnthropicModelId = "model-id"
  105. /export const (\w+)DefaultModelId\s*:\s*\w+\s*=\s*"([^"]+)"/g,
  106. // Pattern 2: Without type annotation - export const anthropicDefaultModelId = "model-id"
  107. /export const (\w+)DefaultModelId\s*=\s*"([^"]+)"/g,
  108. // Pattern 3: Without export - const anthropicDefaultModelId = "model-id"
  109. /const (\w+)DefaultModelId\s*=\s*"([^"]+)"/g,
  110. ]
  111. for (const regex of patterns) {
  112. // Reset regex state for each pattern
  113. regex.lastIndex = 0
  114. let match
  115. while ((match = regex.exec(content)) !== null) {
  116. const [, providerPrefix, modelId] = match
  117. // Map prefix to provider ID (e.g., "anthropic" -> "anthropic", "openAiNative" -> "openai-native")
  118. const providerId = providerPrefix
  119. .replace(/([A-Z])/g, "-$1")
  120. .toLowerCase()
  121. .replace(/^-/, "")
  122. // Don't overwrite if already found (first match wins)
  123. if (!defaultIds[providerId]) {
  124. // Clean up model ID - remove any suffix like ":1m"
  125. const cleanModelId = modelId.split(":")[0]
  126. defaultIds[providerId] = cleanModelId
  127. }
  128. }
  129. }
  130. return defaultIds
  131. }
  132. /**
  133. * Parse TypeScript API definitions and extract provider information
  134. */
  135. async function parseApiDefinitions() {
  136. console.log(chalk.cyan("Reading TypeScript API definitions..."))
  137. const content = await fs.readFile(API_DEFINITIONS_FILE, "utf-8")
  138. // Extract ApiProvider type definition
  139. const providerTypeMatch = content.match(/export type ApiProvider =\s*([\s\S]*?)(?=\n\nexport|\n\ninterface|\ninterface)/m)
  140. if (!providerTypeMatch) {
  141. throw new Error("Could not find ApiProvider type definition")
  142. }
  143. // Parse provider IDs from the union type
  144. const providerTypeContent = providerTypeMatch[1]
  145. const providerIds = []
  146. const providerMatches = providerTypeContent.matchAll(/\|\s*"([^"]+)"/g)
  147. for (const match of providerMatches) {
  148. providerIds.push(match[1])
  149. }
  150. // Also get the first provider (without |)
  151. const firstProviderMatch = providerTypeContent.match(/"([^"]+)"/)
  152. if (firstProviderMatch && !providerIds.includes(firstProviderMatch[1])) {
  153. providerIds.unshift(firstProviderMatch[1])
  154. }
  155. console.log(
  156. chalk.green(
  157. `Found ${providerIds.length} total providers: ${providerIds.slice(0, 5).join(", ")}${providerIds.length > 5 ? "..." : ""}`,
  158. ),
  159. )
  160. // Filter to only enabled providers
  161. const totalProvidersFound = providerIds.length
  162. const filteredProviderIds = providerIds.filter((id) => ENABLED_PROVIDERS.includes(id))
  163. const disabledCount = totalProvidersFound - filteredProviderIds.length
  164. console.log(chalk.cyan(`Filtering to ${filteredProviderIds.length} enabled providers (${disabledCount} disabled)`))
  165. console.log(chalk.green(` Enabled: ${filteredProviderIds.join(", ")}`))
  166. // Validate that all enabled providers exist in the source
  167. const missingProviders = ENABLED_PROVIDERS.filter((id) => !providerIds.includes(id))
  168. if (missingProviders.length > 0) {
  169. console.log(
  170. chalk.yellow(
  171. ` WARNING: ${missingProviders.length} enabled provider(s) not found in api.ts: ${missingProviders.join(", ")}`,
  172. ),
  173. )
  174. }
  175. // Parse ApiHandlerSecrets to auto-discover API key fields
  176. const { parseApiHandlerSecrets, mapProviderToApiKeys, validateApiKeyMappings } = await import("./api-secrets-parser.mjs")
  177. const apiSecretsFields = parseApiHandlerSecrets(content)
  178. const providerApiKeyMap = mapProviderToApiKeys(providerIds, apiSecretsFields)
  179. // Validate the mapping
  180. const validation = validateApiKeyMappings(providerIds, providerApiKeyMap)
  181. console.log(chalk.green(` Mapped API keys for ${validation.mappedProviders}/${validation.totalProviders} providers`))
  182. if (validation.warnings.length > 0) {
  183. validation.warnings.forEach((warning) => console.log(chalk.yellow(` ${warning}`)))
  184. }
  185. // Extract ApiHandlerOptions interface to understand configuration fields
  186. const optionsMatch = content.match(/export interface ApiHandlerOptions \{([\s\S]*?)\}/m)
  187. if (!optionsMatch) {
  188. throw new Error("Could not find ApiHandlerOptions interface")
  189. }
  190. const optionsContent = optionsMatch[1]
  191. const configFields = parseConfigurationFields(optionsContent, providerApiKeyMap, apiSecretsFields)
  192. // Extract model definitions for each provider
  193. const modelDefinitions = extractModelDefinitions(content)
  194. // Extract default model IDs from TypeScript constants
  195. const defaultModelIds = extractDefaultModelIds(content)
  196. console.log(chalk.green(` Extracted ${Object.keys(defaultModelIds).length} default model IDs`))
  197. // Filter config fields to only include whitelisted providers
  198. const filteredConfigFields = configFields.filter(
  199. (field) =>
  200. // Include fields for whitelisted providers
  201. filteredProviderIds.includes(field.category) ||
  202. // Include general fields that apply to all providers
  203. field.category === "general",
  204. )
  205. // Filter model definitions to only include whitelisted providers
  206. const filteredModelDefinitions = Object.fromEntries(
  207. Object.entries(modelDefinitions).filter(([providerId]) => filteredProviderIds.includes(providerId)),
  208. )
  209. console.log(
  210. chalk.cyan(
  211. ` Filtered config fields: ${configFields.length} -> ${filteredConfigFields.length} (${configFields.length - filteredConfigFields.length} excluded)`,
  212. ),
  213. )
  214. console.log(
  215. chalk.cyan(
  216. ` Filtered model definitions: ${Object.keys(modelDefinitions).length} -> ${Object.keys(filteredModelDefinitions).length} (${Object.keys(modelDefinitions).length - Object.keys(filteredModelDefinitions).length} excluded)`,
  217. ),
  218. )
  219. return {
  220. providers: filteredProviderIds,
  221. configFields: filteredConfigFields,
  222. modelDefinitions: filteredModelDefinitions,
  223. defaultModelIds,
  224. providerApiKeyMap,
  225. }
  226. }
  227. /**
  228. * Parse configuration fields from ApiHandlerOptions and ApiHandlerSecrets
  229. */
  230. function parseConfigurationFields(optionsContent, providerApiKeyMap, apiSecretsFields) {
  231. const fields = []
  232. // FIRST: Add API key fields from ApiHandlerSecrets
  233. // These are the actual authentication fields that need to be collected
  234. for (const fieldName of apiSecretsFields.fieldNames) {
  235. const fieldInfo = apiSecretsFields.fields[fieldName]
  236. const lowerName = fieldName.toLowerCase()
  237. // Determine which provider this field belongs to
  238. let category = "general"
  239. for (const [providerId, apiKeys] of Object.entries(providerApiKeyMap)) {
  240. if (apiKeys.includes(fieldName)) {
  241. category = providerId
  242. break
  243. }
  244. }
  245. // All API key fields are required for their respective provider
  246. const required = true
  247. const fieldType = "password"
  248. const placeholder = "Enter your API key"
  249. fields.push({
  250. name: fieldName,
  251. type: fieldInfo.type,
  252. comment: fieldInfo.comment || "",
  253. category,
  254. required,
  255. fieldType,
  256. placeholder,
  257. })
  258. }
  259. // SECOND: Add configuration fields from ApiHandlerOptions
  260. // Match field definitions like: fieldName?: type // comment
  261. const fieldMatches = optionsContent.matchAll(/^\s*([a-zA-Z][a-zA-Z0-9_]*)\?\s*:\s*([^/\n]+)(?:\/\/\s*(.*))?$/gm)
  262. for (const match of fieldMatches) {
  263. const [, name, type, comment] = match
  264. // Skip mode-specific fields (we'll handle those separately)
  265. if (name.includes("planMode") || name.includes("actMode")) {
  266. continue
  267. }
  268. const lowerName = name.toLowerCase()
  269. // Determine field category based on provider-specific prefixes FIRST
  270. let category = "general"
  271. let required = false
  272. let fieldType = "string"
  273. let placeholder = ""
  274. // Check for provider-specific prefixes to categorize appropriately
  275. const providerPrefixes = [
  276. "anthropic",
  277. "openrouter",
  278. "aws",
  279. "bedrock",
  280. "vertex",
  281. "openai",
  282. "ollama",
  283. "lmstudio",
  284. "gemini",
  285. "deepseek",
  286. "qwen",
  287. "doubao",
  288. "mistral",
  289. "litellm",
  290. "moonshot",
  291. "nebius",
  292. "fireworks",
  293. "asksage",
  294. "xai",
  295. "sambanova",
  296. "cerebras",
  297. "sapaicore",
  298. "groq",
  299. "huggingface",
  300. "huawei",
  301. "dify",
  302. "baseten",
  303. "vercel",
  304. "zai",
  305. "requesty",
  306. "together",
  307. "claudecode",
  308. "cline",
  309. ]
  310. // If field name starts with or contains a provider prefix, categorize it as provider-specific
  311. for (const prefix of providerPrefixes) {
  312. if (lowerName.startsWith(prefix) || lowerName.includes(prefix)) {
  313. category = prefix
  314. break
  315. }
  316. }
  317. // Set field type metadata for UI rendering
  318. if (lowerName.includes("apikey")) {
  319. fieldType = "password"
  320. placeholder = "Enter your API key"
  321. } else if (lowerName.includes("key") && !lowerName.includes("apikey")) {
  322. fieldType = "password"
  323. placeholder = "Enter your key"
  324. } else if (lowerName.includes("url") || lowerName.includes("endpoint")) {
  325. fieldType = "url"
  326. placeholder = "https://api.example.com"
  327. } else if (lowerName.includes("region")) {
  328. fieldType = "select"
  329. } else if (lowerName.includes("model")) {
  330. // model fields stay with their provider category
  331. }
  332. // Check if this field is required for any provider using the auto-discovered API key map
  333. // A field is marked as required if it appears in any provider's required fields list
  334. for (const [providerId, requiredFields] of Object.entries(providerApiKeyMap)) {
  335. if (requiredFields.includes(name)) {
  336. required = true
  337. break
  338. }
  339. }
  340. fields.push({
  341. name,
  342. type: type.trim(),
  343. comment: comment?.trim() || "",
  344. category,
  345. required,
  346. fieldType,
  347. placeholder,
  348. })
  349. }
  350. return fields
  351. }
  352. /**
  353. * Extract model definitions for each provider
  354. */
  355. function extractModelDefinitions(content) {
  356. const modelDefinitions = {}
  357. // Find all model constant definitions like: export const anthropicModels = {
  358. const modelMatches = content.matchAll(/export const (\w+)Models = \{([\s\S]*?)\} as const/g)
  359. for (const match of modelMatches) {
  360. const [, providerPrefix, modelsContent] = match
  361. // Parse individual model entries
  362. const models = {}
  363. const modelEntryMatches = modelsContent.matchAll(/"([^"]+)":\s*\{([\s\S]*?)\},?/g)
  364. for (const modelMatch of modelEntryMatches) {
  365. const [, modelId, modelContent] = modelMatch
  366. // Parse model properties
  367. const modelInfo = parseModelInfo(modelContent)
  368. models[modelId] = modelInfo
  369. }
  370. // Map provider prefix to actual provider ID
  371. const providerMapping = {
  372. anthropic: "anthropic",
  373. claudeCode: "claude-code",
  374. bedrock: "bedrock",
  375. vertex: "vertex",
  376. openAiNative: "openai-native",
  377. gemini: "gemini",
  378. deepSeek: "deepseek",
  379. huggingFace: "huggingface",
  380. qwen: "qwen",
  381. doubao: "doubao",
  382. mistral: "mistral",
  383. xai: "xai",
  384. sambanova: "sambanova",
  385. cerebras: "cerebras",
  386. sapAiCore: "sapaicore",
  387. moonshot: "moonshot",
  388. huaweiCloudMaas: "huawei-cloud-maas",
  389. baseten: "baseten",
  390. fireworks: "fireworks",
  391. groq: "groq",
  392. nebius: "nebius",
  393. askSage: "asksage",
  394. qwenCode: "qwen-code",
  395. }
  396. const providerId = providerMapping[providerPrefix] || providerPrefix.toLowerCase()
  397. if (Object.keys(models).length > 0) {
  398. modelDefinitions[providerId] = models
  399. }
  400. }
  401. return modelDefinitions
  402. }
  403. /**
  404. * Parse model information from model definition content
  405. */
  406. function parseModelInfo(modelContent) {
  407. const info = {}
  408. // Parse numeric properties
  409. const numericProps = ["maxTokens", "contextWindow", "inputPrice", "outputPrice", "cacheWritesPrice", "cacheReadsPrice"]
  410. for (const prop of numericProps) {
  411. const match = modelContent.match(new RegExp(`${prop}:\\s*([0-9_,]+)`))
  412. if (match) {
  413. info[prop] = parseInt(match[1].replace(/[_,]/g, ""), 10)
  414. }
  415. }
  416. // Parse boolean properties
  417. const booleanProps = ["supportsImages", "supportsPromptCache"]
  418. for (const prop of booleanProps) {
  419. const match = modelContent.match(new RegExp(`${prop}:\\s*(true|false)`))
  420. if (match) {
  421. info[prop] = match[1] === "true"
  422. }
  423. }
  424. // Parse description
  425. const descMatch = modelContent.match(/description:\s*"([^"]*)"/)
  426. if (descMatch) {
  427. info.description = descMatch[1]
  428. }
  429. return info
  430. }
  431. /**
  432. * Generate Go structs from parsed data
  433. */
  434. function generateGoCode(data) {
  435. console.log(chalk.cyan("Generating Go code..."))
  436. const { providers, configFields, modelDefinitions } = data
  437. // Generate provider constants
  438. const providerConstants = providers.map((p) => `\t${p.toUpperCase().replace(/-/g, "_")} = "${p}"`).join("\n")
  439. // Generate configuration field definitions
  440. const configFieldsJson = JSON.stringify(configFields, null, 2)
  441. .split("\n")
  442. .map((line) => `\t${line}`)
  443. .join("\n")
  444. // Generate model definitions
  445. const modelDefinitionsJson = JSON.stringify(modelDefinitions, null, 2)
  446. .split("\n")
  447. .map((line) => `\t${line}`)
  448. .join("\n")
  449. // Generate provider metadata
  450. const providerMetadata = generateProviderMetadata(providers, configFields, modelDefinitions, data.defaultModelIds)
  451. return `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
  452. // Generated by scripts/generate-provider-definitions.mjs
  453. // Source: src/shared/api.ts
  454. //
  455. // ============================================================================
  456. // DATA CONTRACT & DOCUMENTATION
  457. // ============================================================================
  458. //
  459. // This file provides structured provider metadata extracted from TypeScript source.
  460. // It serves as the bridge between the VSCode extension's TypeScript API definitions
  461. // and the CLI's Go-based setup wizard.
  462. //
  463. // CORE STRUCTURES
  464. // ===============
  465. //
  466. // ConfigField: Individual configuration fields with type, category, and validation metadata
  467. // - Name: Field name as it appears in ApiHandlerOptions (e.g., "cerebrasApiKey")
  468. // - Type: TypeScript type (e.g., "string", "number")
  469. // - Comment: Inline comment from TypeScript source
  470. // - Category: Provider categorization (e.g., "cerebras", "general")
  471. // - Required: Whether this field MUST be collected for any provider
  472. // - FieldType: UI field type hint ("password", "url", "string", "select")
  473. // - Placeholder: Suggested placeholder text for UI input
  474. //
  475. // ModelInfo: Model capabilities, pricing, and limits
  476. // - MaxTokens: Maximum output tokens
  477. // - ContextWindow: Total context window size
  478. // - SupportsImages: Whether model accepts image inputs
  479. // - SupportsPromptCache: Whether model supports prompt caching
  480. // - InputPrice: Cost per 1M input tokens (USD)
  481. // - OutputPrice: Cost per 1M output tokens (USD)
  482. // - CacheWritesPrice: Cost per 1M cached tokens written (USD)
  483. // - CacheReadsPrice: Cost per 1M cached tokens read (USD)
  484. // - Description: Human-readable model description
  485. //
  486. // ProviderDefinition: Complete provider metadata including required/optional fields
  487. // - ID: Provider identifier (e.g., "cerebras", "anthropic")
  488. // - Name: Human-readable display name (e.g., "Cerebras", "Anthropic (Claude)")
  489. // - RequiredFields: Fields that MUST be collected (filtered by category + overrides)
  490. // - OptionalFields: Fields that MAY be collected (filtered by category + overrides)
  491. // - Models: Map of model IDs to ModelInfo
  492. // - DefaultModelID: Recommended default model from TypeScript source
  493. // - HasDynamicModels: Whether provider supports runtime model discovery
  494. // - SetupInstructions: User-facing setup guidance
  495. //
  496. // FIELD FILTERING LOGIC
  497. // =====================
  498. //
  499. // Fields are categorized during parsing based on provider-specific prefixes in field names:
  500. // - "cerebrasApiKey" → category="cerebras"
  501. // - "awsAccessKey" → category="aws" (used by bedrock)
  502. // - "requestTimeoutMs" → category="general" (applies to all providers)
  503. //
  504. // The getFieldsByProvider() function filters fields using this priority:
  505. // 1. Check field_overrides.go via GetFieldOverride() for manual corrections
  506. // 2. Match field.Category against provider ID (primary filtering)
  507. // 3. Apply hardcoded switch cases for complex provider relationships
  508. // 4. Include universal fields (requestTimeoutMs, ulid, clineAccountId) for all providers
  509. //
  510. // Required vs Optional:
  511. // - Fields are marked as required if they appear in the providerRequiredFields map
  512. // in the generator script (scripts/generate-provider-definitions.mjs)
  513. // - getFieldsByProvider() respects the required parameter to separate required/optional
  514. //
  515. // MODEL SELECTION
  516. // ===============
  517. //
  518. // DefaultModelID extraction priority:
  519. // 1. Exact match from TypeScript constant (e.g., cerebrasDefaultModelId = "llama-3.3-70b")
  520. // 2. Pattern matching on model IDs ("latest", "default", "sonnet", "gpt-4", etc.)
  521. // 3. First model in the models map
  522. //
  523. // Models map contains full capability and pricing data extracted from TypeScript model
  524. // definitions (e.g., cerebrasModels, anthropicModels).
  525. //
  526. // HasDynamicModels indicates providers that support runtime model discovery via API
  527. // (e.g., OpenRouter, Ollama, LM Studio). For these providers, the models map may be
  528. // incomplete or a representative sample.
  529. //
  530. // USAGE EXAMPLE
  531. // =============
  532. //
  533. // def, err := GetProviderDefinition("cerebras")
  534. // if err != nil {
  535. // return err
  536. // }
  537. //
  538. // // Collect required fields from user
  539. // for _, field := range def.RequiredFields {
  540. // value := promptUser(field.Name, field.Placeholder, field.FieldType == "password")
  541. // config[field.Name] = value
  542. // }
  543. //
  544. // // Use default model or let user choose
  545. // if def.DefaultModelID != "" {
  546. // config["modelId"] = def.DefaultModelID
  547. // }
  548. //
  549. // EXTENDING & OVERRIDING
  550. // ======================
  551. //
  552. // DO NOT modify this generated file directly. Changes will be lost on regeneration.
  553. //
  554. // To fix incorrect field categorization:
  555. // - Edit cli/pkg/generated/field_overrides.go
  556. // - Add entries to GetFieldOverride() function
  557. // - Example: Force "awsSessionToken" to be relevant for "bedrock"
  558. //
  559. // To change required fields:
  560. // - Edit providerRequiredFields map in scripts/generate-provider-definitions.mjs
  561. // - Rerun: npm run generate-provider-definitions
  562. //
  563. // To add new providers:
  564. // - Add to ApiProvider type in src/shared/api.ts
  565. // - Add fields to ApiHandlerOptions with provider-specific prefixes
  566. // - Optionally add model definitions (e.g., export const newProviderModels = {...})
  567. // - Rerun generator
  568. //
  569. // To fix default model extraction:
  570. // - Ensure TypeScript source has: export const <provider>DefaultModelId = "model-id"
  571. // - Or update extractDefaultModelIds() patterns in generator script
  572. //
  573. // For upstream changes:
  574. // - Submit pull request to src/shared/api.ts in the main repository
  575. //
  576. // ============================================================================
  577. package generated
  578. import (
  579. "encoding/json"
  580. "fmt"
  581. "strings"
  582. )
  583. // Provider constants
  584. const (
  585. ${providerConstants}
  586. )
  587. // AllProviders returns a slice of enabled provider IDs for the CLI build.
  588. // This is a filtered subset of all providers available in the VSCode extension.
  589. // To modify which providers are included, edit ENABLED_PROVIDERS in scripts/cli-providers.mjs
  590. var AllProviders = []string{
  591. ${providers.map((p) => `\t"${p}",`).join("\n")}
  592. }
  593. // ConfigField represents a configuration field requirement
  594. type ConfigField struct {
  595. Name string \`json:"name"\`
  596. Type string \`json:"type"\`
  597. Comment string \`json:"comment"\`
  598. Category string \`json:"category"\`
  599. Required bool \`json:"required"\`
  600. FieldType string \`json:"fieldType"\`
  601. Placeholder string \`json:"placeholder"\`
  602. }
  603. // ModelInfo represents model capabilities and pricing
  604. type ModelInfo struct {
  605. MaxTokens int \`json:"maxTokens,omitempty"\`
  606. ContextWindow int \`json:"contextWindow,omitempty"\`
  607. SupportsImages bool \`json:"supportsImages"\`
  608. SupportsPromptCache bool \`json:"supportsPromptCache"\`
  609. InputPrice float64 \`json:"inputPrice,omitempty"\`
  610. OutputPrice float64 \`json:"outputPrice,omitempty"\`
  611. CacheWritesPrice float64 \`json:"cacheWritesPrice,omitempty"\`
  612. CacheReadsPrice float64 \`json:"cacheReadsPrice,omitempty"\`
  613. Description string \`json:"description,omitempty"\`
  614. }
  615. // ProviderDefinition represents a provider's metadata and requirements
  616. type ProviderDefinition struct {
  617. ID string \`json:"id"\`
  618. Name string \`json:"name"\`
  619. RequiredFields []ConfigField \`json:"requiredFields"\`
  620. OptionalFields []ConfigField \`json:"optionalFields"\`
  621. Models map[string]ModelInfo \`json:"models"\`
  622. DefaultModelID string \`json:"defaultModelId"\`
  623. HasDynamicModels bool \`json:"hasDynamicModels"\`
  624. SetupInstructions string \`json:"setupInstructions"\`
  625. }
  626. // Raw configuration fields data (parsed from TypeScript)
  627. var rawConfigFields = \`${configFieldsJson.replace(/`/g, '` + "`" + `')}\`
  628. // Raw model definitions data (parsed from TypeScript)
  629. var rawModelDefinitions = \`${modelDefinitionsJson.replace(/`/g, '` + "`" + `')}\`
  630. // GetConfigFields returns all configuration fields
  631. func GetConfigFields() ([]ConfigField, error) {
  632. var fields []ConfigField
  633. if err := json.Unmarshal([]byte(rawConfigFields), &fields); err != nil {
  634. return nil, fmt.Errorf("failed to parse config fields: %w", err)
  635. }
  636. return fields, nil
  637. }
  638. // GetModelDefinitions returns all model definitions
  639. func GetModelDefinitions() (map[string]map[string]ModelInfo, error) {
  640. var models map[string]map[string]ModelInfo
  641. if err := json.Unmarshal([]byte(rawModelDefinitions), &models); err != nil {
  642. return nil, fmt.Errorf("failed to parse model definitions: %w", err)
  643. }
  644. return models, nil
  645. }
  646. // GetProviderDefinition returns the definition for a specific provider
  647. func GetProviderDefinition(providerID string) (*ProviderDefinition, error) {
  648. definitions, err := GetProviderDefinitions()
  649. if err != nil {
  650. return nil, err
  651. }
  652. def, exists := definitions[providerID]
  653. if !exists {
  654. return nil, fmt.Errorf("provider %s not found", providerID)
  655. }
  656. return &def, nil
  657. }
  658. // GetProviderDefinitions returns all provider definitions
  659. func GetProviderDefinitions() (map[string]ProviderDefinition, error) {
  660. configFields, err := GetConfigFields()
  661. if err != nil {
  662. return nil, err
  663. }
  664. modelDefinitions, err := GetModelDefinitions()
  665. if err != nil {
  666. return nil, err
  667. }
  668. definitions := make(map[string]ProviderDefinition)
  669. ${providerMetadata}
  670. return definitions, nil
  671. }
  672. // IsValidProvider checks if a provider ID is valid
  673. func IsValidProvider(providerID string) bool {
  674. for _, p := range AllProviders {
  675. if p == providerID {
  676. return true
  677. }
  678. }
  679. return false
  680. }
  681. // GetProviderDisplayName returns a human-readable name for a provider
  682. func GetProviderDisplayName(providerID string) string {
  683. displayNames := map[string]string{
  684. ${providers.map((p) => `\t\t"${p}": "${getProviderDisplayName(p)}",`).join("\n")}
  685. }
  686. if name, exists := displayNames[providerID]; exists {
  687. return name
  688. }
  689. return providerID
  690. }
  691. // getFieldsByProvider filters configuration fields by provider and requirement
  692. // Uses category field as primary filter with override support
  693. func getFieldsByProvider(providerID string, allFields []ConfigField, required bool) []ConfigField {
  694. var fields []ConfigField
  695. for _, field := range allFields {
  696. fieldName := strings.ToLower(field.Name)
  697. fieldCategory := strings.ToLower(field.Category)
  698. providerName := strings.ToLower(providerID)
  699. isRelevant := false
  700. // Priority 1: Check manual overrides FIRST (from GetFieldOverride in this package)
  701. if override, hasOverride := GetFieldOverride(providerID, field.Name); hasOverride {
  702. isRelevant = override
  703. } else if fieldCategory == providerName {
  704. // Priority 2: Direct category match (primary filtering mechanism)
  705. isRelevant = true
  706. } else if fieldCategory == "aws" && providerID == "bedrock" {
  707. // Priority 3: Handle provider-specific category relationships
  708. // AWS fields are used by Bedrock provider
  709. isRelevant = true
  710. } else if fieldCategory == "openai" && providerID == "openai-native" {
  711. // OpenAI fields used by openai-native
  712. isRelevant = true
  713. } else if fieldCategory == "general" {
  714. // Priority 4: Universal fields that apply to all providers
  715. // Note: ulid is excluded as it's auto-generated and users should not set it
  716. universalFields := []string{"requesttimeoutms", "clineaccountid"}
  717. for _, universal := range universalFields {
  718. if fieldName == universal {
  719. isRelevant = true
  720. break
  721. }
  722. }
  723. }
  724. if isRelevant && field.Required == required {
  725. fields = append(fields, field)
  726. }
  727. }
  728. return fields
  729. }
  730. `
  731. }
  732. /**
  733. * Generate provider metadata for each provider
  734. */
  735. function generateProviderMetadata(providers, configFields, modelDefinitions, defaultModelIds) {
  736. return providers
  737. .map((providerId) => {
  738. const displayName = getProviderDisplayName(providerId)
  739. const models = modelDefinitions[providerId] || {}
  740. const defaultModelId = getDefaultModelId(providerId, models, defaultModelIds)
  741. const hasDynamicModels = hasDynamicModelsSupport(providerId)
  742. const setupInstructions = getSetupInstructions(providerId)
  743. return `\t// ${displayName}
  744. definitions["${providerId}"] = ProviderDefinition{
  745. ID: "${providerId}",
  746. Name: "${displayName}",
  747. RequiredFields: getFieldsByProvider("${providerId}", configFields, true),
  748. OptionalFields: getFieldsByProvider("${providerId}", configFields, false),
  749. Models: modelDefinitions["${providerId}"],
  750. DefaultModelID: "${defaultModelId}",
  751. HasDynamicModels: ${hasDynamicModels},
  752. SetupInstructions: \`${setupInstructions}\`,
  753. }`
  754. })
  755. .join("\n\n")
  756. }
  757. /**
  758. * Get human-readable display name for a provider
  759. */
  760. function getProviderDisplayName(providerId) {
  761. const displayNames = {
  762. anthropic: "Anthropic (Claude)",
  763. "claude-code": "Claude Code",
  764. openrouter: "OpenRouter",
  765. bedrock: "AWS Bedrock",
  766. vertex: "Google Vertex AI",
  767. openai: "OpenAI Compatible",
  768. ollama: "Ollama",
  769. lmstudio: "LM Studio",
  770. gemini: "Google Gemini",
  771. "openai-native": "OpenAI",
  772. requesty: "Requesty",
  773. together: "Together AI",
  774. deepseek: "DeepSeek",
  775. qwen: "Qwen",
  776. "qwen-code": "Qwen Code",
  777. doubao: "Doubao",
  778. mistral: "Mistral AI",
  779. "vscode-lm": "VSCode Language Models",
  780. cline: "Cline",
  781. litellm: "LiteLLM",
  782. moonshot: "Moonshot AI",
  783. nebius: "Nebius AI",
  784. fireworks: "Fireworks AI",
  785. asksage: "AskSage",
  786. xai: "X AI (Grok)",
  787. sambanova: "SambaNova",
  788. cerebras: "Cerebras",
  789. sapaicore: "SAP AI Core",
  790. groq: "Groq",
  791. huggingface: "Hugging Face",
  792. "huawei-cloud-maas": "Huawei Cloud MaaS",
  793. dify: "Dify",
  794. baseten: "Baseten",
  795. "vercel-ai-gateway": "Vercel AI Gateway",
  796. zai: "Z AI",
  797. }
  798. return displayNames[providerId] || providerId.charAt(0).toUpperCase() + providerId.slice(1)
  799. }
  800. /**
  801. * Get default model ID for a provider
  802. */
  803. function getDefaultModelId(providerId, models, defaultModelIds) {
  804. // First, check if we have an extracted default from TypeScript source
  805. if (defaultModelIds && defaultModelIds[providerId]) {
  806. return defaultModelIds[providerId]
  807. }
  808. // Fallback to pattern matching if no explicit default was found
  809. const modelIds = Object.keys(models)
  810. if (modelIds.length === 0) return ""
  811. // Look for common default patterns
  812. const defaultPatterns = ["latest", "default", "sonnet", "gpt-4", "claude-3", "gemini-pro"]
  813. for (const pattern of defaultPatterns) {
  814. const match = modelIds.find((id) => id.toLowerCase().includes(pattern))
  815. if (match) return match
  816. }
  817. // Return first model if no pattern matches
  818. return modelIds[0]
  819. }
  820. /**
  821. * Check if provider supports dynamic model fetching
  822. */
  823. function hasDynamicModelsSupport(providerId) {
  824. // Providers that support dynamic model fetching
  825. const dynamicProviders = [
  826. "openrouter",
  827. "openai",
  828. "openai-native",
  829. "ollama",
  830. "lmstudio",
  831. "litellm",
  832. "together",
  833. "fireworks",
  834. "groq",
  835. ]
  836. return dynamicProviders.includes(providerId)
  837. }
  838. /**
  839. * Get setup instructions for a provider
  840. */
  841. function getSetupInstructions(providerId) {
  842. const instructions = {
  843. anthropic: "Get your API key from https://console.anthropic.com/",
  844. openrouter: "Get your API key from https://openrouter.ai/keys",
  845. bedrock: "Configure AWS credentials with Bedrock access permissions",
  846. vertex: "Set up Google Cloud project with Vertex AI API enabled",
  847. openai: "Get your API key from https://platform.openai.com/api-keys",
  848. "openai-native": "Get your API key from your API provider",
  849. ollama: "Install Ollama locally and ensure it's running on the specified port",
  850. lmstudio: "Install LM Studio and start the local server",
  851. gemini: "Get your API key from https://makersuite.google.com/app/apikey",
  852. deepseek: "Get your API key from https://platform.deepseek.com/",
  853. qwen: "Get your API key from Alibaba Cloud DashScope",
  854. doubao: "Get your API key from ByteDance Volcano Engine",
  855. mistral: "Get your API key from https://console.mistral.ai/",
  856. xai: "Get your API key from https://console.x.ai/",
  857. groq: "Get your API key from https://console.groq.com/keys",
  858. cerebras: "Get your API key from https://cloud.cerebras.ai/",
  859. fireworks: "Get your API key from https://fireworks.ai/",
  860. }
  861. return instructions[providerId] || `Configure ${getProviderDisplayName(providerId)} API credentials`
  862. }
  863. /**
  864. * Main function to generate provider definitions
  865. */
  866. async function main() {
  867. try {
  868. console.log(chalk.cyan("Starting provider definitions generation..."))
  869. // Parse TypeScript API definitions
  870. const data = await parseApiDefinitions()
  871. // Generate Go code
  872. const goCode = generateGoCode(data)
  873. // Ensure output directory exists
  874. const outputDir = path.dirname(GO_OUTPUT_FILE)
  875. await fs.mkdir(outputDir, { recursive: true })
  876. // Write Go file
  877. await fs.writeFile(GO_OUTPUT_FILE, goCode)
  878. console.log(chalk.green(`Successfully generated provider definitions:`))
  879. console.log(chalk.green(` Output: ${GO_OUTPUT_FILE}`))
  880. console.log(chalk.green(` Providers: ${data.providers.length}`))
  881. console.log(chalk.green(` Config fields: ${data.configFields.length}`))
  882. console.log(chalk.green(` Model definitions: ${Object.keys(data.modelDefinitions).length} providers`))
  883. } catch (error) {
  884. console.error(chalk.red("ERROR generating provider definitions:"), error.message)
  885. if (error.stack) {
  886. console.error(chalk.gray(error.stack))
  887. }
  888. process.exit(1)
  889. }
  890. }
  891. // Add helper function to the generated Go code
  892. const helperFunction = `
  893. // getFieldsByProvider filters configuration fields by provider and requirement
  894. func getFieldsByProvider(providerID string, allFields []ConfigField, required bool) []ConfigField {
  895. var fields []ConfigField
  896. for _, field := range allFields {
  897. // Check if field is relevant to this provider
  898. fieldName := strings.ToLower(field.Name)
  899. providerName := strings.ToLower(providerID)
  900. isRelevant := false
  901. // Direct provider name match
  902. if strings.Contains(fieldName, providerName) {
  903. isRelevant = true
  904. }
  905. // Provider-specific field mappings
  906. switch providerID {
  907. case "anthropic":
  908. isRelevant = strings.Contains(fieldName, "apikey") || strings.Contains(fieldName, "anthropic")
  909. case "openrouter":
  910. isRelevant = strings.Contains(fieldName, "openrouter")
  911. case "bedrock":
  912. isRelevant = strings.Contains(fieldName, "aws") || strings.Contains(fieldName, "bedrock")
  913. case "vertex":
  914. isRelevant = strings.Contains(fieldName, "vertex")
  915. case "openai", "openai-native":
  916. isRelevant = strings.Contains(fieldName, "openai")
  917. case "ollama":
  918. isRelevant = strings.Contains(fieldName, "ollama")
  919. case "lmstudio":
  920. isRelevant = strings.Contains(fieldName, "lmstudio")
  921. case "gemini":
  922. isRelevant = strings.Contains(fieldName, "gemini")
  923. }
  924. // General fields that apply to all providers
  925. if field.Category == "general" {
  926. isRelevant = true
  927. }
  928. if isRelevant && field.Required == required {
  929. fields = append(fields, field)
  930. }
  931. }
  932. return fields
  933. }`
  934. // Run if this script is executed directly
  935. if (import.meta.url === `file://${process.argv[1]}`) {
  936. main()
  937. }