cli-providers.mjs 35 KB

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