// Copyright (c) 2025-2026 libaxuan // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. package config import ( "github.com/libaxuan/cursor2api-go/models" "encoding/json" "fmt" "os" "strconv" "strings" "github.com/joho/godotenv" "github.com/sirupsen/logrus" ) // Config 应用程序配置结构 type Config struct { // 服务器配置 Port int `json:"port"` Debug bool `json:"debug"` // API配置 APIKey string `json:"api_key"` Models string `json:"models"` SystemPromptInject string `json:"system_prompt_inject"` Timeout int `json:"timeout"` MaxInputLength int `json:"max_input_length"` // 兼容性配置 // KILO_TOOL_STRICT=true 时:只要请求提供了 tools,就强制/强提示模型至少发起一次工具调用 // 以适配 Kilo Code 这类“必须用工具”的上层编排器。 KiloToolStrict bool `json:"kilo_tool_strict"` // Cursor相关配置 ScriptURL string `json:"script_url"` FP FP `json:"fp"` } // FP 指纹配置结构 type FP struct { UserAgent string `json:"userAgent"` UNMASKED_VENDOR_WEBGL string `json:"unmaskedVendorWebgl"` UNMASKED_RENDERER_WEBGL string `json:"unmaskedRendererWebgl"` } // LoadConfig 加载配置 func LoadConfig() (*Config, error) { // 尝试加载.env文件 if err := godotenv.Load(); err != nil { logrus.Debug("No .env file found, using environment variables") } config := &Config{ // 设置默认值 Port: getEnvAsInt("PORT", 8002), Debug: getEnvAsBool("DEBUG", false), APIKey: getEnv("API_KEY", "0000"), Models: getEnv("MODELS", "claude-sonnet-4.6"), SystemPromptInject: getEnv("SYSTEM_PROMPT_INJECT", ""), Timeout: getEnvAsInt("TIMEOUT", 60), MaxInputLength: getEnvAsInt("MAX_INPUT_LENGTH", 200000), KiloToolStrict: getEnvAsBool("KILO_TOOL_STRICT", false), ScriptURL: getEnv("SCRIPT_URL", "https://cursor.com/_next/static/chunks/pages/_app.js"), FP: FP{ UserAgent: getEnv("USER_AGENT", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"), UNMASKED_VENDOR_WEBGL: getEnv("UNMASKED_VENDOR_WEBGL", "Google Inc. (Intel)"), UNMASKED_RENDERER_WEBGL: getEnv("UNMASKED_RENDERER_WEBGL", "ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0, D3D11)"), }, } // 验证必要的配置 if err := config.validate(); err != nil { return nil, fmt.Errorf("config validation failed: %w", err) } return config, nil } // validate 验证配置 func (c *Config) validate() error { if c.Port <= 0 || c.Port > 65535 { return fmt.Errorf("invalid port: %d", c.Port) } if c.APIKey == "" { return fmt.Errorf("API_KEY is required") } if c.Timeout <= 0 { return fmt.Errorf("timeout must be positive") } if c.MaxInputLength <= 0 { return fmt.Errorf("max input length must be positive") } return nil } // GetBaseModels 获取基础模型列表 func (c *Config) GetBaseModels() []string { modelsList := strings.Split(c.Models, ",") result := make([]string, 0, len(modelsList)) for _, model := range modelsList { if trimmed := strings.TrimSpace(model); trimmed != "" { result = append(result, trimmed) } } return result } // GetModels 获取模型列表 func (c *Config) GetModels() []string { return models.ExpandModelList(c.GetBaseModels()) } // IsValidModel 检查模型是否有效 func (c *Config) IsValidModel(model string) bool { validModels := c.GetModels() for _, validModel := range validModels { if validModel == model { return true } } return false } // ToJSON 将配置序列化为JSON(用于调试) func (c *Config) ToJSON() string { // 创建一个副本,隐藏敏感信息 safeCfg := *c safeCfg.APIKey = "***" data, err := json.MarshalIndent(safeCfg, "", " ") if err != nil { return fmt.Sprintf("Error marshaling config: %v", err) } return string(data) } // 辅助函数 // getEnv 获取环境变量,如果不存在则返回默认值 func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } // getEnvAsInt 获取环境变量并转换为int func getEnvAsInt(key string, defaultValue int) int { valueStr := os.Getenv(key) if valueStr == "" { return defaultValue } value, err := strconv.Atoi(valueStr) if err != nil { logrus.Warnf("Invalid integer value for %s: %s, using default: %d", key, valueStr, defaultValue) return defaultValue } return value } // getEnvAsBool 获取环境变量并转换为bool func getEnvAsBool(key string, defaultValue bool) bool { valueStr := os.Getenv(key) if valueStr == "" { return defaultValue } value, err := strconv.ParseBool(valueStr) if err != nil { logrus.Warnf("Invalid boolean value for %s: %s, using default: %t", key, valueStr, defaultValue) return defaultValue } return value }