mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-17 14:48:34 +00:00
dc51018563
* fix(subagent): inherit parent agent's provider instead of alphabetical fallback Subagents previously used a fixed provider (alphabetically first from the registry, often "anthropic") regardless of which provider the parent agent used. This caused invalid combos like anthropic/glm-5 when a zai-coding agent spawned subagents. - Pass provider registry to SubagentManager for runtime resolution - Inject parent provider name into context (WithParentProvider) - Resolve activeProvider from parent context before LLM call - Fix trace spans to show actual resolved provider, not default * fix(providers): api_base fallback from config/env for DB providers DB providers with empty api_base now inherit from config/env vars (e.g., GOCLAW_ANTHROPIC_BASE_URL). Prevents proxy API keys from being sent to the real provider API endpoint. - Add APIBaseForType() method on ProvidersConfig - registerProvidersFromDB falls back to config when api_base is empty - ProvidersHandler uses resolveAPIBase() for model listing - Add api_base, display_name, settings to provider validation whitelist * fix(tracing): pass resolved provider name to subagent span emitters - emitSubagentSpanStart now accepts providerName param instead of reading sm.provider.Name() — ensures root subagent span reflects the inherited parent provider, not the fallback default - registerInMemory now uses resolveAPIBase() so DB providers with empty api_base inherit the config/env fallback (same as startup path) --------- Co-authored-by: viettranx <viettranx@gmail.com>
494 lines
29 KiB
Go
494 lines
29 KiB
Go
package config
|
|
|
|
// PendingCompactionConfig configures LLM-based compaction of pending group messages.
|
|
// When a group accumulates more than Threshold pending messages, older messages are
|
|
// summarized by an LLM and replaced with a compact summary, keeping KeepRecent raw messages.
|
|
type PendingCompactionConfig struct {
|
|
Threshold int `json:"threshold,omitempty"` // trigger compaction when entries exceed this (default 50)
|
|
KeepRecent int `json:"keep_recent,omitempty"` // keep this many recent raw messages after compaction (default 15)
|
|
MaxTokens int `json:"max_tokens,omitempty"` // max output tokens for LLM summarization (default 4096)
|
|
Provider string `json:"provider,omitempty"` // LLM provider name (e.g. "openai"); empty = use agent's provider
|
|
Model string `json:"model,omitempty"` // model for summarization; empty = use agent's model
|
|
}
|
|
|
|
// ChannelsConfig contains per-channel configuration.
|
|
type ChannelsConfig struct {
|
|
Telegram TelegramConfig `json:"telegram"`
|
|
Discord DiscordConfig `json:"discord"`
|
|
Slack SlackConfig `json:"slack"`
|
|
WhatsApp WhatsAppConfig `json:"whatsapp"`
|
|
Zalo ZaloConfig `json:"zalo"`
|
|
ZaloPersonal ZaloPersonalConfig `json:"zalo_personal"`
|
|
Feishu FeishuConfig `json:"feishu"`
|
|
PendingCompaction *PendingCompactionConfig `json:"pending_compaction,omitempty"` // global pending message compaction settings
|
|
}
|
|
|
|
type TelegramConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Token string `json:"token"`
|
|
Proxy string `json:"proxy,omitempty"`
|
|
APIServer string `json:"api_server,omitempty"` // custom Telegram Bot API server URL (e.g. "http://localhost:8081")
|
|
AllowFrom FlexibleStringSlice `json:"allow_from"`
|
|
DMPolicy string `json:"dm_policy,omitempty"` // "pairing" (default), "allowlist", "open", "disabled"
|
|
GroupPolicy string `json:"group_policy,omitempty"` // "open" (default), "allowlist", "disabled"
|
|
RequireMention *bool `json:"require_mention,omitempty"` // require @bot mention in groups (default true)
|
|
HistoryLimit int `json:"history_limit,omitempty"` // max pending group messages for context (default 50, 0=disabled)
|
|
DMStream *bool `json:"dm_stream,omitempty"` // enable streaming for DMs (default false) — edits placeholder progressively
|
|
GroupStream *bool `json:"group_stream,omitempty"` // enable streaming for groups (default false) — sends new message, edits progressively
|
|
DraftTransport *bool `json:"draft_transport,omitempty"` // use sendMessageDraft for DM streaming (default true) — stealth preview, no notifications per edit
|
|
ReasoningStream *bool `json:"reasoning_stream,omitempty"` // show reasoning as separate message when provider emits thinking events (default true)
|
|
ReactionLevel string `json:"reaction_level,omitempty"` // "off" (default), "minimal", "full" — status emoji reactions
|
|
MediaMaxBytes int64 `json:"media_max_bytes,omitempty"` // max media download size in bytes (default 20MB)
|
|
LinkPreview *bool `json:"link_preview,omitempty"` // enable URL previews in messages (default true)
|
|
BlockReply *bool `json:"block_reply,omitempty"` // override gateway block_reply (nil = inherit)
|
|
|
|
// Optional STT (Speech-to-Text) pipeline for voice/audio inbound messages.
|
|
// When stt_proxy_url is set, audio/voice messages are transcribed before being forwarded to the agent.
|
|
STTProxyURL string `json:"stt_proxy_url,omitempty"` // base URL of the STT proxy service (e.g. "https://stt.example.com")
|
|
STTAPIKey string `json:"stt_api_key,omitempty"` // Bearer token for the STT proxy
|
|
STTTenantID string `json:"stt_tenant_id,omitempty"` // optional tenant/org identifier forwarded to the STT proxy
|
|
STTTimeoutSeconds int `json:"stt_timeout_seconds,omitempty"` // per-request timeout for STT calls (default 30s)
|
|
|
|
// Optional audio-aware routing: when set, voice/audio inbound messages are routed to this
|
|
// agent instead of the default channel agent. Requires the named agent to exist in the config.
|
|
VoiceAgentID string `json:"voice_agent_id,omitempty"` // agent ID to route voice inbound to (e.g. "speaking-agent")
|
|
|
|
// Per-group (and per-topic) overrides. Key is chat ID string (e.g. "-100123456") or "*" for wildcard.
|
|
// TS ref: channels.telegram.groups in src/config/types.telegram.ts.
|
|
Groups map[string]*TelegramGroupConfig `json:"groups,omitempty"`
|
|
}
|
|
|
|
// TelegramGroupConfig defines per-group overrides for a Telegram channel.
|
|
// Matching TS TelegramGroupConfig in src/config/types.telegram.ts.
|
|
type TelegramGroupConfig struct {
|
|
GroupPolicy string `json:"group_policy,omitempty"` // override group policy for this group
|
|
RequireMention *bool `json:"require_mention,omitempty"` // override require_mention for this group
|
|
AllowFrom FlexibleStringSlice `json:"allow_from,omitempty"` // override allow_from for this group
|
|
Enabled *bool `json:"enabled,omitempty"` // disable bot for this group (default: true)
|
|
Skills []string `json:"skills,omitempty"` // skill whitelist (nil = all, [] = none)
|
|
Tools []string `json:"tools,omitempty"` // tool allow list (nil = all, supports "group:xxx")
|
|
SystemPrompt string `json:"system_prompt,omitempty"` // extra system prompt for this group
|
|
Topics map[string]*TelegramTopicConfig `json:"topics,omitempty"` // per-topic overrides (key: thread ID string)
|
|
Quota *QuotaWindow `json:"quota,omitempty"` // per-group quota override
|
|
}
|
|
|
|
// TelegramTopicConfig defines per-topic overrides within a Telegram group.
|
|
// Matching TS TelegramTopicConfig in src/config/types.telegram.ts.
|
|
type TelegramTopicConfig struct {
|
|
RequireMention *bool `json:"require_mention,omitempty"`
|
|
GroupPolicy string `json:"group_policy,omitempty"`
|
|
Skills []string `json:"skills,omitempty"`
|
|
Tools []string `json:"tools,omitempty"` // tool allow list (nil = inherit, supports "group:xxx")
|
|
Enabled *bool `json:"enabled,omitempty"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from,omitempty"`
|
|
SystemPrompt string `json:"system_prompt,omitempty"`
|
|
}
|
|
|
|
type DiscordConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Token string `json:"token"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from"`
|
|
DMPolicy string `json:"dm_policy,omitempty"` // "open" (default), "allowlist", "disabled"
|
|
GroupPolicy string `json:"group_policy,omitempty"` // "open" (default), "allowlist", "disabled"
|
|
RequireMention *bool `json:"require_mention,omitempty"` // require @bot mention in groups (default true)
|
|
HistoryLimit int `json:"history_limit,omitempty"` // max pending group messages for context (default 50, 0=disabled)
|
|
BlockReply *bool `json:"block_reply,omitempty"` // override gateway block_reply (nil = inherit)
|
|
MediaMaxBytes int64 `json:"media_max_bytes,omitempty"` // max media download size (default 25MB)
|
|
STTProxyURL string `json:"stt_proxy_url,omitempty"`
|
|
STTAPIKey string `json:"stt_api_key,omitempty"`
|
|
STTTenantID string `json:"stt_tenant_id,omitempty"`
|
|
STTTimeoutSeconds int `json:"stt_timeout_seconds,omitempty"`
|
|
VoiceAgentID string `json:"voice_agent_id,omitempty"`
|
|
}
|
|
|
|
type SlackConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
BotToken string `json:"bot_token"` // xoxb-... (Bot User OAuth Token)
|
|
AppToken string `json:"app_token"` // xapp-... (App-Level Token for Socket Mode)
|
|
UserToken string `json:"user_token,omitempty"` // xoxp-... (Optional: custom bot identity)
|
|
AllowFrom FlexibleStringSlice `json:"allow_from"`
|
|
DMPolicy string `json:"dm_policy,omitempty"` // "pairing" (default), "allowlist", "open", "disabled"
|
|
GroupPolicy string `json:"group_policy,omitempty"` // "open" (default), "pairing", "allowlist", "disabled"
|
|
RequireMention *bool `json:"require_mention,omitempty"` // require @bot mention in channels (default true)
|
|
HistoryLimit int `json:"history_limit,omitempty"` // max pending group messages for context (default 50, 0=disabled)
|
|
DMStream *bool `json:"dm_stream,omitempty"` // enable streaming for DMs (default false)
|
|
GroupStream *bool `json:"group_stream,omitempty"` // enable streaming for groups (default false)
|
|
NativeStream *bool `json:"native_stream,omitempty"` // use Slack ChatStreamer API if available (default false)
|
|
ReactionLevel string `json:"reaction_level,omitempty"` // "off" (default), "minimal", "full"
|
|
BlockReply *bool `json:"block_reply,omitempty"` // override gateway block_reply (nil = inherit)
|
|
DebounceDelay int `json:"debounce_delay,omitempty"` // ms delay before dispatching rapid messages (default 300, 0=disabled)
|
|
ThreadTTL *int `json:"thread_ttl,omitempty"` // hours before thread participation expires (default 24, 0=disabled — always require @mention)
|
|
MediaMaxBytes int64 `json:"media_max_bytes,omitempty"` // max file download size in bytes (default 20MB)
|
|
}
|
|
|
|
type WhatsAppConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
BridgeURL string `json:"bridge_url"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from"`
|
|
DMPolicy string `json:"dm_policy,omitempty"` // "open" (default), "allowlist", "disabled"
|
|
GroupPolicy string `json:"group_policy,omitempty"` // "open" (default), "allowlist", "disabled"
|
|
BlockReply *bool `json:"block_reply,omitempty"` // override gateway block_reply (nil = inherit)
|
|
}
|
|
|
|
type ZaloConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Token string `json:"token"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from"`
|
|
DMPolicy string `json:"dm_policy,omitempty"` // "pairing" (default), "allowlist", "open", "disabled"
|
|
WebhookURL string `json:"webhook_url,omitempty"`
|
|
WebhookSecret string `json:"webhook_secret,omitempty"`
|
|
MediaMaxMB int `json:"media_max_mb,omitempty"` // default 5
|
|
BlockReply *bool `json:"block_reply,omitempty"` // override gateway block_reply (nil = inherit)
|
|
}
|
|
|
|
type ZaloPersonalConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from"`
|
|
DMPolicy string `json:"dm_policy,omitempty"` // "pairing" (default), "allowlist", "open", "disabled"
|
|
GroupPolicy string `json:"group_policy,omitempty"` // "open" (default), "allowlist", "disabled"
|
|
RequireMention *bool `json:"require_mention,omitempty"` // require @bot mention in groups (default true)
|
|
HistoryLimit int `json:"history_limit,omitempty"` // max pending group messages for context (default 50, 0=disabled)
|
|
CredentialsPath string `json:"credentials_path,omitempty"` // path to saved cookies JSON
|
|
BlockReply *bool `json:"block_reply,omitempty"` // override gateway block_reply (nil = inherit)
|
|
}
|
|
|
|
type FeishuConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
AppID string `json:"app_id"`
|
|
AppSecret string `json:"app_secret"`
|
|
EncryptKey string `json:"encrypt_key,omitempty"`
|
|
VerificationToken string `json:"verification_token,omitempty"`
|
|
Domain string `json:"domain,omitempty"` // "lark" (default/global), "feishu" (China), or custom URL
|
|
ConnectionMode string `json:"connection_mode,omitempty"` // "websocket" (default), "webhook"
|
|
WebhookPort int `json:"webhook_port,omitempty"` // default 3000
|
|
WebhookPath string `json:"webhook_path,omitempty"` // default "/feishu/events"
|
|
AllowFrom FlexibleStringSlice `json:"allow_from"`
|
|
DMPolicy string `json:"dm_policy,omitempty"` // "pairing" (default)
|
|
GroupPolicy string `json:"group_policy,omitempty"` // "open" (default)
|
|
GroupAllowFrom FlexibleStringSlice `json:"group_allow_from,omitempty"`
|
|
RequireMention *bool `json:"require_mention,omitempty"` // default true (groups)
|
|
TopicSessionMode string `json:"topic_session_mode,omitempty"` // "disabled" (default)
|
|
TextChunkLimit int `json:"text_chunk_limit,omitempty"` // default 4000
|
|
MediaMaxMB int `json:"media_max_mb,omitempty"` // default 30
|
|
RenderMode string `json:"render_mode,omitempty"` // "auto", "raw", "card"
|
|
Streaming *bool `json:"streaming,omitempty"` // default true
|
|
ReactionLevel string `json:"reaction_level,omitempty"` // "off" (default), "minimal", "full" — typing emoji reactions
|
|
HistoryLimit int `json:"history_limit,omitempty"`
|
|
BlockReply *bool `json:"block_reply,omitempty"` // override gateway block_reply (nil = inherit)
|
|
STTProxyURL string `json:"stt_proxy_url,omitempty"`
|
|
STTAPIKey string `json:"stt_api_key,omitempty"`
|
|
STTTenantID string `json:"stt_tenant_id,omitempty"`
|
|
STTTimeoutSeconds int `json:"stt_timeout_seconds,omitempty"`
|
|
VoiceAgentID string `json:"voice_agent_id,omitempty"`
|
|
}
|
|
|
|
// ProvidersConfig maps provider name to its config.
|
|
type ProvidersConfig struct {
|
|
Anthropic ProviderConfig `json:"anthropic"`
|
|
OpenAI ProviderConfig `json:"openai"`
|
|
OpenRouter ProviderConfig `json:"openrouter"`
|
|
Groq ProviderConfig `json:"groq"`
|
|
Gemini ProviderConfig `json:"gemini"`
|
|
DeepSeek ProviderConfig `json:"deepseek"`
|
|
Mistral ProviderConfig `json:"mistral"`
|
|
XAI ProviderConfig `json:"xai"`
|
|
MiniMax ProviderConfig `json:"minimax"`
|
|
Cohere ProviderConfig `json:"cohere"`
|
|
Perplexity ProviderConfig `json:"perplexity"`
|
|
DashScope ProviderConfig `json:"dashscope"`
|
|
Bailian ProviderConfig `json:"bailian"`
|
|
Zai ProviderConfig `json:"zai"`
|
|
ZaiCoding ProviderConfig `json:"zai_coding"`
|
|
Ollama OllamaConfig `json:"ollama"` // local Ollama instance (no API key needed)
|
|
OllamaCloud ProviderConfig `json:"ollama_cloud"` // Ollama Cloud (API key required)
|
|
ClaudeCLI ClaudeCLIConfig `json:"claude_cli"`
|
|
ACP ACPConfig `json:"acp"`
|
|
}
|
|
|
|
// OllamaConfig configures a local (or self-hosted) Ollama instance.
|
|
// No API key is required — Ollama accepts any Bearer token value.
|
|
type OllamaConfig struct {
|
|
Host string `json:"host"` // Ollama server base URL, e.g. http://localhost:11434
|
|
}
|
|
|
|
// ClaudeCLIConfig configures the Claude CLI provider (uses subscription, not API key).
|
|
type ClaudeCLIConfig struct {
|
|
CLIPath string `json:"cli_path" yaml:"cli_path"` // path to claude binary (default: "claude")
|
|
Model string `json:"model" yaml:"model"` // default model alias (default: "sonnet")
|
|
BaseWorkDir string `json:"base_work_dir" yaml:"base_work_dir"` // base dir for agent workspaces
|
|
PermMode string `json:"perm_mode" yaml:"perm_mode"` // permission mode (default: "bypassPermissions")
|
|
}
|
|
|
|
// ACPConfig configures the ACP (Agent Client Protocol) provider.
|
|
// Orchestrates any ACP-compatible coding agent (Claude Code, Codex CLI, Gemini CLI) as a subprocess.
|
|
type ACPConfig struct {
|
|
Binary string `json:"binary"` // agent binary name or path (e.g. "claude", "codex")
|
|
Args []string `json:"args"` // extra spawn args
|
|
Model string `json:"model"` // default model/agent name
|
|
WorkDir string `json:"work_dir"` // base workspace dir
|
|
IdleTTL string `json:"idle_ttl"` // process idle TTL (e.g. "5m")
|
|
PermMode string `json:"perm_mode"` // "approve-all" (default), "approve-reads", "deny-all"
|
|
}
|
|
|
|
type ProviderConfig struct {
|
|
APIKey string `json:"api_key"`
|
|
APIBase string `json:"api_base,omitempty"`
|
|
}
|
|
|
|
// APIBaseForType returns the config-level api_base for a given provider type.
|
|
// Used as a fallback when DB providers have no api_base set.
|
|
func (p *ProvidersConfig) APIBaseForType(providerType string) string {
|
|
switch providerType {
|
|
case "anthropic_native":
|
|
return p.Anthropic.APIBase
|
|
case "openai", "openai_compat":
|
|
return p.OpenAI.APIBase
|
|
case "openrouter":
|
|
return p.OpenRouter.APIBase
|
|
case "groq":
|
|
return p.Groq.APIBase
|
|
case "deepseek":
|
|
return p.DeepSeek.APIBase
|
|
case "gemini_native":
|
|
return p.Gemini.APIBase
|
|
case "mistral":
|
|
return p.Mistral.APIBase
|
|
case "xai":
|
|
return p.XAI.APIBase
|
|
case "minimax_native":
|
|
return p.MiniMax.APIBase
|
|
case "cohere":
|
|
return p.Cohere.APIBase
|
|
case "perplexity":
|
|
return p.Perplexity.APIBase
|
|
case "dashscope":
|
|
return p.DashScope.APIBase
|
|
case "bailian":
|
|
return p.Bailian.APIBase
|
|
case "zai":
|
|
return p.Zai.APIBase
|
|
case "zai_coding":
|
|
return p.ZaiCoding.APIBase
|
|
case "ollama_cloud":
|
|
return p.OllamaCloud.APIBase
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// HasAnyProvider returns true if at least one provider has an API key or CLI configured.
|
|
func (c *Config) HasAnyProvider() bool {
|
|
p := c.Providers
|
|
return p.Anthropic.APIKey != "" ||
|
|
p.OpenAI.APIKey != "" ||
|
|
p.OpenRouter.APIKey != "" ||
|
|
p.Groq.APIKey != "" ||
|
|
p.Gemini.APIKey != "" ||
|
|
p.DeepSeek.APIKey != "" ||
|
|
p.Mistral.APIKey != "" ||
|
|
p.XAI.APIKey != "" ||
|
|
p.MiniMax.APIKey != "" ||
|
|
p.Cohere.APIKey != "" ||
|
|
p.Perplexity.APIKey != "" ||
|
|
p.DashScope.APIKey != "" ||
|
|
p.Bailian.APIKey != "" ||
|
|
p.Zai.APIKey != "" ||
|
|
p.ZaiCoding.APIKey != "" ||
|
|
p.Ollama.Host != "" ||
|
|
p.OllamaCloud.APIKey != "" ||
|
|
p.ClaudeCLI.CLIPath != "" ||
|
|
p.ACP.Binary != ""
|
|
}
|
|
|
|
// QuotaWindow defines request limits per time window. Zero means unlimited.
|
|
type QuotaWindow struct {
|
|
Hour int `json:"hour,omitempty"` // max requests per hour (0 = unlimited)
|
|
Day int `json:"day,omitempty"` // max requests per day (0 = unlimited)
|
|
Week int `json:"week,omitempty"` // max requests per week (0 = unlimited)
|
|
}
|
|
|
|
// IsZero returns true if no limits are set.
|
|
func (w QuotaWindow) IsZero() bool { return w.Hour == 0 && w.Day == 0 && w.Week == 0 }
|
|
|
|
// QuotaConfig configures per-user/group request quotas.
|
|
// Config merge priority: Groups > Channels > Providers > Default.
|
|
type QuotaConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Default QuotaWindow `json:"default"`
|
|
Providers map[string]QuotaWindow `json:"providers,omitempty"` // key = provider name (e.g. "anthropic")
|
|
Channels map[string]QuotaWindow `json:"channels,omitempty"` // key = channel name (e.g. "telegram")
|
|
Groups map[string]QuotaWindow `json:"groups,omitempty"` // key = userID (e.g. "group:telegram:-100123")
|
|
}
|
|
|
|
// GatewayConfig controls the gateway server.
|
|
type GatewayConfig struct {
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
Token string `json:"token,omitempty"` // bearer token for WS/HTTP auth
|
|
OwnerIDs []string `json:"owner_ids,omitempty"` // sender IDs considered "owner"
|
|
AllowedOrigins []string `json:"allowed_origins,omitempty"` // WebSocket CORS whitelist (empty = allow all)
|
|
MaxMessageChars int `json:"max_message_chars,omitempty"` // max user message characters (default 32000)
|
|
RateLimitRPM int `json:"rate_limit_rpm,omitempty"` // rate limit: requests per minute per user (default 20, 0 = disabled)
|
|
InjectionAction string `json:"injection_action,omitempty"` // prompt injection action: "log", "warn" (default), "block", "off"
|
|
InboundDebounceMs int `json:"inbound_debounce_ms,omitempty"` // merge rapid messages from same sender (default 1000ms, -1 = disabled)
|
|
Quota *QuotaConfig `json:"quota,omitempty"` // per-user/group request quotas
|
|
BlockReply *bool `json:"block_reply,omitempty"` // deliver intermediate text during tool iterations (default false)
|
|
ToolStatus *bool `json:"tool_status,omitempty"` // show tool name in streaming preview during tool execution (default true)
|
|
TaskRecoveryIntervalSec int `json:"task_recovery_interval_sec,omitempty"` // team task recovery ticker interval in seconds (default 300 = 5min)
|
|
}
|
|
|
|
// ToolsConfig controls tool availability, policy, and web search.
|
|
type ToolsConfig struct {
|
|
Profile string `json:"profile,omitempty"` // global profile: "minimal", "coding", "messaging", "full"
|
|
Allow []string `json:"allow,omitempty"` // global allow list (tool names or "group:xxx")
|
|
Deny []string `json:"deny,omitempty"` // global deny list
|
|
AlsoAllow []string `json:"alsoAllow,omitempty"` // additive: adds without removing existing
|
|
ByProvider map[string]*ToolPolicySpec `json:"byProvider,omitempty"` // per-provider overrides
|
|
ExecApproval ExecApprovalCfg `json:"execApproval"` // exec command approval settings
|
|
WebFetch WebFetchPolicyConfig `json:"web_fetch"` // domain policy for URL fetching
|
|
Web WebToolsConfig `json:"web"`
|
|
Browser BrowserToolConfig `json:"browser"`
|
|
RateLimitPerHour int `json:"rate_limit_per_hour,omitempty"` // max tool executions per hour per session (0 = disabled)
|
|
ScrubCredentials *bool `json:"scrub_credentials,omitempty"` // auto-redact API keys/tokens in tool output (default true)
|
|
McpServers map[string]*MCPServerConfig `json:"mcp_servers,omitempty"` // external MCP server connections
|
|
}
|
|
|
|
// MCPServerConfig configures a single external MCP server connection.
|
|
type MCPServerConfig struct {
|
|
Transport string `json:"transport"` // "stdio", "sse", "streamable-http"
|
|
Command string `json:"command,omitempty"` // stdio: command to spawn
|
|
Args []string `json:"args,omitempty"` // stdio: command arguments
|
|
Env map[string]string `json:"env,omitempty"` // stdio: extra environment variables
|
|
URL string `json:"url,omitempty"` // sse/http: server URL
|
|
Headers map[string]string `json:"headers,omitempty"` // sse/http: extra HTTP headers
|
|
Enabled *bool `json:"enabled,omitempty"` // default true
|
|
ToolPrefix string `json:"tool_prefix,omitempty"` // prefix for tool names (avoids collisions)
|
|
TimeoutSec int `json:"timeout_sec,omitempty"` // per-tool-call timeout in seconds (default 60)
|
|
}
|
|
|
|
// IsEnabled returns whether this MCP server is enabled (default true).
|
|
func (c *MCPServerConfig) IsEnabled() bool {
|
|
return c.Enabled == nil || *c.Enabled
|
|
}
|
|
|
|
// ExecApprovalCfg configures command execution approval (matching TS exec-approval.ts).
|
|
type ExecApprovalCfg struct {
|
|
Security string `json:"security,omitempty"` // "deny", "allowlist", "full" (default "full")
|
|
Ask string `json:"ask,omitempty"` // "off", "on-miss", "always" (default "off")
|
|
Allowlist []string `json:"allowlist,omitempty"` // glob patterns for allowed commands
|
|
}
|
|
|
|
// WebFetchPolicyConfig controls domain filtering for the web_fetch tool.
|
|
type WebFetchPolicyConfig struct {
|
|
Policy string `json:"policy,omitempty"` // "allow_all" (default), "allowlist"
|
|
AllowedDomains []string `json:"allowed_domains,omitempty"` // e.g. ["github.com", "*.example.com"]
|
|
BlockedDomains []string `json:"blocked_domains,omitempty"` // always checked regardless of policy
|
|
}
|
|
|
|
// BrowserToolConfig controls the browser automation tool.
|
|
type BrowserToolConfig struct {
|
|
Enabled bool `json:"enabled"` // enable the browser tool (default false)
|
|
Headless bool `json:"headless,omitempty"` // run Chrome in headless mode (ignored when RemoteURL is set)
|
|
RemoteURL string `json:"remote_url,omitempty"` // CDP endpoint for remote Chrome sidecar, e.g. "ws://chrome:9222"
|
|
}
|
|
|
|
// ToolPolicySpec defines a tool policy at any level (global, per-agent, per-provider).
|
|
type ToolPolicySpec struct {
|
|
Profile string `json:"profile,omitempty"`
|
|
Allow []string `json:"allow,omitempty"`
|
|
Deny []string `json:"deny,omitempty"`
|
|
AlsoAllow []string `json:"alsoAllow,omitempty"`
|
|
ByProvider map[string]*ToolPolicySpec `json:"byProvider,omitempty"`
|
|
}
|
|
|
|
type WebToolsConfig struct {
|
|
Brave BraveConfig `json:"brave"`
|
|
DuckDuckGo DuckDuckGoConfig `json:"duckduckgo"`
|
|
}
|
|
|
|
type BraveConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
APIKey string `json:"api_key"`
|
|
MaxResults int `json:"max_results"`
|
|
}
|
|
|
|
type DuckDuckGoConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
MaxResults int `json:"max_results"`
|
|
}
|
|
|
|
// SessionsConfig controls session behavior.
|
|
// Matching TS src/config/sessions/types.ts + src/config/types.base.ts.
|
|
type SessionsConfig struct {
|
|
Scope string `json:"scope,omitempty"` // "per-sender" (default), "global"
|
|
DmScope string `json:"dm_scope,omitempty"` // "main", "per-peer", "per-channel-peer" (default), "per-account-channel-peer"
|
|
MainKey string `json:"main_key,omitempty"` // main session key suffix (default "main", used when dm_scope="main")
|
|
}
|
|
|
|
// TtsConfig configures text-to-speech.
|
|
// Matching TS src/config/types.tts.ts.
|
|
type TtsConfig struct {
|
|
Provider string `json:"provider,omitempty"` // "openai", "elevenlabs", "edge", "minimax"
|
|
Auto string `json:"auto,omitempty"` // "off" (default), "always", "inbound", "tagged"
|
|
Mode string `json:"mode,omitempty"` // "final" (default), "all"
|
|
MaxLength int `json:"max_length,omitempty"` // max text length before truncation (default 1500)
|
|
TimeoutMs int `json:"timeout_ms,omitempty"` // API timeout in ms (default 30000)
|
|
OpenAI TtsOpenAIConfig `json:"openai"`
|
|
ElevenLabs TtsElevenLabsConfig `json:"elevenlabs"`
|
|
Edge TtsEdgeConfig `json:"edge"`
|
|
MiniMax TtsMiniMaxConfig `json:"minimax"`
|
|
}
|
|
|
|
// TtsOpenAIConfig configures the OpenAI TTS provider.
|
|
type TtsOpenAIConfig struct {
|
|
APIKey string `json:"api_key,omitempty"`
|
|
APIBase string `json:"api_base,omitempty"` // custom endpoint URL
|
|
Model string `json:"model,omitempty"` // default "gpt-4o-mini-tts"
|
|
Voice string `json:"voice,omitempty"` // default "alloy"
|
|
}
|
|
|
|
// TtsElevenLabsConfig configures the ElevenLabs TTS provider.
|
|
type TtsElevenLabsConfig struct {
|
|
APIKey string `json:"api_key,omitempty"`
|
|
BaseURL string `json:"base_url,omitempty"`
|
|
VoiceID string `json:"voice_id,omitempty"` // default "pMsXgVXv3BLzUgSXRplE"
|
|
ModelID string `json:"model_id,omitempty"` // default "eleven_multilingual_v2"
|
|
}
|
|
|
|
// TtsEdgeConfig configures the Microsoft Edge TTS provider (free, no API key).
|
|
type TtsEdgeConfig struct {
|
|
Enabled bool `json:"enabled,omitempty"`
|
|
Voice string `json:"voice,omitempty"` // default "en-US-MichelleNeural"
|
|
Rate string `json:"rate,omitempty"` // speech rate, e.g. "+0%"
|
|
}
|
|
|
|
// TtsMiniMaxConfig configures the MiniMax TTS provider.
|
|
type TtsMiniMaxConfig struct {
|
|
APIKey string `json:"api_key,omitempty"`
|
|
GroupID string `json:"group_id,omitempty"` // MiniMax GroupId (required)
|
|
APIBase string `json:"api_base,omitempty"` // default "https://api.minimax.io/v1"
|
|
Model string `json:"model,omitempty"` // default "speech-02-hd"
|
|
VoiceID string `json:"voice_id,omitempty"` // default "Wise_Woman"
|
|
}
|
|
|
|
// MergeChannelGroupQuotas merges per-group quota overrides from channel configs
|
|
// (e.g., channels.telegram.groups[chatID].quota) into gateway.quota.groups.
|
|
// This allows per-group quotas to be set at the channel level and picked up
|
|
// by the QuotaChecker at runtime.
|
|
func MergeChannelGroupQuotas(cfg *Config) {
|
|
if cfg.Gateway.Quota == nil {
|
|
return
|
|
}
|
|
if cfg.Gateway.Quota.Groups == nil {
|
|
cfg.Gateway.Quota.Groups = make(map[string]QuotaWindow)
|
|
}
|
|
|
|
// Telegram per-group quotas
|
|
for chatID, groupCfg := range cfg.Channels.Telegram.Groups {
|
|
if groupCfg != nil && groupCfg.Quota != nil && !groupCfg.Quota.IsZero() {
|
|
key := "group:telegram:" + chatID
|
|
cfg.Gateway.Quota.Groups[key] = *groupCfg.Quota
|
|
}
|
|
}
|
|
}
|