| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060 |
- #!/usr/bin/env node
- /**
- * CLI Provider Definition Generator
- * ==================================
- *
- * This script generates Go code for the CLI version of Cline by extracting provider
- * metadata from the TypeScript source (src/shared/api.ts) and converting it to Go
- * structs. It serves as the bridge between the VSCode extension's TypeScript API
- * definitions and the CLI's Go-based setup wizard.
- *
- * Purpose:
- * --------
- * - Extract provider configurations, API key requirements, and model definitions
- * - Filter to only include whitelisted providers (ENABLED_PROVIDERS constant)
- * - Generate type-safe Go code with embedded JSON data
- * - Keep the CLI binary lean by excluding unused providers
- *
- * What it generates:
- * ------------------
- * - cli/pkg/generated/providers.go - Go structs and constants for provider metadata
- * - Includes: Provider constants, config fields, model definitions, helper functions
- *
- * How it works:
- * -------------
- * 1. Parses TypeScript API definitions from src/shared/api.ts
- * 2. Extracts provider IDs, configuration fields, and model information
- * 3. Filters config fields and models to only include ENABLED_PROVIDERS
- * 4. Generates Go code with JSON-embedded data for runtime access
- * 5. Includes comprehensive documentation in the generated file
- *
- * Data Filtering:
- * ---------------
- * - Provider list: Filtered to ENABLED_PROVIDERS (currently 9 of 36 providers)
- * - Config fields: Only includes fields where category matches a whitelisted provider
- * - Model definitions: Only includes model maps for whitelisted providers
- * - Result: Non-whitelisted provider data never makes it into the CLI binary
- *
- * Usage:
- * ------
- * npm run cli-providers
- *
- * To modify which providers are included:
- * 1. Edit the ENABLED_PROVIDERS array below
- * 2. Run: npm run cli-providers
- * 3. Verify the output in cli/pkg/generated/providers.go
- *
- * Dependencies:
- * -------------
- * - api-secrets-parser.mjs - Helper module for parsing API key fields
- * - src/shared/api.ts - Source of truth for provider definitions
- *
- * Output:
- * -------
- * The generated Go file includes:
- * - Type definitions (ConfigField, ModelInfo, ProviderDefinition)
- * - Provider constants and AllProviders array
- * - Embedded JSON data for config fields and model definitions
- * - Helper functions for querying provider metadata
- * - Comprehensive documentation for developers
- */
- import chalk from "chalk"
- import * as fs from "fs/promises"
- import * as path from "path"
- import { fileURLToPath } from "url"
- const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url))
- const ROOT_DIR = path.resolve(SCRIPT_DIR, "..")
- const API_DEFINITIONS_FILE = path.resolve(ROOT_DIR, "src", "shared", "api.ts")
- const GO_OUTPUT_FILE = path.resolve(ROOT_DIR, "cli", "pkg", "generated", "providers.go")
- /**
- * ENABLED_PROVIDERS - Controls which providers are included in the CLI build
- *
- * This list determines which providers from src/shared/api.ts will be included
- * in the generated Go code for the CLI version. This allows us to keep the CLI
- * lean by only including the most commonly used providers.
- *
- * To add or remove providers:
- * 1. Add/remove the provider ID from this array (must match ApiProvider values)
- * 2. Run: npm run cli-providers (or node scripts/cli-providers.mjs)
- * 3. Verify the output in cli/pkg/generated/providers.go
- *
- * Provider IDs must match exactly as defined in the ApiProvider type in api.ts
- */
- const ENABLED_PROVIDERS = [
- "anthropic", // Anthropic Claude models
- "openai", // OpenAI-compatible providers
- "openai-native", // OpenAI official API
- "openrouter", // OpenRouter meta-provider
- "xai", // X AI (Grok)
- "bedrock", // AWS Bedrock
- "gemini", // Google Gemini
- "ollama", // Ollama local models
- "cerebras", // Cerebras models
- "oca", // Oracle Code Assist
- "nousResearch", // NousResearch provider
- ]
- /**
- * Extract default model IDs from TypeScript source
- * Uses multiple regex patterns to catch different variable declaration styles
- */
- function extractDefaultModelIds(content) {
- const defaultIds = {}
- // Multiple regex patterns to handle different TypeScript patterns
- const patterns = [
- // Pattern 1: With type annotation - export const anthropicDefaultModelId: AnthropicModelId = "model-id"
- /export const (\w+)DefaultModelId\s*:\s*\w+\s*=\s*"([^"]+)"/g,
- // Pattern 2: Without type annotation - export const anthropicDefaultModelId = "model-id"
- /export const (\w+)DefaultModelId\s*=\s*"([^"]+)"/g,
- // Pattern 3: Without export - const anthropicDefaultModelId = "model-id"
- /const (\w+)DefaultModelId\s*=\s*"([^"]+)"/g,
- ]
- for (const regex of patterns) {
- // Reset regex state for each pattern
- regex.lastIndex = 0
- let match
- while ((match = regex.exec(content)) !== null) {
- const [, providerPrefix, modelId] = match
- // Map prefix to provider ID (e.g., "anthropic" -> "anthropic", "openAiNative" -> "openai-native")
- const providerId = providerPrefix
- .replace(/([A-Z])/g, "-$1")
- .toLowerCase()
- .replace(/^-/, "")
- // Don't overwrite if already found (first match wins)
- if (!defaultIds[providerId]) {
- // Clean up model ID - remove any suffix like ":1m"
- const cleanModelId = modelId.split(":")[0]
- defaultIds[providerId] = cleanModelId
- }
- }
- }
- return defaultIds
- }
- /**
- * Parse TypeScript API definitions and extract provider information
- */
- async function parseApiDefinitions() {
- console.log(chalk.cyan("Reading TypeScript API definitions..."))
- const content = await fs.readFile(API_DEFINITIONS_FILE, "utf-8")
- // Extract ApiProvider type definition
- const providerTypeMatch = content.match(/export type ApiProvider =\s*([\s\S]*?)(?=\n\nexport|\n\ninterface|\ninterface)/m)
- if (!providerTypeMatch) {
- throw new Error("Could not find ApiProvider type definition")
- }
- // Parse provider IDs from the union type
- const providerTypeContent = providerTypeMatch[1]
- const providerIds = []
- const providerMatches = providerTypeContent.matchAll(/\|\s*"([^"]+)"/g)
- for (const match of providerMatches) {
- providerIds.push(match[1])
- }
- // Also get the first provider (without |)
- const firstProviderMatch = providerTypeContent.match(/"([^"]+)"/)
- if (firstProviderMatch && !providerIds.includes(firstProviderMatch[1])) {
- providerIds.unshift(firstProviderMatch[1])
- }
- console.log(
- chalk.green(
- `Found ${providerIds.length} total providers: ${providerIds.slice(0, 5).join(", ")}${providerIds.length > 5 ? "..." : ""}`,
- ),
- )
- // Filter to only enabled providers
- const totalProvidersFound = providerIds.length
- const filteredProviderIds = providerIds.filter((id) => ENABLED_PROVIDERS.includes(id))
- const disabledCount = totalProvidersFound - filteredProviderIds.length
- console.log(chalk.cyan(`Filtering to ${filteredProviderIds.length} enabled providers (${disabledCount} disabled)`))
- console.log(chalk.green(` Enabled: ${filteredProviderIds.join(", ")}`))
- // Validate that all enabled providers exist in the source
- const missingProviders = ENABLED_PROVIDERS.filter((id) => !providerIds.includes(id))
- if (missingProviders.length > 0) {
- console.log(
- chalk.yellow(
- ` WARNING: ${missingProviders.length} enabled provider(s) not found in api.ts: ${missingProviders.join(", ")}`,
- ),
- )
- }
- // Parse ApiHandlerSecrets to auto-discover API key fields
- const { parseApiHandlerSecrets, mapProviderToApiKeys, validateApiKeyMappings } = await import("./api-secrets-parser.mjs")
- const apiSecretsFields = parseApiHandlerSecrets(content)
- const providerApiKeyMap = mapProviderToApiKeys(providerIds, apiSecretsFields)
- // Validate the mapping
- const validation = validateApiKeyMappings(providerIds, providerApiKeyMap)
- console.log(chalk.green(` Mapped API keys for ${validation.mappedProviders}/${validation.totalProviders} providers`))
- if (validation.warnings.length > 0) {
- validation.warnings.forEach((warning) => console.log(chalk.yellow(` ${warning}`)))
- }
- // Extract ApiHandlerOptions interface to understand configuration fields
- const optionsMatch = content.match(/export interface ApiHandlerOptions \{([\s\S]*?)\}/m)
- if (!optionsMatch) {
- throw new Error("Could not find ApiHandlerOptions interface")
- }
- const optionsContent = optionsMatch[1]
- const configFields = parseConfigurationFields(optionsContent, providerApiKeyMap, apiSecretsFields)
- // Extract model definitions for each provider
- const modelDefinitions = extractModelDefinitions(content)
- // Extract default model IDs from TypeScript constants
- const defaultModelIds = extractDefaultModelIds(content)
- console.log(chalk.green(` Extracted ${Object.keys(defaultModelIds).length} default model IDs`))
- // Filter config fields to only include whitelisted providers
- const filteredConfigFields = configFields.filter(
- (field) =>
- // Include fields for whitelisted providers
- filteredProviderIds.includes(field.category) ||
- // Include general fields that apply to all providers
- field.category === "general",
- )
- // Filter model definitions to only include whitelisted providers
- const filteredModelDefinitions = Object.fromEntries(
- Object.entries(modelDefinitions).filter(([providerId]) => filteredProviderIds.includes(providerId)),
- )
- console.log(
- chalk.cyan(
- ` Filtered config fields: ${configFields.length} -> ${filteredConfigFields.length} (${configFields.length - filteredConfigFields.length} excluded)`,
- ),
- )
- console.log(
- chalk.cyan(
- ` Filtered model definitions: ${Object.keys(modelDefinitions).length} -> ${Object.keys(filteredModelDefinitions).length} (${Object.keys(modelDefinitions).length - Object.keys(filteredModelDefinitions).length} excluded)`,
- ),
- )
- return {
- providers: filteredProviderIds,
- configFields: filteredConfigFields,
- modelDefinitions: filteredModelDefinitions,
- defaultModelIds,
- providerApiKeyMap,
- }
- }
- /**
- * Parse configuration fields from ApiHandlerOptions and ApiHandlerSecrets
- */
- function parseConfigurationFields(optionsContent, providerApiKeyMap, apiSecretsFields) {
- const fields = []
- // FIRST: Add API key fields from ApiHandlerSecrets
- // These are the actual authentication fields that need to be collected
- for (const fieldName of apiSecretsFields.fieldNames) {
- const fieldInfo = apiSecretsFields.fields[fieldName]
- const lowerName = fieldName.toLowerCase()
- // Determine which provider this field belongs to
- let category = "general"
- for (const [providerId, apiKeys] of Object.entries(providerApiKeyMap)) {
- if (apiKeys.includes(fieldName)) {
- category = providerId
- break
- }
- }
- // All API key fields are required for their respective provider
- const required = true
- const fieldType = "password"
- const placeholder = "Enter your API key"
- fields.push({
- name: fieldName,
- type: fieldInfo.type,
- comment: fieldInfo.comment || "",
- category,
- required,
- fieldType,
- placeholder,
- })
- }
- // SECOND: Add configuration fields from ApiHandlerOptions
- // Match field definitions like: fieldName?: type // comment
- const fieldMatches = optionsContent.matchAll(/^\s*([a-zA-Z][a-zA-Z0-9_]*)\?\s*:\s*([^/\n]+)(?:\/\/\s*(.*))?$/gm)
- for (const match of fieldMatches) {
- const [, name, type, comment] = match
- // Skip mode-specific fields (we'll handle those separately)
- if (name.includes("planMode") || name.includes("actMode")) {
- continue
- }
- const lowerName = name.toLowerCase()
- // Determine field category based on provider-specific prefixes FIRST
- let category = "general"
- let required = false
- let fieldType = "string"
- let placeholder = ""
- // Check for provider-specific prefixes to categorize appropriately
- const providerPrefixes = [
- "anthropic",
- "openrouter",
- "aws",
- "bedrock",
- "vertex",
- "openai",
- "ollama",
- "lmstudio",
- "gemini",
- "deepseek",
- "qwen",
- "doubao",
- "mistral",
- "litellm",
- "moonshot",
- "nebius",
- "fireworks",
- "asksage",
- "xai",
- "sambanova",
- "cerebras",
- "sapaicore",
- "groq",
- "huggingface",
- "huawei",
- "dify",
- "baseten",
- "vercel",
- "zai",
- "requesty",
- "together",
- "claudecode",
- "cline",
- ]
- // If field name starts with or contains a provider prefix, categorize it as provider-specific
- for (const prefix of providerPrefixes) {
- if (lowerName.startsWith(prefix) || lowerName.includes(prefix)) {
- category = prefix
- break
- }
- }
- // Set field type metadata for UI rendering
- if (lowerName.includes("apikey")) {
- fieldType = "password"
- placeholder = "Enter your API key"
- } else if (lowerName.includes("key") && !lowerName.includes("apikey")) {
- fieldType = "password"
- placeholder = "Enter your key"
- } else if (lowerName.includes("url") || lowerName.includes("endpoint")) {
- fieldType = "url"
- placeholder = "https://api.example.com"
- } else if (lowerName.includes("region")) {
- fieldType = "select"
- } else if (lowerName.includes("model")) {
- // model fields stay with their provider category
- }
- // Check if this field is required for any provider using the auto-discovered API key map
- // A field is marked as required if it appears in any provider's required fields list
- for (const [providerId, requiredFields] of Object.entries(providerApiKeyMap)) {
- if (requiredFields.includes(name)) {
- required = true
- break
- }
- }
- fields.push({
- name,
- type: type.trim(),
- comment: comment?.trim() || "",
- category,
- required,
- fieldType,
- placeholder,
- })
- }
- return fields
- }
- /**
- * Extract model definitions for each provider
- */
- function extractModelDefinitions(content) {
- const modelDefinitions = {}
- // Find all model constant definitions like: export const anthropicModels = {
- const modelMatches = content.matchAll(/export const (\w+)Models = \{([\s\S]*?)\} as const/g)
- for (const match of modelMatches) {
- const [, providerPrefix, modelsContent] = match
- // Parse individual model entries
- const models = {}
- const modelEntryMatches = modelsContent.matchAll(/"([^"]+)":\s*\{([\s\S]*?)\},?/g)
- for (const modelMatch of modelEntryMatches) {
- const [, modelId, modelContent] = modelMatch
- // Parse model properties
- const modelInfo = parseModelInfo(modelContent)
- models[modelId] = modelInfo
- }
- // Map provider prefix to actual provider ID
- const providerMapping = {
- anthropic: "anthropic",
- claudeCode: "claude-code",
- bedrock: "bedrock",
- vertex: "vertex",
- openAiNative: "openai-native",
- gemini: "gemini",
- deepSeek: "deepseek",
- huggingFace: "huggingface",
- qwen: "qwen",
- doubao: "doubao",
- mistral: "mistral",
- xai: "xai",
- sambanova: "sambanova",
- cerebras: "cerebras",
- sapAiCore: "sapaicore",
- moonshot: "moonshot",
- huaweiCloudMaas: "huawei-cloud-maas",
- baseten: "baseten",
- fireworks: "fireworks",
- groq: "groq",
- nebius: "nebius",
- askSage: "asksage",
- qwenCode: "qwen-code",
- }
- const providerId = providerMapping[providerPrefix] || providerPrefix.toLowerCase()
- if (Object.keys(models).length > 0) {
- modelDefinitions[providerId] = models
- }
- }
- return modelDefinitions
- }
- /**
- * Parse model information from model definition content
- */
- function parseModelInfo(modelContent) {
- const info = {}
- // Parse numeric properties
- const numericProps = ["maxTokens", "contextWindow", "inputPrice", "outputPrice", "cacheWritesPrice", "cacheReadsPrice"]
- for (const prop of numericProps) {
- const match = modelContent.match(new RegExp(`${prop}:\\s*([0-9_,]+)`))
- if (match) {
- info[prop] = parseInt(match[1].replace(/[_,]/g, ""), 10)
- }
- }
- // Parse boolean properties
- const booleanProps = ["supportsImages", "supportsPromptCache"]
- for (const prop of booleanProps) {
- const match = modelContent.match(new RegExp(`${prop}:\\s*(true|false)`))
- if (match) {
- info[prop] = match[1] === "true"
- }
- }
- // Parse description
- const descMatch = modelContent.match(/description:\s*"([^"]*)"/)
- if (descMatch) {
- info.description = descMatch[1]
- }
- return info
- }
- /**
- * Generate Go structs from parsed data
- */
- function generateGoCode(data) {
- console.log(chalk.cyan("Generating Go code..."))
- const { providers, configFields, modelDefinitions } = data
- // Generate provider constants
- const providerConstants = providers.map((p) => `\t${p.toUpperCase().replace(/-/g, "_")} = "${p}"`).join("\n")
- // Generate configuration field definitions
- const configFieldsJson = JSON.stringify(configFields, null, 2)
- .split("\n")
- .map((line) => `\t${line}`)
- .join("\n")
- // Generate model definitions
- const modelDefinitionsJson = JSON.stringify(modelDefinitions, null, 2)
- .split("\n")
- .map((line) => `\t${line}`)
- .join("\n")
- // Generate provider metadata
- const providerMetadata = generateProviderMetadata(providers, configFields, modelDefinitions, data.defaultModelIds)
- return `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
- // Generated by scripts/generate-provider-definitions.mjs
- // Source: src/shared/api.ts
- //
- // ============================================================================
- // DATA CONTRACT & DOCUMENTATION
- // ============================================================================
- //
- // This file provides structured provider metadata extracted from TypeScript source.
- // It serves as the bridge between the VSCode extension's TypeScript API definitions
- // and the CLI's Go-based setup wizard.
- //
- // CORE STRUCTURES
- // ===============
- //
- // ConfigField: Individual configuration fields with type, category, and validation metadata
- // - Name: Field name as it appears in ApiHandlerOptions (e.g., "cerebrasApiKey")
- // - Type: TypeScript type (e.g., "string", "number")
- // - Comment: Inline comment from TypeScript source
- // - Category: Provider categorization (e.g., "cerebras", "general")
- // - Required: Whether this field MUST be collected for any provider
- // - FieldType: UI field type hint ("password", "url", "string", "select")
- // - Placeholder: Suggested placeholder text for UI input
- //
- // ModelInfo: Model capabilities, pricing, and limits
- // - MaxTokens: Maximum output tokens
- // - ContextWindow: Total context window size
- // - SupportsImages: Whether model accepts image inputs
- // - SupportsPromptCache: Whether model supports prompt caching
- // - InputPrice: Cost per 1M input tokens (USD)
- // - OutputPrice: Cost per 1M output tokens (USD)
- // - CacheWritesPrice: Cost per 1M cached tokens written (USD)
- // - CacheReadsPrice: Cost per 1M cached tokens read (USD)
- // - Description: Human-readable model description
- //
- // ProviderDefinition: Complete provider metadata including required/optional fields
- // - ID: Provider identifier (e.g., "cerebras", "anthropic")
- // - Name: Human-readable display name (e.g., "Cerebras", "Anthropic (Claude)")
- // - RequiredFields: Fields that MUST be collected (filtered by category + overrides)
- // - OptionalFields: Fields that MAY be collected (filtered by category + overrides)
- // - Models: Map of model IDs to ModelInfo
- // - DefaultModelID: Recommended default model from TypeScript source
- // - HasDynamicModels: Whether provider supports runtime model discovery
- // - SetupInstructions: User-facing setup guidance
- //
- // FIELD FILTERING LOGIC
- // =====================
- //
- // Fields are categorized during parsing based on provider-specific prefixes in field names:
- // - "cerebrasApiKey" → category="cerebras"
- // - "awsAccessKey" → category="aws" (used by bedrock)
- // - "requestTimeoutMs" → category="general" (applies to all providers)
- //
- // The getFieldsByProvider() function filters fields using this priority:
- // 1. Check field_overrides.go via GetFieldOverride() for manual corrections
- // 2. Match field.Category against provider ID (primary filtering)
- // 3. Apply hardcoded switch cases for complex provider relationships
- // 4. Include universal fields (requestTimeoutMs, ulid, clineAccountId) for all providers
- //
- // Required vs Optional:
- // - Fields are marked as required if they appear in the providerRequiredFields map
- // in the generator script (scripts/generate-provider-definitions.mjs)
- // - getFieldsByProvider() respects the required parameter to separate required/optional
- //
- // MODEL SELECTION
- // ===============
- //
- // DefaultModelID extraction priority:
- // 1. Exact match from TypeScript constant (e.g., cerebrasDefaultModelId = "llama-3.3-70b")
- // 2. Pattern matching on model IDs ("latest", "default", "sonnet", "gpt-4", etc.)
- // 3. First model in the models map
- //
- // Models map contains full capability and pricing data extracted from TypeScript model
- // definitions (e.g., cerebrasModels, anthropicModels).
- //
- // HasDynamicModels indicates providers that support runtime model discovery via API
- // (e.g., OpenRouter, Ollama, LM Studio). For these providers, the models map may be
- // incomplete or a representative sample.
- //
- // USAGE EXAMPLE
- // =============
- //
- // def, err := GetProviderDefinition("cerebras")
- // if err != nil {
- // return err
- // }
- //
- // // Collect required fields from user
- // for _, field := range def.RequiredFields {
- // value := promptUser(field.Name, field.Placeholder, field.FieldType == "password")
- // config[field.Name] = value
- // }
- //
- // // Use default model or let user choose
- // if def.DefaultModelID != "" {
- // config["modelId"] = def.DefaultModelID
- // }
- //
- // EXTENDING & OVERRIDING
- // ======================
- //
- // DO NOT modify this generated file directly. Changes will be lost on regeneration.
- //
- // To fix incorrect field categorization:
- // - Edit cli/pkg/generated/field_overrides.go
- // - Add entries to GetFieldOverride() function
- // - Example: Force "awsSessionToken" to be relevant for "bedrock"
- //
- // To change required fields:
- // - Edit providerRequiredFields map in scripts/generate-provider-definitions.mjs
- // - Rerun: npm run generate-provider-definitions
- //
- // To add new providers:
- // - Add to ApiProvider type in src/shared/api.ts
- // - Add fields to ApiHandlerOptions with provider-specific prefixes
- // - Optionally add model definitions (e.g., export const newProviderModels = {...})
- // - Rerun generator
- //
- // To fix default model extraction:
- // - Ensure TypeScript source has: export const <provider>DefaultModelId = "model-id"
- // - Or update extractDefaultModelIds() patterns in generator script
- //
- // For upstream changes:
- // - Submit pull request to src/shared/api.ts in the main repository
- //
- // ============================================================================
- package generated
- import (
- "encoding/json"
- "fmt"
- "strings"
- )
- // Provider constants
- const (
- ${providerConstants}
- )
- // AllProviders returns a slice of enabled provider IDs for the CLI build.
- // This is a filtered subset of all providers available in the VSCode extension.
- // To modify which providers are included, edit ENABLED_PROVIDERS in scripts/cli-providers.mjs
- var AllProviders = []string{
- ${providers.map((p) => `\t"${p}",`).join("\n")}
- }
- // ConfigField represents a configuration field requirement
- type ConfigField struct {
- Name string \`json:"name"\`
- Type string \`json:"type"\`
- Comment string \`json:"comment"\`
- Category string \`json:"category"\`
- Required bool \`json:"required"\`
- FieldType string \`json:"fieldType"\`
- Placeholder string \`json:"placeholder"\`
- }
- // ModelInfo represents model capabilities and pricing
- type ModelInfo struct {
- MaxTokens int \`json:"maxTokens,omitempty"\`
- ContextWindow int \`json:"contextWindow,omitempty"\`
- SupportsImages bool \`json:"supportsImages"\`
- SupportsPromptCache bool \`json:"supportsPromptCache"\`
- InputPrice float64 \`json:"inputPrice,omitempty"\`
- OutputPrice float64 \`json:"outputPrice,omitempty"\`
- CacheWritesPrice float64 \`json:"cacheWritesPrice,omitempty"\`
- CacheReadsPrice float64 \`json:"cacheReadsPrice,omitempty"\`
- Description string \`json:"description,omitempty"\`
- }
- // ProviderDefinition represents a provider's metadata and requirements
- type ProviderDefinition struct {
- ID string \`json:"id"\`
- Name string \`json:"name"\`
- RequiredFields []ConfigField \`json:"requiredFields"\`
- OptionalFields []ConfigField \`json:"optionalFields"\`
- Models map[string]ModelInfo \`json:"models"\`
- DefaultModelID string \`json:"defaultModelId"\`
- HasDynamicModels bool \`json:"hasDynamicModels"\`
- SetupInstructions string \`json:"setupInstructions"\`
- }
- // Raw configuration fields data (parsed from TypeScript)
- var rawConfigFields = \`${configFieldsJson.replace(/`/g, '` + "`" + `')}\`
- // Raw model definitions data (parsed from TypeScript)
- var rawModelDefinitions = \`${modelDefinitionsJson.replace(/`/g, '` + "`" + `')}\`
- // GetConfigFields returns all configuration fields
- func GetConfigFields() ([]ConfigField, error) {
- var fields []ConfigField
- if err := json.Unmarshal([]byte(rawConfigFields), &fields); err != nil {
- return nil, fmt.Errorf("failed to parse config fields: %w", err)
- }
- return fields, nil
- }
- // GetModelDefinitions returns all model definitions
- func GetModelDefinitions() (map[string]map[string]ModelInfo, error) {
- var models map[string]map[string]ModelInfo
- if err := json.Unmarshal([]byte(rawModelDefinitions), &models); err != nil {
- return nil, fmt.Errorf("failed to parse model definitions: %w", err)
- }
- return models, nil
- }
- // GetProviderDefinition returns the definition for a specific provider
- func GetProviderDefinition(providerID string) (*ProviderDefinition, error) {
- definitions, err := GetProviderDefinitions()
- if err != nil {
- return nil, err
- }
-
- def, exists := definitions[providerID]
- if !exists {
- return nil, fmt.Errorf("provider %s not found", providerID)
- }
-
- return &def, nil
- }
- // GetProviderDefinitions returns all provider definitions
- func GetProviderDefinitions() (map[string]ProviderDefinition, error) {
- configFields, err := GetConfigFields()
- if err != nil {
- return nil, err
- }
-
- modelDefinitions, err := GetModelDefinitions()
- if err != nil {
- return nil, err
- }
-
- definitions := make(map[string]ProviderDefinition)
-
- ${providerMetadata}
-
- return definitions, nil
- }
- // IsValidProvider checks if a provider ID is valid
- func IsValidProvider(providerID string) bool {
- for _, p := range AllProviders {
- if p == providerID {
- return true
- }
- }
- return false
- }
- // GetProviderDisplayName returns a human-readable name for a provider
- func GetProviderDisplayName(providerID string) string {
- displayNames := map[string]string{
- ${providers.map((p) => `\t\t"${p}": "${getProviderDisplayName(p)}",`).join("\n")}
- }
-
- if name, exists := displayNames[providerID]; exists {
- return name
- }
- return providerID
- }
- // getFieldsByProvider filters configuration fields by provider and requirement
- // Uses category field as primary filter with override support
- func getFieldsByProvider(providerID string, allFields []ConfigField, required bool) []ConfigField {
- var fields []ConfigField
-
- for _, field := range allFields {
- fieldName := strings.ToLower(field.Name)
- fieldCategory := strings.ToLower(field.Category)
- providerName := strings.ToLower(providerID)
-
- isRelevant := false
-
- // Priority 1: Check manual overrides FIRST (from GetFieldOverride in this package)
- if override, hasOverride := GetFieldOverride(providerID, field.Name); hasOverride {
- isRelevant = override
- } else if fieldCategory == providerName {
- // Priority 2: Direct category match (primary filtering mechanism)
- isRelevant = true
- } else if fieldCategory == "aws" && providerID == "bedrock" {
- // Priority 3: Handle provider-specific category relationships
- // AWS fields are used by Bedrock provider
- isRelevant = true
- } else if fieldCategory == "openai" && providerID == "openai-native" {
- // OpenAI fields used by openai-native
- isRelevant = true
- } else if fieldCategory == "general" {
- // Priority 4: Universal fields that apply to all providers
- // Note: ulid is excluded as it's auto-generated and users should not set it
- universalFields := []string{"requesttimeoutms", "clineaccountid"}
- for _, universal := range universalFields {
- if fieldName == universal {
- isRelevant = true
- break
- }
- }
- }
-
- if isRelevant && field.Required == required {
- fields = append(fields, field)
- }
- }
-
- return fields
- }
- `
- }
- /**
- * Generate provider metadata for each provider
- */
- function generateProviderMetadata(providers, configFields, modelDefinitions, defaultModelIds) {
- return providers
- .map((providerId) => {
- const displayName = getProviderDisplayName(providerId)
- const models = modelDefinitions[providerId] || {}
- const defaultModelId = getDefaultModelId(providerId, models, defaultModelIds)
- const hasDynamicModels = hasDynamicModelsSupport(providerId)
- const setupInstructions = getSetupInstructions(providerId)
- return `\t// ${displayName}
- definitions["${providerId}"] = ProviderDefinition{
- ID: "${providerId}",
- Name: "${displayName}",
- RequiredFields: getFieldsByProvider("${providerId}", configFields, true),
- OptionalFields: getFieldsByProvider("${providerId}", configFields, false),
- Models: modelDefinitions["${providerId}"],
- DefaultModelID: "${defaultModelId}",
- HasDynamicModels: ${hasDynamicModels},
- SetupInstructions: \`${setupInstructions}\`,
- }`
- })
- .join("\n\n")
- }
- /**
- * Get human-readable display name for a provider
- */
- function getProviderDisplayName(providerId) {
- const displayNames = {
- anthropic: "Anthropic (Claude)",
- "claude-code": "Claude Code",
- openrouter: "OpenRouter",
- bedrock: "AWS Bedrock",
- vertex: "Google Vertex AI",
- openai: "OpenAI Compatible",
- ollama: "Ollama",
- lmstudio: "LM Studio",
- gemini: "Google Gemini",
- "openai-native": "OpenAI",
- requesty: "Requesty",
- together: "Together AI",
- deepseek: "DeepSeek",
- qwen: "Qwen",
- "qwen-code": "Qwen Code",
- doubao: "Doubao",
- mistral: "Mistral AI",
- "vscode-lm": "VSCode Language Models",
- cline: "Cline",
- litellm: "LiteLLM",
- moonshot: "Moonshot AI",
- nebius: "Nebius AI",
- fireworks: "Fireworks AI",
- asksage: "AskSage",
- xai: "X AI (Grok)",
- sambanova: "SambaNova",
- cerebras: "Cerebras",
- sapaicore: "SAP AI Core",
- groq: "Groq",
- huggingface: "Hugging Face",
- "huawei-cloud-maas": "Huawei Cloud MaaS",
- dify: "Dify",
- baseten: "Baseten",
- "vercel-ai-gateway": "Vercel AI Gateway",
- zai: "Z AI",
- }
- return displayNames[providerId] || providerId.charAt(0).toUpperCase() + providerId.slice(1)
- }
- /**
- * Get default model ID for a provider
- */
- function getDefaultModelId(providerId, models, defaultModelIds) {
- // First, check if we have an extracted default from TypeScript source
- if (defaultModelIds && defaultModelIds[providerId]) {
- return defaultModelIds[providerId]
- }
- // Fallback to pattern matching if no explicit default was found
- const modelIds = Object.keys(models)
- if (modelIds.length === 0) return ""
- // Look for common default patterns
- const defaultPatterns = ["latest", "default", "sonnet", "gpt-4", "claude-3", "gemini-pro"]
- for (const pattern of defaultPatterns) {
- const match = modelIds.find((id) => id.toLowerCase().includes(pattern))
- if (match) return match
- }
- // Return first model if no pattern matches
- return modelIds[0]
- }
- /**
- * Check if provider supports dynamic model fetching
- */
- function hasDynamicModelsSupport(providerId) {
- // Providers that support dynamic model fetching
- const dynamicProviders = [
- "openrouter",
- "openai",
- "openai-native",
- "ollama",
- "lmstudio",
- "litellm",
- "together",
- "fireworks",
- "groq",
- ]
- return dynamicProviders.includes(providerId)
- }
- /**
- * Get setup instructions for a provider
- */
- function getSetupInstructions(providerId) {
- const instructions = {
- anthropic: "Get your API key from https://console.anthropic.com/",
- openrouter: "Get your API key from https://openrouter.ai/keys",
- bedrock: "Configure AWS credentials with Bedrock access permissions",
- vertex: "Set up Google Cloud project with Vertex AI API enabled",
- openai: "Get your API key from https://platform.openai.com/api-keys",
- "openai-native": "Get your API key from your API provider",
- ollama: "Install Ollama locally and ensure it's running on the specified port",
- lmstudio: "Install LM Studio and start the local server",
- gemini: "Get your API key from https://makersuite.google.com/app/apikey",
- deepseek: "Get your API key from https://platform.deepseek.com/",
- qwen: "Get your API key from Alibaba Cloud DashScope",
- doubao: "Get your API key from ByteDance Volcano Engine",
- mistral: "Get your API key from https://console.mistral.ai/",
- xai: "Get your API key from https://console.x.ai/",
- groq: "Get your API key from https://console.groq.com/keys",
- cerebras: "Get your API key from https://cloud.cerebras.ai/",
- fireworks: "Get your API key from https://fireworks.ai/",
- }
- return instructions[providerId] || `Configure ${getProviderDisplayName(providerId)} API credentials`
- }
- /**
- * Main function to generate provider definitions
- */
- async function main() {
- try {
- console.log(chalk.cyan("Starting provider definitions generation..."))
- // Parse TypeScript API definitions
- const data = await parseApiDefinitions()
- // Generate Go code
- const goCode = generateGoCode(data)
- // Ensure output directory exists
- const outputDir = path.dirname(GO_OUTPUT_FILE)
- await fs.mkdir(outputDir, { recursive: true })
- // Write Go file
- await fs.writeFile(GO_OUTPUT_FILE, goCode)
- console.log(chalk.green(`Successfully generated provider definitions:`))
- console.log(chalk.green(` Output: ${GO_OUTPUT_FILE}`))
- console.log(chalk.green(` Providers: ${data.providers.length}`))
- console.log(chalk.green(` Config fields: ${data.configFields.length}`))
- console.log(chalk.green(` Model definitions: ${Object.keys(data.modelDefinitions).length} providers`))
- } catch (error) {
- console.error(chalk.red("ERROR generating provider definitions:"), error.message)
- if (error.stack) {
- console.error(chalk.gray(error.stack))
- }
- process.exit(1)
- }
- }
- // Add helper function to the generated Go code
- const helperFunction = `
- // getFieldsByProvider filters configuration fields by provider and requirement
- func getFieldsByProvider(providerID string, allFields []ConfigField, required bool) []ConfigField {
- var fields []ConfigField
-
- for _, field := range allFields {
- // Check if field is relevant to this provider
- fieldName := strings.ToLower(field.Name)
- providerName := strings.ToLower(providerID)
-
- isRelevant := false
-
- // Direct provider name match
- if strings.Contains(fieldName, providerName) {
- isRelevant = true
- }
-
- // Provider-specific field mappings
- switch providerID {
- case "anthropic":
- isRelevant = strings.Contains(fieldName, "apikey") || strings.Contains(fieldName, "anthropic")
- case "openrouter":
- isRelevant = strings.Contains(fieldName, "openrouter")
- case "bedrock":
- isRelevant = strings.Contains(fieldName, "aws") || strings.Contains(fieldName, "bedrock")
- case "vertex":
- isRelevant = strings.Contains(fieldName, "vertex")
- case "openai", "openai-native":
- isRelevant = strings.Contains(fieldName, "openai")
- case "ollama":
- isRelevant = strings.Contains(fieldName, "ollama")
- case "lmstudio":
- isRelevant = strings.Contains(fieldName, "lmstudio")
- case "gemini":
- isRelevant = strings.Contains(fieldName, "gemini")
- }
-
- // General fields that apply to all providers
- if field.Category == "general" {
- isRelevant = true
- }
-
- if isRelevant && field.Required == required {
- fields = append(fields, field)
- }
- }
-
- return fields
- }`
- // Run if this script is executed directly
- if (import.meta.url === `file://${process.argv[1]}`) {
- main()
- }
|