| // Package config provides configuration management for the CLI Proxy API server. | |
| // It handles loading and parsing YAML configuration files, and provides structured | |
| // access to application settings including server port, authentication directory, | |
| // debug settings, proxy configuration, and API keys. | |
| package config | |
| import ( | |
| "errors" | |
| "fmt" | |
| "os" | |
| "strings" | |
| "syscall" | |
| "gopkg.in/yaml.v3" | |
| ) | |
| const DefaultPanelGitHubRepository = "https://github.com/router-for-me/Cli-Proxy-API-Management-Center" | |
| // Config represents the application's configuration, loaded from a YAML file. | |
| type Config struct { | |
| SDKConfig `yaml:",inline"` | |
| // Host is the network host/interface on which the API server will bind. | |
| // Default is empty ("") to bind all interfaces (IPv4 + IPv6). Use "127.0.0.1" or "localhost" for local-only access. | |
| Host string `yaml:"host" json:"-"` | |
| // Port is the network port on which the API server will listen. | |
| Port int `yaml:"port" json:"-"` | |
| // TLS config controls HTTPS server settings. | |
| TLS TLSConfig `yaml:"tls" json:"tls"` | |
| // RemoteManagement nests management-related options under 'remote-management'. | |
| RemoteManagement RemoteManagement `yaml:"remote-management" json:"-"` | |
| // AuthDir is the directory where authentication token files are stored. | |
| AuthDir string `yaml:"auth-dir" json:"-"` | |
| // Debug enables or disables debug-level logging and other debug features. | |
| Debug bool `yaml:"debug" json:"debug"` | |
| // CommercialMode disables high-overhead HTTP middleware features to minimize per-request memory usage. | |
| CommercialMode bool `yaml:"commercial-mode" json:"commercial-mode"` | |
| // LoggingToFile controls whether application logs are written to rotating files or stdout. | |
| LoggingToFile bool `yaml:"logging-to-file" json:"logging-to-file"` | |
| // LogsMaxTotalSizeMB limits the total size (in MB) of log files under the logs directory. | |
| // When exceeded, the oldest log files are deleted until within the limit. Set to 0 to disable. | |
| LogsMaxTotalSizeMB int `yaml:"logs-max-total-size-mb" json:"logs-max-total-size-mb"` | |
| // UsageStatisticsEnabled toggles in-memory usage aggregation; when false, usage data is discarded. | |
| UsageStatisticsEnabled bool `yaml:"usage-statistics-enabled" json:"usage-statistics-enabled"` | |
| // DisableCooling disables quota cooldown scheduling when true. | |
| DisableCooling bool `yaml:"disable-cooling" json:"disable-cooling"` | |
| // RequestRetry defines the retry times when the request failed. | |
| RequestRetry int `yaml:"request-retry" json:"request-retry"` | |
| // MaxRetryInterval defines the maximum wait time in seconds before retrying a cooled-down credential. | |
| MaxRetryInterval int `yaml:"max-retry-interval" json:"max-retry-interval"` | |
| // QuotaExceeded defines the behavior when a quota is exceeded. | |
| QuotaExceeded QuotaExceeded `yaml:"quota-exceeded" json:"quota-exceeded"` | |
| // Routing controls credential selection behavior. | |
| Routing RoutingConfig `yaml:"routing" json:"routing"` | |
| // WebsocketAuth enables or disables authentication for the WebSocket API. | |
| WebsocketAuth bool `yaml:"ws-auth" json:"ws-auth"` | |
| // CodexInstructionsEnabled controls whether official Codex instructions are injected. | |
| // When false (default), CodexInstructionsForModel returns immediately without modification. | |
| // When true, the original instruction injection logic is used. | |
| CodexInstructionsEnabled bool `yaml:"codex-instructions-enabled" json:"codex-instructions-enabled"` | |
| // GeminiKey defines Gemini API key configurations with optional routing overrides. | |
| GeminiKey []GeminiKey `yaml:"gemini-api-key" json:"gemini-api-key"` | |
| // Codex defines a list of Codex API key configurations as specified in the YAML configuration file. | |
| CodexKey []CodexKey `yaml:"codex-api-key" json:"codex-api-key"` | |
| // ClaudeKey defines a list of Claude API key configurations as specified in the YAML configuration file. | |
| ClaudeKey []ClaudeKey `yaml:"claude-api-key" json:"claude-api-key"` | |
| // KiroKey defines a list of Kiro API credential configurations (Amazon Q Developer / AWS CodeWhisperer). | |
| KiroKey []KiroKey `yaml:"kiro-api-key" json:"kiro-api-key"` | |
| // OpenAICompatibility defines OpenAI API compatibility configurations for external providers. | |
| OpenAICompatibility []OpenAICompatibility `yaml:"openai-compatibility" json:"openai-compatibility"` | |
| // VertexCompatAPIKey defines Vertex AI-compatible API key configurations for third-party providers. | |
| // Used for services that use Vertex AI-style paths but with simple API key authentication. | |
| VertexCompatAPIKey []VertexCompatKey `yaml:"vertex-api-key" json:"vertex-api-key"` | |
| // AmpCode contains Amp CLI upstream configuration, management restrictions, and model mappings. | |
| AmpCode AmpCode `yaml:"ampcode" json:"ampcode"` | |
| // Antigravity contains configuration for the Antigravity provider. | |
| Antigravity AntigravityConfig `yaml:"antigravity" json:"antigravity"` | |
| // OAuthExcludedModels defines per-provider global model exclusions applied to OAuth/file-backed auth entries. | |
| OAuthExcludedModels map[string][]string `yaml:"oauth-excluded-models,omitempty" json:"oauth-excluded-models,omitempty"` | |
| // OAuthModelAlias defines global model name aliases for OAuth/file-backed auth channels. | |
| // These aliases affect both model listing and model routing for supported channels: | |
| // gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow. | |
| // | |
| // NOTE: This does not apply to existing per-credential model alias features under: | |
| // gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, and ampcode. | |
| OAuthModelAlias map[string][]OAuthModelAlias `yaml:"oauth-model-alias,omitempty" json:"oauth-model-alias,omitempty"` | |
| // Payload defines default and override rules for provider payload parameters. | |
| Payload PayloadConfig `yaml:"payload" json:"payload"` | |
| legacyMigrationPending bool `yaml:"-" json:"-"` | |
| } | |
| // TLSConfig holds HTTPS server settings. | |
| type TLSConfig struct { | |
| // Enable toggles HTTPS server mode. | |
| Enable bool `yaml:"enable" json:"enable"` | |
| // Cert is the path to the TLS certificate file. | |
| Cert string `yaml:"cert" json:"cert"` | |
| // Key is the path to the TLS private key file. | |
| Key string `yaml:"key" json:"key"` | |
| } | |
| // RemoteManagement holds management API configuration under 'remote-management'. | |
| type RemoteManagement struct { | |
| // AllowRemote toggles remote (non-localhost) access to management API. | |
| AllowRemote bool `yaml:"allow-remote"` | |
| // SecretKey is the management key (plaintext or bcrypt hashed). YAML key intentionally 'secret-key'. | |
| SecretKey string `yaml:"secret-key"` | |
| // DisableControlPanel skips serving and syncing the bundled management UI when true. | |
| DisableControlPanel bool `yaml:"disable-control-panel"` | |
| // PanelGitHubRepository overrides the GitHub repository used to fetch the management panel asset. | |
| // Accepts either a repository URL (https://github.com/org/repo) or an API releases endpoint. | |
| PanelGitHubRepository string `yaml:"panel-github-repository"` | |
| } | |
| // QuotaExceeded defines the behavior when API quota limits are exceeded. | |
| // It provides configuration options for automatic failover mechanisms. | |
| type QuotaExceeded struct { | |
| // SwitchProject indicates whether to automatically switch to another project when a quota is exceeded. | |
| SwitchProject bool `yaml:"switch-project" json:"switch-project"` | |
| // SwitchPreviewModel indicates whether to automatically switch to a preview model when a quota is exceeded. | |
| SwitchPreviewModel bool `yaml:"switch-preview-model" json:"switch-preview-model"` | |
| } | |
| // RoutingConfig configures how credentials are selected for requests. | |
| type RoutingConfig struct { | |
| // Strategy selects the credential selection strategy. | |
| // Supported values: "round-robin" (default), "fill-first". | |
| Strategy string `yaml:"strategy,omitempty" json:"strategy,omitempty"` | |
| } | |
| // OAuthModelAlias defines a model ID alias for a specific channel. | |
| // It maps the upstream model name (Name) to the client-visible alias (Alias). | |
| // When Fork is true, the alias is added as an additional model in listings while | |
| // keeping the original model ID available. | |
| type OAuthModelAlias struct { | |
| Name string `yaml:"name" json:"name"` | |
| Alias string `yaml:"alias" json:"alias"` | |
| Fork bool `yaml:"fork,omitempty" json:"fork,omitempty"` | |
| } | |
| // AmpModelMapping defines a model name mapping for Amp CLI requests. | |
| // When Amp requests a model that isn't available locally, this mapping | |
| // allows routing to an alternative model that IS available. | |
| type AmpModelMapping struct { | |
| // From is the model name that Amp CLI requests (e.g., "claude-opus-4.5"). | |
| From string `yaml:"from" json:"from"` | |
| // To is the target model name to route to (e.g., "claude-sonnet-4"). | |
| // The target model must have available providers in the registry. | |
| To string `yaml:"to" json:"to"` | |
| // Regex indicates whether the 'from' field should be interpreted as a regular | |
| // expression for matching model names. When true, this mapping is evaluated | |
| // after exact matches and in the order provided. Defaults to false (exact match). | |
| Regex bool `yaml:"regex,omitempty" json:"regex,omitempty"` | |
| } | |
| // AmpCode groups Amp CLI integration settings including upstream routing, | |
| // optional overrides, management route restrictions, and model fallback mappings. | |
| type AmpCode struct { | |
| // UpstreamURL defines the upstream Amp control plane used for non-provider calls. | |
| UpstreamURL string `yaml:"upstream-url" json:"upstream-url"` | |
| // UpstreamAPIKey optionally overrides the Authorization header when proxying Amp upstream calls. | |
| UpstreamAPIKey string `yaml:"upstream-api-key" json:"upstream-api-key"` | |
| // UpstreamAPIKeys maps client API keys (from top-level api-keys) to upstream API keys. | |
| // When a client authenticates with a key that matches an entry, that upstream key is used. | |
| // If no match is found, falls back to UpstreamAPIKey (default behavior). | |
| UpstreamAPIKeys []AmpUpstreamAPIKeyEntry `yaml:"upstream-api-keys,omitempty" json:"upstream-api-keys,omitempty"` | |
| // RestrictManagementToLocalhost restricts Amp management routes (/api/user, /api/threads, etc.) | |
| // to only accept connections from localhost (127.0.0.1, ::1). When true, prevents drive-by | |
| // browser attacks and remote access to management endpoints. Default: false (API key auth is sufficient). | |
| RestrictManagementToLocalhost bool `yaml:"restrict-management-to-localhost" json:"restrict-management-to-localhost"` | |
| // ModelMappings defines model name mappings for Amp CLI requests. | |
| // When Amp requests a model that isn't available locally, these mappings | |
| // allow routing to an alternative model that IS available. | |
| ModelMappings []AmpModelMapping `yaml:"model-mappings" json:"model-mappings"` | |
| // ForceModelMappings when true, model mappings take precedence over local API keys. | |
| // When false (default), local API keys are used first if available. | |
| ForceModelMappings bool `yaml:"force-model-mappings" json:"force-model-mappings"` | |
| } | |
| // AmpUpstreamAPIKeyEntry maps a set of client API keys to a specific upstream API key. | |
| // When a request is authenticated with one of the APIKeys, the corresponding UpstreamAPIKey | |
| // is used for the upstream Amp request. | |
| type AmpUpstreamAPIKeyEntry struct { | |
| // UpstreamAPIKey is the API key to use when proxying to the Amp upstream. | |
| UpstreamAPIKey string `yaml:"upstream-api-key" json:"upstream-api-key"` | |
| // APIKeys are the client API keys (from top-level api-keys) that map to this upstream key. | |
| APIKeys []string `yaml:"api-keys" json:"api-keys"` | |
| } | |
| // AntigravityConfig holds configuration for the Antigravity provider. | |
| type AntigravityConfig struct { | |
| BaseURLDaily string `yaml:"base-url-daily" json:"base-url-daily"` | |
| SandboxBaseURLDaily string `yaml:"sandbox-base-url-daily" json:"sandbox-base-url-daily"` | |
| BaseURLProd string `yaml:"base-url-prod" json:"base-url-prod"` | |
| ClientID string `yaml:"client-id" json:"client-id"` | |
| ClientSecret string `yaml:"client-secret" json:"client-secret"` | |
| UserAgent string `yaml:"user-agent" json:"user-agent"` | |
| SystemInstruction string `yaml:"system-instruction" json:"system-instruction"` | |
| } | |
| // PayloadConfig defines default and override parameter rules applied to provider payloads. | |
| type PayloadConfig struct { | |
| // Default defines rules that only set parameters when they are missing in the payload. | |
| Default []PayloadRule `yaml:"default" json:"default"` | |
| // DefaultRaw defines rules that set raw JSON values only when they are missing. | |
| DefaultRaw []PayloadRule `yaml:"default-raw" json:"default-raw"` | |
| // Override defines rules that always set parameters, overwriting any existing values. | |
| Override []PayloadRule `yaml:"override" json:"override"` | |
| // OverrideRaw defines rules that always set raw JSON values, overwriting any existing values. | |
| OverrideRaw []PayloadRule `yaml:"override-raw" json:"override-raw"` | |
| } | |
| // PayloadRule describes a single rule targeting a list of models with parameter updates. | |
| type PayloadRule struct { | |
| // Models lists model entries with name pattern and protocol constraint. | |
| Models []PayloadModelRule `yaml:"models" json:"models"` | |
| // Params maps JSON paths (gjson/sjson syntax) to values written into the payload. | |
| // For *-raw rules, values are treated as raw JSON fragments (strings are used as-is). | |
| Params map[string]any `yaml:"params" json:"params"` | |
| } | |
| // PayloadModelRule ties a model name pattern to a specific translator protocol. | |
| type PayloadModelRule struct { | |
| // Name is the model name or wildcard pattern (e.g., "gpt-*", "*-5", "gemini-*-pro"). | |
| Name string `yaml:"name" json:"name"` | |
| // Protocol restricts the rule to a specific translator format (e.g., "gemini", "responses"). | |
| Protocol string `yaml:"protocol" json:"protocol"` | |
| } | |
| // CloakConfig configures request cloaking for non-Claude-Code clients. | |
| // Cloaking disguises API requests to appear as originating from the official Claude Code CLI. | |
| type CloakConfig struct { | |
| // Mode controls cloaking behavior: "auto" (default), "always", or "never". | |
| // - "auto": cloak only when client is not Claude Code (based on User-Agent) | |
| // - "always": always apply cloaking regardless of client | |
| // - "never": never apply cloaking | |
| Mode string `yaml:"mode,omitempty" json:"mode,omitempty"` | |
| // StrictMode controls how system prompts are handled when cloaking. | |
| // - false (default): prepend Claude Code prompt to user system messages | |
| // - true: strip all user system messages, keep only Claude Code prompt | |
| StrictMode bool `yaml:"strict-mode,omitempty" json:"strict-mode,omitempty"` | |
| // SensitiveWords is a list of words to obfuscate with zero-width characters. | |
| // This can help bypass certain content filters. | |
| SensitiveWords []string `yaml:"sensitive-words,omitempty" json:"sensitive-words,omitempty"` | |
| } | |
| // ClaudeAPIKeyEntry represents an API key configuration with optional proxy setting for Claude. | |
| type ClaudeAPIKeyEntry struct { | |
| // APIKey is the authentication key for accessing Claude API services. | |
| APIKey string `yaml:"api-key" json:"api-key"` | |
| // ProxyURL overrides the global proxy setting for this API key if provided. | |
| ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` | |
| } | |
| // ClaudeKey represents the configuration for a Claude API key, | |
| // including the API key itself and an optional base URL for the API endpoint. | |
| type ClaudeKey struct { | |
| // APIKey is the authentication key for accessing Claude API services. | |
| // Deprecated: Use APIKeyEntries for multiple API keys with round-robin support. | |
| APIKey string `yaml:"api-key" json:"api-key"` | |
| // APIKeyEntries defines API keys with optional per-key proxy configuration. | |
| // When provided, each entry creates a separate Auth for round-robin load balancing. | |
| APIKeyEntries []ClaudeAPIKeyEntry `yaml:"api-key-entries,omitempty" json:"api-key-entries,omitempty"` | |
| // Priority controls selection preference when multiple credentials match. | |
| // Higher values are preferred; defaults to 0. | |
| Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` | |
| // Prefix optionally namespaces models for this credential (e.g., "teamA/claude-sonnet-4"). | |
| Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` | |
| // BaseURL is the base URL for the Claude API endpoint. | |
| // If empty, the default Claude API URL will be used. | |
| BaseURL string `yaml:"base-url" json:"base-url"` | |
| // ProxyURL overrides the global proxy setting for this API key if provided. | |
| // Used as fallback when APIKeyEntries is empty or when entries don't specify ProxyURL. | |
| ProxyURL string `yaml:"proxy-url" json:"proxy-url"` | |
| // Models defines upstream model names and aliases for request routing. | |
| Models []ClaudeModel `yaml:"models" json:"models"` | |
| // Headers optionally adds extra HTTP headers for requests sent with this key. | |
| Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` | |
| // ExcludedModels lists model IDs that should be excluded for this provider. | |
| ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` | |
| // Cloak configures request cloaking for non-Claude-Code clients. | |
| Cloak *CloakConfig `yaml:"cloak,omitempty" json:"cloak,omitempty"` | |
| } | |
| func (k ClaudeKey) GetAPIKey() string { return k.APIKey } | |
| func (k ClaudeKey) GetBaseURL() string { return k.BaseURL } | |
| // ClaudeModel describes a mapping between an alias and the actual upstream model name. | |
| type ClaudeModel struct { | |
| // Name is the upstream model identifier used when issuing requests. | |
| Name string `yaml:"name" json:"name"` | |
| // Alias is the client-facing model name that maps to Name. | |
| Alias string `yaml:"alias" json:"alias"` | |
| } | |
| func (m ClaudeModel) GetName() string { return m.Name } | |
| func (m ClaudeModel) GetAlias() string { return m.Alias } | |
| // KiroKey represents the configuration for a Kiro API credential | |
| // (Amazon Q Developer / AWS CodeWhisperer). | |
| type KiroKey struct { | |
| // RefreshToken is the Kiro refresh token for updating access tokens. | |
| RefreshToken string `yaml:"refresh-token" json:"refresh-token"` | |
| // ProfileARN is the AWS CodeWhisperer profile ARN (optional - auto-fetched from token refresh). | |
| ProfileARN string `yaml:"profile-arn,omitempty" json:"profile-arn,omitempty"` | |
| // Region is the AWS region (default: us-east-1). | |
| Region string `yaml:"region,omitempty" json:"region,omitempty"` | |
| // Priority controls selection preference when multiple credentials match. | |
| // Higher values are preferred; defaults to 0. | |
| Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` | |
| // Prefix optionally namespaces models for this credential. | |
| Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` | |
| // ProxyURL overrides the global proxy setting for this credential if provided. | |
| ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` | |
| // CredentialsFile is an optional path to a JSON credentials file. | |
| CredentialsFile string `yaml:"credentials-file,omitempty" json:"credentials-file,omitempty"` | |
| // KiroCliDBFile is an optional path to kiro-cli SQLite database. | |
| KiroCliDBFile string `yaml:"kiro-cli-db-file,omitempty" json:"kiro-cli-db-file,omitempty"` | |
| // Models defines upstream model names and aliases for request routing. | |
| Models []KiroModel `yaml:"models,omitempty" json:"models,omitempty"` | |
| // Headers optionally adds extra HTTP headers for requests sent with this key. | |
| Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` | |
| // ExcludedModels lists model IDs that should be excluded for this provider. | |
| ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` | |
| } | |
| func (k KiroKey) GetRefreshToken() string { return k.RefreshToken } | |
| func (k KiroKey) GetProfileARN() string { return k.ProfileARN } | |
| func (k KiroKey) GetRegion() string { return k.Region } | |
| // KiroModel describes a mapping between an alias and the actual upstream model name. | |
| type KiroModel struct { | |
| // Name is the upstream model identifier used when issuing requests. | |
| Name string `yaml:"name" json:"name"` | |
| // Alias is the client-facing model name that maps to Name. | |
| Alias string `yaml:"alias" json:"alias"` | |
| } | |
| func (m KiroModel) GetName() string { return m.Name } | |
| func (m KiroModel) GetAlias() string { return m.Alias } | |
| // CodexKey represents the configuration for a Codex API key, | |
| // including the API key itself and an optional base URL for the API endpoint. | |
| type CodexKey struct { | |
| // APIKey is the authentication key for accessing Codex API services. | |
| APIKey string `yaml:"api-key" json:"api-key"` | |
| // Priority controls selection preference when multiple credentials match. | |
| // Higher values are preferred; defaults to 0. | |
| Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` | |
| // Prefix optionally namespaces models for this credential (e.g., "teamA/gpt-5-codex"). | |
| Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` | |
| // BaseURL is the base URL for the Codex API endpoint. | |
| // If empty, the default Codex API URL will be used. | |
| BaseURL string `yaml:"base-url" json:"base-url"` | |
| // ProxyURL overrides the global proxy setting for this API key if provided. | |
| ProxyURL string `yaml:"proxy-url" json:"proxy-url"` | |
| // Models defines upstream model names and aliases for request routing. | |
| Models []CodexModel `yaml:"models" json:"models"` | |
| // Headers optionally adds extra HTTP headers for requests sent with this key. | |
| Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` | |
| // ExcludedModels lists model IDs that should be excluded for this provider. | |
| ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` | |
| } | |
| func (k CodexKey) GetAPIKey() string { return k.APIKey } | |
| func (k CodexKey) GetBaseURL() string { return k.BaseURL } | |
| // CodexModel describes a mapping between an alias and the actual upstream model name. | |
| type CodexModel struct { | |
| // Name is the upstream model identifier used when issuing requests. | |
| Name string `yaml:"name" json:"name"` | |
| // Alias is the client-facing model name that maps to Name. | |
| Alias string `yaml:"alias" json:"alias"` | |
| } | |
| func (m CodexModel) GetName() string { return m.Name } | |
| func (m CodexModel) GetAlias() string { return m.Alias } | |
| // GeminiKey represents the configuration for a Gemini API key, | |
| // including optional overrides for upstream base URL, proxy routing, and headers. | |
| type GeminiKey struct { | |
| // APIKey is the authentication key for accessing Gemini API services. | |
| APIKey string `yaml:"api-key" json:"api-key"` | |
| // Priority controls selection preference when multiple credentials match. | |
| // Higher values are preferred; defaults to 0. | |
| Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` | |
| // Prefix optionally namespaces models for this credential (e.g., "teamA/gemini-3-pro-preview"). | |
| Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` | |
| // BaseURL optionally overrides the Gemini API endpoint. | |
| BaseURL string `yaml:"base-url,omitempty" json:"base-url,omitempty"` | |
| // ProxyURL optionally overrides the global proxy for this API key. | |
| ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` | |
| // Models defines upstream model names and aliases for request routing. | |
| Models []GeminiModel `yaml:"models,omitempty" json:"models,omitempty"` | |
| // Headers optionally adds extra HTTP headers for requests sent with this key. | |
| Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` | |
| // ExcludedModels lists model IDs that should be excluded for this provider. | |
| ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` | |
| } | |
| func (k GeminiKey) GetAPIKey() string { return k.APIKey } | |
| func (k GeminiKey) GetBaseURL() string { return k.BaseURL } | |
| // GeminiModel describes a mapping between an alias and the actual upstream model name. | |
| type GeminiModel struct { | |
| // Name is the upstream model identifier used when issuing requests. | |
| Name string `yaml:"name" json:"name"` | |
| // Alias is the client-facing model name that maps to Name. | |
| Alias string `yaml:"alias" json:"alias"` | |
| } | |
| func (m GeminiModel) GetName() string { return m.Name } | |
| func (m GeminiModel) GetAlias() string { return m.Alias } | |
| // OpenAICompatibility represents the configuration for OpenAI API compatibility | |
| // with external providers, allowing model aliases to be routed through OpenAI API format. | |
| type OpenAICompatibility struct { | |
| // Name is the identifier for this OpenAI compatibility configuration. | |
| Name string `yaml:"name" json:"name"` | |
| // Priority controls selection preference when multiple providers or credentials match. | |
| // Higher values are preferred; defaults to 0. | |
| Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` | |
| // Prefix optionally namespaces model aliases for this provider (e.g., "teamA/kimi-k2"). | |
| Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` | |
| // BaseURL is the base URL for the external OpenAI-compatible API endpoint. | |
| BaseURL string `yaml:"base-url" json:"base-url"` | |
| // APIKeyEntries defines API keys with optional per-key proxy configuration. | |
| APIKeyEntries []OpenAICompatibilityAPIKey `yaml:"api-key-entries,omitempty" json:"api-key-entries,omitempty"` | |
| // Models defines the model configurations including aliases for routing. | |
| Models []OpenAICompatibilityModel `yaml:"models" json:"models"` | |
| // Headers optionally adds extra HTTP headers for requests sent to this provider. | |
| Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` | |
| } | |
| // OpenAICompatibilityAPIKey represents an API key configuration with optional proxy setting. | |
| type OpenAICompatibilityAPIKey struct { | |
| // APIKey is the authentication key for accessing the external API services. | |
| APIKey string `yaml:"api-key" json:"api-key"` | |
| // ProxyURL overrides the global proxy setting for this API key if provided. | |
| ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` | |
| } | |
| // OpenAICompatibilityModel represents a model configuration for OpenAI compatibility, | |
| // including the actual model name and its alias for API routing. | |
| type OpenAICompatibilityModel struct { | |
| // Name is the actual model name used by the external provider. | |
| Name string `yaml:"name" json:"name"` | |
| // Alias is the model name alias that clients will use to reference this model. | |
| Alias string `yaml:"alias" json:"alias"` | |
| } | |
| func (m OpenAICompatibilityModel) GetName() string { return m.Name } | |
| func (m OpenAICompatibilityModel) GetAlias() string { return m.Alias } | |
| // LoadConfig reads a YAML configuration file from the given path, | |
| // unmarshals it into a Config struct, applies environment variable overrides, | |
| // and returns it. | |
| // | |
| // Parameters: | |
| // - configFile: The path to the YAML configuration file | |
| // | |
| // Returns: | |
| // - *Config: The loaded configuration | |
| // - error: An error if the configuration could not be loaded | |
| func LoadConfig(configFile string) (*Config, error) { | |
| return LoadConfigOptional(configFile, false) | |
| } | |
| // LoadConfigOptional reads YAML from configFile. | |
| // If optional is true and the file is missing, it returns an empty Config. | |
| // If optional is true and the file is empty or invalid, it returns an empty Config. | |
| func LoadConfigOptional(configFile string, optional bool) (*Config, error) { | |
| // Perform oauth-model-alias migration before loading config. | |
| // This migrates oauth-model-mappings to oauth-model-alias if needed. | |
| if migrated, err := MigrateOAuthModelAlias(configFile); err != nil { | |
| // Log warning but don't fail - config loading should still work | |
| fmt.Printf("Warning: oauth-model-alias migration failed: %v\n", err) | |
| } else if migrated { | |
| fmt.Println("Migrated oauth-model-mappings to oauth-model-alias") | |
| } | |
| // Read the entire configuration file into memory. | |
| data, err := os.ReadFile(configFile) | |
| if err != nil { | |
| if optional { | |
| if os.IsNotExist(err) || errors.Is(err, syscall.EISDIR) { | |
| // Missing and optional: return empty config (cloud deploy standby). | |
| return &Config{}, nil | |
| } | |
| } | |
| return nil, fmt.Errorf("failed to read config file: %w", err) | |
| } | |
| // In cloud deploy mode (optional=true), if file is empty or contains only whitespace, return empty config. | |
| if optional && len(data) == 0 { | |
| return &Config{}, nil | |
| } | |
| // Unmarshal the YAML data into the Config struct. | |
| var cfg Config | |
| // Set defaults before unmarshal so that absent keys keep defaults. | |
| cfg.Host = "" // Default empty: binds to all interfaces (IPv4 + IPv6) | |
| cfg.LoggingToFile = false | |
| cfg.LogsMaxTotalSizeMB = 0 | |
| cfg.UsageStatisticsEnabled = false | |
| cfg.DisableCooling = false | |
| cfg.AmpCode.RestrictManagementToLocalhost = false // Default to false: API key auth is sufficient | |
| cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository | |
| // Set Antigravity defaults | |
| cfg.Antigravity.BaseURLDaily = "https://daily-cloudcode-pa.googleapis.com" | |
| cfg.Antigravity.SandboxBaseURLDaily = "https://daily-cloudcode-pa.sandbox.googleapis.com" | |
| cfg.Antigravity.BaseURLProd = "https://cloudcode-pa.googleapis.com" | |
| cfg.Antigravity.ClientID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com" | |
| cfg.Antigravity.ClientSecret = "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" | |
| cfg.Antigravity.UserAgent = "antigravity/1.104.0 darwin/arm64" | |
| cfg.Antigravity.SystemInstruction = "You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**" | |
| if err = yaml.Unmarshal(data, &cfg); err != nil { | |
| if optional { | |
| // In cloud deploy mode, if YAML parsing fails, return empty config instead of error. | |
| return &Config{}, nil | |
| } | |
| return nil, fmt.Errorf("failed to parse config file: %w", err) | |
| } | |
| var legacy legacyConfigData | |
| if errLegacy := yaml.Unmarshal(data, &legacy); errLegacy == nil { | |
| if cfg.migrateLegacyGeminiKeys(legacy.LegacyGeminiKeys) { | |
| cfg.legacyMigrationPending = true | |
| } | |
| if cfg.migrateLegacyOpenAICompatibilityKeys(legacy.OpenAICompat) { | |
| cfg.legacyMigrationPending = true | |
| } | |
| if cfg.migrateLegacyAmpConfig(&legacy) { | |
| cfg.legacyMigrationPending = true | |
| } | |
| } | |
| // Hash remote management key if plaintext is detected (nested) | |
| // We consider a value to be already hashed if it looks like a bcrypt hash ($2a$, $2b$, or $2y$ prefix). | |
| if cfg.RemoteManagement.SecretKey != "" && !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) { | |
| hashed, errHash := hashSecret(cfg.RemoteManagement.SecretKey) | |
| if errHash != nil { | |
| return nil, fmt.Errorf("failed to hash remote management key: %w", errHash) | |
| } | |
| cfg.RemoteManagement.SecretKey = hashed | |
| // Persist the hashed value back to the config file to avoid re-hashing on next startup. | |
| // Preserve YAML comments and ordering; update only the nested key. | |
| _ = SaveConfigPreserveCommentsUpdateNestedScalar(configFile, []string{"remote-management", "secret-key"}, hashed) | |
| } | |
| cfg.RemoteManagement.PanelGitHubRepository = strings.TrimSpace(cfg.RemoteManagement.PanelGitHubRepository) | |
| if cfg.RemoteManagement.PanelGitHubRepository == "" { | |
| cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository | |
| } | |
| if cfg.LogsMaxTotalSizeMB < 0 { | |
| cfg.LogsMaxTotalSizeMB = 0 | |
| } | |
| // Sync request authentication providers with inline API keys for backwards compatibility. | |
| syncInlineAccessProvider(&cfg) | |
| // Sanitize Gemini API key configuration and migrate legacy entries. | |
| cfg.SanitizeGeminiKeys() | |
| // Sanitize Vertex-compatible API keys: drop entries without base-url | |
| cfg.SanitizeVertexCompatKeys() | |
| // Sanitize Codex keys: drop entries without base-url | |
| cfg.SanitizeCodexKeys() | |
| // Sanitize Claude key headers | |
| cfg.SanitizeClaudeKeys() | |
| // Sanitize OpenAI compatibility providers: drop entries without base-url | |
| cfg.SanitizeOpenAICompatibility() | |
| // Normalize OAuth provider model exclusion map. | |
| cfg.OAuthExcludedModels = NormalizeOAuthExcludedModels(cfg.OAuthExcludedModels) | |
| // Normalize global OAuth model name aliases. | |
| cfg.SanitizeOAuthModelAlias() | |
| // Validate raw payload rules and drop invalid entries. | |
| cfg.SanitizePayloadRules() | |
| if cfg.legacyMigrationPending { | |
| fmt.Println("Detected legacy configuration keys, attempting to persist the normalized config...") | |
| if !optional && configFile != "" { | |
| if err := SaveConfigPreserveComments(configFile, &cfg); err != nil { | |
| return nil, fmt.Errorf("failed to persist migrated legacy config: %w", err) | |
| } | |
| fmt.Println("Legacy configuration normalized and persisted.") | |
| } else { | |
| fmt.Println("Legacy configuration normalized in memory; persistence skipped.") | |
| } | |
| } | |
| // Return the populated configuration struct. | |
| return &cfg, nil | |
| } | |