mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 00:14:33 +00:00
Register Requesty (https://router.requesty.ai/v1) next to OpenRouter on the existing OpenAI-compatible transport: config and env vars (GOCLAW_REQUESTY_API_KEY, GOCLAW_REQUESTY_BASE_URL), secret masking, DB and in-memory registration, CLI setup, doctor, placeholder provider, OpenAPI enum, Web/Desktop provider lists and docs. The models list merges Requesty managed policies (GET /models/managed) with the key's catalog from GET /models.
225 lines
7.0 KiB
Go
225 lines
7.0 KiB
Go
package config
|
|
|
|
import "encoding/json"
|
|
|
|
const secretMask = "***"
|
|
|
|
// MaskedCopy returns a deep copy of the config with all secret fields masked.
|
|
// Used by config.get to avoid exposing secrets to WebSocket clients.
|
|
func (c *Config) MaskedCopy() *Config {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
// Deep copy via JSON round-trip
|
|
data, err := json.Marshal(c)
|
|
if err != nil {
|
|
return &Config{}
|
|
}
|
|
cp := Default()
|
|
if err := json.Unmarshal(data, cp); err != nil {
|
|
return &Config{}
|
|
}
|
|
|
|
// Mask provider API keys
|
|
maskNonEmpty(&cp.Providers.Anthropic.APIKey)
|
|
maskNonEmpty(&cp.Providers.OpenAI.APIKey)
|
|
maskNonEmpty(&cp.Providers.AtlasCloud.APIKey)
|
|
maskNonEmpty(&cp.Providers.APIRoute.APIKey)
|
|
maskNonEmpty(&cp.Providers.Requesty.APIKey)
|
|
maskNonEmpty(&cp.Providers.OpenRouter.APIKey)
|
|
maskNonEmpty(&cp.Providers.Groq.APIKey)
|
|
maskNonEmpty(&cp.Providers.DeepSeek.APIKey)
|
|
maskNonEmpty(&cp.Providers.Gemini.APIKey)
|
|
maskNonEmpty(&cp.Providers.Mistral.APIKey)
|
|
maskNonEmpty(&cp.Providers.XAI.APIKey)
|
|
maskNonEmpty(&cp.Providers.MiniMax.APIKey)
|
|
maskNonEmpty(&cp.Providers.Cohere.APIKey)
|
|
maskNonEmpty(&cp.Providers.Perplexity.APIKey)
|
|
maskNonEmpty(&cp.Providers.DashScope.APIKey)
|
|
maskNonEmpty(&cp.Providers.Bailian.APIKey)
|
|
maskNonEmpty(&cp.Providers.Zai.APIKey)
|
|
maskNonEmpty(&cp.Providers.ZaiCoding.APIKey)
|
|
maskNonEmpty(&cp.Providers.OllamaCloud.APIKey)
|
|
maskNonEmpty(&cp.Providers.Vertex.APIKey)
|
|
|
|
// Mask gateway token
|
|
maskNonEmpty(&cp.Gateway.Token)
|
|
maskNonEmpty(&cp.Gateway.MCPServerToken)
|
|
|
|
// Mask channel secrets
|
|
maskNonEmpty(&cp.Channels.Telegram.Token)
|
|
maskNonEmpty(&cp.Channels.Discord.Token)
|
|
maskNonEmpty(&cp.Channels.Slack.BotToken)
|
|
maskNonEmpty(&cp.Channels.Slack.AppToken)
|
|
maskNonEmpty(&cp.Channels.Zalo.Token)
|
|
maskNonEmpty(&cp.Channels.Zalo.WebhookSecret)
|
|
maskNonEmpty(&cp.Channels.Feishu.AppID)
|
|
maskNonEmpty(&cp.Channels.Feishu.AppSecret)
|
|
maskNonEmpty(&cp.Channels.Feishu.EncryptKey)
|
|
maskNonEmpty(&cp.Channels.Feishu.VerificationToken)
|
|
|
|
// Mask TTS API keys
|
|
maskNonEmpty(&cp.Tts.OpenAI.APIKey)
|
|
maskNonEmpty(&cp.Tts.ElevenLabs.APIKey)
|
|
maskNonEmpty(&cp.Tts.MiniMax.APIKey)
|
|
|
|
// Mask Tailscale auth key
|
|
maskNonEmpty(&cp.Tailscale.AuthKey)
|
|
|
|
return cp
|
|
}
|
|
|
|
// StripSecrets zeros out all secret fields in the config.
|
|
// Used before saving to disk to ensure secrets never persist in config.json.
|
|
func (c *Config) StripSecrets() {
|
|
// Provider API keys
|
|
c.Providers.Anthropic.APIKey = ""
|
|
c.Providers.OpenAI.APIKey = ""
|
|
c.Providers.AtlasCloud.APIKey = ""
|
|
c.Providers.APIRoute.APIKey = ""
|
|
c.Providers.Requesty.APIKey = ""
|
|
c.Providers.OpenRouter.APIKey = ""
|
|
c.Providers.Groq.APIKey = ""
|
|
c.Providers.DeepSeek.APIKey = ""
|
|
c.Providers.Gemini.APIKey = ""
|
|
c.Providers.Mistral.APIKey = ""
|
|
c.Providers.XAI.APIKey = ""
|
|
c.Providers.MiniMax.APIKey = ""
|
|
c.Providers.Cohere.APIKey = ""
|
|
c.Providers.Perplexity.APIKey = ""
|
|
c.Providers.DashScope.APIKey = ""
|
|
c.Providers.Bailian.APIKey = ""
|
|
c.Providers.Zai.APIKey = ""
|
|
c.Providers.ZaiCoding.APIKey = ""
|
|
c.Providers.OllamaCloud.APIKey = ""
|
|
c.Providers.Vertex.APIKey = ""
|
|
|
|
// Gateway token
|
|
c.Gateway.Token = ""
|
|
c.Gateway.MCPServerToken = ""
|
|
|
|
// Channel secrets
|
|
c.Channels.Telegram.Token = ""
|
|
c.Channels.Discord.Token = ""
|
|
c.Channels.Slack.BotToken = ""
|
|
c.Channels.Slack.AppToken = ""
|
|
c.Channels.Zalo.Token = ""
|
|
c.Channels.Zalo.WebhookSecret = ""
|
|
c.Channels.Feishu.AppID = ""
|
|
c.Channels.Feishu.AppSecret = ""
|
|
c.Channels.Feishu.EncryptKey = ""
|
|
c.Channels.Feishu.VerificationToken = ""
|
|
|
|
// TTS API keys
|
|
c.Tts.OpenAI.APIKey = ""
|
|
c.Tts.ElevenLabs.APIKey = ""
|
|
c.Tts.MiniMax.APIKey = ""
|
|
|
|
// Tailscale auth key
|
|
c.Tailscale.AuthKey = ""
|
|
}
|
|
|
|
// StripMaskedSecrets strips only fields that still contain the mask value "***".
|
|
// Real values (user-entered via UI) are preserved, so that secrets entered
|
|
// via the config UI persist in config.json.
|
|
func (c *Config) StripMaskedSecrets() {
|
|
stripIfMasked := func(s *string) {
|
|
if *s == secretMask {
|
|
*s = ""
|
|
}
|
|
}
|
|
|
|
// Provider API keys
|
|
stripIfMasked(&c.Providers.Anthropic.APIKey)
|
|
stripIfMasked(&c.Providers.OpenAI.APIKey)
|
|
stripIfMasked(&c.Providers.AtlasCloud.APIKey)
|
|
stripIfMasked(&c.Providers.APIRoute.APIKey)
|
|
stripIfMasked(&c.Providers.Requesty.APIKey)
|
|
stripIfMasked(&c.Providers.OpenRouter.APIKey)
|
|
stripIfMasked(&c.Providers.Groq.APIKey)
|
|
stripIfMasked(&c.Providers.DeepSeek.APIKey)
|
|
stripIfMasked(&c.Providers.Gemini.APIKey)
|
|
stripIfMasked(&c.Providers.Mistral.APIKey)
|
|
stripIfMasked(&c.Providers.XAI.APIKey)
|
|
stripIfMasked(&c.Providers.MiniMax.APIKey)
|
|
stripIfMasked(&c.Providers.Cohere.APIKey)
|
|
stripIfMasked(&c.Providers.Perplexity.APIKey)
|
|
stripIfMasked(&c.Providers.DashScope.APIKey)
|
|
stripIfMasked(&c.Providers.Bailian.APIKey)
|
|
stripIfMasked(&c.Providers.Zai.APIKey)
|
|
stripIfMasked(&c.Providers.ZaiCoding.APIKey)
|
|
stripIfMasked(&c.Providers.OllamaCloud.APIKey)
|
|
stripIfMasked(&c.Providers.Vertex.APIKey)
|
|
|
|
// Gateway token
|
|
stripIfMasked(&c.Gateway.Token)
|
|
stripIfMasked(&c.Gateway.MCPServerToken)
|
|
|
|
// Channel secrets
|
|
stripIfMasked(&c.Channels.Telegram.Token)
|
|
stripIfMasked(&c.Channels.Discord.Token)
|
|
stripIfMasked(&c.Channels.Slack.BotToken)
|
|
stripIfMasked(&c.Channels.Slack.AppToken)
|
|
stripIfMasked(&c.Channels.Zalo.Token)
|
|
stripIfMasked(&c.Channels.Zalo.WebhookSecret)
|
|
stripIfMasked(&c.Channels.Feishu.AppID)
|
|
stripIfMasked(&c.Channels.Feishu.AppSecret)
|
|
stripIfMasked(&c.Channels.Feishu.EncryptKey)
|
|
stripIfMasked(&c.Channels.Feishu.VerificationToken)
|
|
|
|
// TTS API keys
|
|
stripIfMasked(&c.Tts.OpenAI.APIKey)
|
|
stripIfMasked(&c.Tts.ElevenLabs.APIKey)
|
|
stripIfMasked(&c.Tts.MiniMax.APIKey)
|
|
|
|
// Tailscale auth key
|
|
stripIfMasked(&c.Tailscale.AuthKey)
|
|
}
|
|
|
|
// ApplyDBSecrets overlays secrets from the config_secrets table onto the config.
|
|
// Called before ApplyEnvOverrides() — env vars take highest precedence.
|
|
// Precedence chain: config.json defaults → DB secrets → env vars.
|
|
func (c *Config) ApplyDBSecrets(secrets map[string]string) {
|
|
apply := func(key string, dst *string) {
|
|
if v, ok := secrets[key]; ok && v != "" {
|
|
*dst = v
|
|
}
|
|
}
|
|
|
|
apply("gateway.token", &c.Gateway.Token)
|
|
apply("gateway.mcp_server_token", &c.Gateway.MCPServerToken)
|
|
apply("tts.openai.api_key", &c.Tts.OpenAI.APIKey)
|
|
apply("tts.elevenlabs.api_key", &c.Tts.ElevenLabs.APIKey)
|
|
apply("tts.minimax.api_key", &c.Tts.MiniMax.APIKey)
|
|
apply("tts.minimax.group_id", &c.Tts.MiniMax.GroupID)
|
|
apply("tailscale.auth_key", &c.Tailscale.AuthKey)
|
|
}
|
|
|
|
// ExtractDBSecrets returns the config_secrets key-value pairs from the config.
|
|
// Saves secrets to the config_secrets table.
|
|
func (c *Config) ExtractDBSecrets() map[string]string {
|
|
secrets := make(map[string]string)
|
|
|
|
collect := func(key, value string) {
|
|
if value != "" && value != secretMask {
|
|
secrets[key] = value
|
|
}
|
|
}
|
|
|
|
collect("gateway.token", c.Gateway.Token)
|
|
collect("gateway.mcp_server_token", c.Gateway.MCPServerToken)
|
|
collect("tts.openai.api_key", c.Tts.OpenAI.APIKey)
|
|
collect("tts.elevenlabs.api_key", c.Tts.ElevenLabs.APIKey)
|
|
collect("tts.minimax.api_key", c.Tts.MiniMax.APIKey)
|
|
collect("tts.minimax.group_id", c.Tts.MiniMax.GroupID)
|
|
collect("tailscale.auth_key", c.Tailscale.AuthKey)
|
|
|
|
return secrets
|
|
}
|
|
|
|
func maskNonEmpty(s *string) {
|
|
if *s != "" {
|
|
*s = secretMask
|
|
}
|
|
}
|