package ai import ( "encoding/json" "os" "path/filepath" "strings" ) // Config AI 配置;provider: stub|openai(OpenAI 兼容协议)。 type Config struct { Provider string `json:"provider"` BaseURL string `json:"baseURL"` APIKey string `json:"apiKey,omitempty"` Model string `json:"model"` } func configPath() (string, error) { home, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(home, ".opsxdb", "ai.json"), nil } // LoadConfig 读取配置:~/.opsxdb/ai.json 优先,env(OPSXDB_AI_*)兜底逐字段补充。 func LoadConfig() Config { cfg := Config{ Provider: strings.TrimSpace(os.Getenv("OPSXDB_AI_PROVIDER")), BaseURL: strings.TrimSpace(os.Getenv("OPSXDB_AI_BASE_URL")), APIKey: strings.TrimSpace(os.Getenv("OPSXDB_AI_API_KEY")), Model: strings.TrimSpace(os.Getenv("OPSXDB_AI_MODEL")), } if p, err := configPath(); err == nil { if b, err := os.ReadFile(p); err == nil { var f Config if json.Unmarshal(b, &f) == nil { if f.Provider != "" { cfg.Provider = f.Provider } if f.BaseURL != "" { cfg.BaseURL = f.BaseURL } if f.APIKey != "" { cfg.APIKey = f.APIKey } if f.Model != "" { cfg.Model = f.Model } } } } if cfg.Provider == "" { if cfg.APIKey != "" { cfg.Provider = "openai" } else { cfg.Provider = "stub" } } return cfg } // SaveConfig 写 ~/.opsxdb/ai.json(0600,与本机 token 同威胁模型)。 func SaveConfig(c Config) error { p, err := configPath() if err != nil { return err } if err := os.MkdirAll(filepath.Dir(p), 0700); err != nil { return err } b, err := json.MarshalIndent(c, "", " ") if err != nil { return err } return os.WriteFile(p, b, 0600) } // MaskedKey 返回脱敏后的 key 展示形式(sk-***)。 func (c Config) MaskedKey() string { k := c.APIKey if k == "" { return "" } if len(k) <= 6 { return "***" } return k[:3] + "***" }