// 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 }