Files
Thibault Jaigu 32eb2328f3 feat(provider): add Requesty OpenAI-compatible provider (#1579)
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.
2026-09-26 03:33:33 +07:00

682 lines
43 KiB
Go

package config
// ChatBehaviorConfig controls optional human-like channel delivery behavior.
// Pointer fields allow per-channel overrides to inherit gateway defaults.
type ChatBehaviorConfig struct {
Enabled *bool `json:"enabled,omitempty"`
IntermediateReplies *IntermediateRepliesConfig `json:"intermediate_replies,omitempty"`
QuickAck *QuickAckConfig `json:"quick_ack,omitempty"`
FinalSplit *FinalSplitConfig `json:"final_split,omitempty"`
}
// QuickAckConfig controls one short acknowledgement before longer non-streaming runs.
type QuickAckConfig struct {
Enabled *bool `json:"enabled,omitempty"`
Mode *string `json:"mode,omitempty"`
MinDelayMs *int `json:"min_delay_ms,omitempty"`
Provider string `json:"provider,omitempty"`
Model string `json:"model,omitempty"`
TimeoutMs *int `json:"timeout_ms,omitempty"`
MaxTokens *int `json:"max_tokens,omitempty"`
MaxChars *int `json:"max_chars,omitempty"`
Templates []string `json:"templates,omitempty"`
}
// IntermediateRepliesConfig controls delivery-only progress messages during tool phases.
type IntermediateRepliesConfig struct {
Enabled *bool `json:"enabled,omitempty"`
Mode *string `json:"mode,omitempty"`
Provider string `json:"provider,omitempty"`
Model string `json:"model,omitempty"`
TimeoutMs *int `json:"timeout_ms,omitempty"`
MaxTokens *int `json:"max_tokens,omitempty"`
MaxChars *int `json:"max_chars,omitempty"`
}
// FinalSplitConfig controls semantic splitting of final channel replies.
type FinalSplitConfig struct {
Enabled *bool `json:"enabled,omitempty"`
MinChars *int `json:"min_chars,omitempty"`
MaxMessages *int `json:"max_messages,omitempty"`
DelayMs *int `json:"delay_ms,omitempty"`
}
// 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 200)
KeepRecent int `json:"keep_recent,omitempty"` // keep this many recent raw messages after compaction (default 40)
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
}
// TelegramManagerConfig controls the hidden Telegram management tool for this channel.
// It is intentionally channel-scoped instead of part of the global builtin tool profiles.
type TelegramManagerConfig struct {
Enabled bool `json:"enabled,omitempty"`
AllowedActions []string `json:"allowed_actions,omitempty"`
}
// 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)
MentionMode string `json:"mention_mode,omitempty"` // "strict" (default) = only respond when mentioned; "yield" = respond unless another bot is mentioned
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
ReasoningDelivery string `json:"reasoning_delivery,omitempty"` // off, streaming_only, always_bubbles
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)
ChatBehavior *ChatBehaviorConfig `json:"chat_behavior,omitempty"` // override gateway chat behavior (nil = inherit)
ForceIPv4 bool `json:"force_ipv4,omitempty"` // force IPv4 for all Telegram API requests (use when IPv6 routing is broken)
TelegramManager *TelegramManagerConfig `json:"telegram_manager,omitempty"` // hidden Telegram-only management tool permissions
// 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")
// Audio guard: intercept technical errors in voice agent replies and replace with friendly fallbacks.
// Only active when VoiceAgentID is set. Custom error markers replace built-in defaults when provided.
AudioGuardFallbackTranscript string `json:"audio_guard_fallback_transcript,omitempty"` // fallback with %s for transcript (e.g. "I heard: \"%s\". Try again!")
AudioGuardFallbackNoTranscript string `json:"audio_guard_fallback_no_transcript,omitempty"` // fallback when no transcript available
AudioGuardErrorMarkers []string `json:"audio_guard_error_markers,omitempty"` // custom error detection markers (replaces defaults)
// 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
MentionMode string `json:"mention_mode,omitempty"` // override mention_mode 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"`
MentionMode string `json:"mention_mode,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 200, 0=disabled)
BlockReply *bool `json:"block_reply,omitempty"` // override gateway block_reply (nil = inherit)
ChatBehavior *ChatBehaviorConfig `json:"chat_behavior,omitempty"` // override gateway chat behavior (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)
ChatBehavior *ChatBehaviorConfig `json:"chat_behavior,omitempty"` // override gateway chat behavior (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"`
AuthDir string `json:"auth_dir,omitempty"` // optional: SQLite auth dir override (desktop)
AllowFrom FlexibleStringSlice `json:"allow_from"`
DMPolicy string `json:"dm_policy,omitempty"` // "pairing" (default for DB instances), "open", "allowlist", "disabled"
GroupPolicy string `json:"group_policy,omitempty"` // "pairing" (default for DB instances), "open" (default for config), "allowlist", "disabled"
RequireMention *bool `json:"require_mention,omitempty"` // only respond in groups when bot is @mentioned (default false)
HistoryLimit int `json:"history_limit,omitempty"` // max pending group messages for context (default 200, 0=disabled)
BlockReply *bool `json:"block_reply,omitempty"` // override gateway block_reply (nil = inherit)
ChatBehavior *ChatBehaviorConfig `json:"chat_behavior,omitempty"` // override gateway chat behavior (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)
ChatBehavior *ChatBehaviorConfig `json:"chat_behavior,omitempty"` // override gateway chat behavior (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)
ChatBehavior *ChatBehaviorConfig `json:"chat_behavior,omitempty"` // override gateway chat behavior (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)
ChatBehavior *ChatBehaviorConfig `json:"chat_behavior,omitempty"` // override gateway chat behavior (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"`
AtlasCloud ProviderConfig `json:"atlascloud"` // Atlas Cloud (OpenAI-compatible endpoint)
APIRoute ProviderConfig `json:"api_route"` // API Route (OpenAI-compatible endpoint)
Requesty ProviderConfig `json:"requesty"` // Requesty (OpenAI-compatible router)
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"`
Novita ProviderConfig `json:"novita"` // Novita AI (OpenAI-compatible endpoint)
BytePlus ProviderConfig `json:"byteplus"` // BytePlus ModelArk (Seed 2.0)
BytePlusCoding ProviderConfig `json:"byteplus_coding"` // BytePlus ModelArk Coding Plan
Vertex VertexConfig `json:"vertex"` // Google Cloud Vertex AI (OAuth2 service account + ADC)
// RequestTimeoutSec bounds provider verify and models-list HTTP calls.
// Tenant-scoped, overridable via the "providers.request_timeout_sec" system config.
RequestTimeoutSec int `json:"request_timeout_sec,omitempty"`
}
// VertexConfig configures Google Cloud Vertex AI.
// Credentials precedence: APIKey (inline JSON) > CredentialsFile (path) > ADC (both empty).
// ProjectID and Region are required; Model optional (defaults to google/gemini-2.0-flash-001).
type VertexConfig struct {
APIKey string `json:"api_key,omitempty"` // service account JSON inline (secret — never persist in config.json)
CredentialsFile string `json:"credentials_file,omitempty"` // path to service account JSON file
ProjectID string `json:"project_id,omitempty"`
Region string `json:"region,omitempty"`
Model string `json:"model,omitempty"`
}
// 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 "atlascloud":
return p.AtlasCloud.APIBase
case "api_route":
return p.APIRoute.APIBase
case "requesty":
return p.Requesty.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
case "novita":
return p.Novita.APIBase
case "byteplus":
return p.BytePlus.APIBase
case "byteplus_coding":
return p.BytePlusCoding.APIBase
case "vertex":
// Computed from project+region at registration time; no config-level static base.
return ""
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.AtlasCloud.APIKey != "" ||
p.APIRoute.APIKey != "" ||
p.Requesty.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 != "" ||
p.Novita.APIKey != "" ||
p.BytePlus.APIKey != "" ||
p.BytePlusCoding.APIKey != "" ||
(p.Vertex.ProjectID != "" && p.Vertex.Region != "")
}
// 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
MCPServerToken string `json:"mcp_server_token,omitempty"` // bearer token gating the CRUD MCP server mounted at /api/mcp/; callers may pass an optional "X-GoClaw-Tenant-Id" header (UUID or slug) to scope a request to a tenant, defaulting to the master tenant when absent (see internal/mcp/crud_server.go)
OwnerIDs []string `json:"owner_ids,omitempty"` // sender IDs considered "owner"
AllowedOrigins []string `json:"allowed_origins,omitempty"` // WebSocket CORS whitelist (empty = allow all)
MCPAllowedHosts []string `json:"mcp_allowed_hosts,omitempty"` // trusted MCP server hostnames exempt from the private-IP SSRF block during config validation (empty = none)
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"` // silence-window in ms that merges rapid channel/Web Chat messages from the same sender/session; 0 disables for text but media-bearing messages still honor a built-in media floor so multi-attachment bursts (#63) coalesce into a single agent run. Agents may override via per-agent agent_config.inbound_debounce_ms.
Quota *QuotaConfig `json:"quota,omitempty"` // per-user/group request quotas
BlockReply *bool `json:"block_reply,omitempty"` // deliver intermediate text during tool iterations (default false)
ChatBehavior *ChatBehaviorConfig `json:"chat_behavior,omitempty"` // human-like channel delivery behavior (default disabled)
ToolStatus *bool `json:"tool_status,omitempty"` // show tool name in streaming preview during tool execution (default true)
TeamWorkClassify *bool `json:"team_work_classify,omitempty"` // classify new requests as direct handling or team workflow (default false)
TaskRecoveryIntervalSec int `json:"task_recovery_interval_sec,omitempty"` // team task recovery ticker interval in seconds (default 300 = 5min)
WebhookAsyncTimeoutSec int `json:"webhook_async_timeout_sec,omitempty"` // async webhook worker agent-run deadline in seconds (default 600, cap 3600)
WebhookSyncTimeoutSec int `json:"webhook_sync_timeout_sec,omitempty"` // sync + test webhook handler agent-run deadline in seconds (default 600, cap 3600). NOTE: sync holds the HTTP connection open for this duration — a value above an upstream proxy/LB read timeout may be cut.
WebhookStream *bool `json:"webhook_stream,omitempty"` // stream provider responses for server-side webhook agent runs (sync/async/test) so the upstream can populate/serve its prompt cache (default true). Response to the caller is unchanged (still assembled JSON).
BackgroundProvider string `json:"background_provider,omitempty"` // LLM provider for background workers (vault enrichment, consolidation)
BackgroundModel string `json:"background_model,omitempty"` // LLM model for background workers
PublicURL string `json:"public_url,omitempty"` // public base URL for OAuth callbacks (e.g. "https://goclaw.example.com")
}
// 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
ShellDenyGroups map[string]bool `json:"shellDenyGroups,omitempty"` // global shell deny-group toggles (group name -> denied); per-agent overrides win per-key
CommandKeywordAllowlist []CommandKeywordAllowlistRule `json:"commandKeywordAllowlist,omitempty"` // scoped bypass for credentialed CLI content keywords
ExecApproval ExecApprovalCfg `json:"execApproval"` // exec command approval settings
WebFetch WebFetchPolicyConfig `json:"web_fetch"` // domain policy for URL fetching
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
DocumentParser DocumentParserConfig `json:"document_parser"` // local-first document text extraction
}
// DocumentParserConfig controls local-first document text extraction in the
// read_document tool. Local-first is OPT-IN (disabled by default): plain
// pdftotext/pandoc output mis-orders tables/multi-column layouts and ignores
// the analysis prompt, so the cloud vision path remains the default. When
// enabled, PDF/DOCX are extracted via local binaries before any cloud LLM
// call; missing binaries or empty output fall back to the provider chain.
//
// Changing these values requires a gateway restart — they are captured at tool
// construction and not picked up by config hot-reload. (Binary availability,
// however, is re-checked per call, so runtime installs are seen without a
// restart.)
type DocumentParserConfig struct {
LocalFirst *bool `json:"local_first,omitempty"` // nil => false (default OFF)
MaxPages int `json:"max_pages,omitempty"` // 0 => 200
TimeoutSec int `json:"timeout_sec,omitempty"` // 0 => 30s
MinTextLen int `json:"min_text_len,omitempty"` // 0 => 16
}
// LocalFirstEnabled reports whether local-first extraction is turned on.
func (c DocumentParserConfig) LocalFirstEnabled() bool {
return c.LocalFirst != nil && *c.LocalFirst
}
// CommandKeywordAllowlistRule scopes product/security vocabulary that may appear
// in credentialed CLI content args without disabling command-path deny patterns.
type CommandKeywordAllowlistRule struct {
ID string `json:"id,omitempty"`
Command string `json:"command"`
Subcommands []string `json:"subcommands,omitempty"`
Args []string `json:"args,omitempty"`
ArgPositions []int `json:"argPositions,omitempty"` // 0-based after matched subcommand
Keywords []string `json:"keywords,omitempty"`
Reason string `json:"reason,omitempty"`
Enabled *bool `json:"enabled,omitempty"` // nil means enabled
}
// 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)
Backend string `json:"backend,omitempty"` // "chrome" (default) or "lightpanda"; auto-detected from /json/version if empty
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"
ActionTimeoutMs int `json:"action_timeout_ms,omitempty"` // per-action timeout in ms (default 30000)
IdleTimeoutMs int `json:"idle_timeout_ms,omitempty"` // idle page auto-close in ms (default 600000, 0=disabled)
MaxPages int `json:"max_pages,omitempty"` // max open pages per tenant (default 5)
CookieSyncEnabled bool `json:"cookie_sync_enabled"` // apply selected synced cookies to scoped browser sessions
}
// 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"`
Wait *WaitToolPolicy `json:"wait,omitempty"`
ToolCallPrefix string `json:"toolCallPrefix,omitempty"` // prefix to strip from model's tool call names before registry lookup
// RateLimitPerHour overrides the global tools.rate_limit_per_hour for this
// agent (applied per session key). 0 = inherit the global limit.
RateLimitPerHour int `json:"rate_limit_per_hour,omitempty"`
}
// WaitToolPolicy configures per-agent safety bounds for the wait tool.
type WaitToolPolicy struct {
MinMs int `json:"min_ms,omitempty"`
MaxMs int `json:"max_ms,omitempty"`
}
// 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", "openai_compat", "elevenlabs", "edge", "minimax", "gemini"
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"`
OpenAICompat TtsOpenAICompatConfig `json:"openai_compat"`
ElevenLabs TtsElevenLabsConfig `json:"elevenlabs"`
Edge TtsEdgeConfig `json:"edge"`
MiniMax TtsMiniMaxConfig `json:"minimax"`
Gemini TtsGeminiConfig `json:"gemini"`
}
// TtsOpenAICompatConfig configures a self-hosted OpenAI-compatible audio
// endpoint (gpu-manager, Speaches, vLLM, LocalAI, llama.cpp, Ollama) for both
// TTS and STT. Kept separate from TtsOpenAIConfig because the voice namespace
// is the engine's own — see internal/audio/openaicompat for why the provider
// name must not be "openai".
//
// Setting api_base is what enables the provider; there is no vendor default.
type TtsOpenAICompatConfig struct {
APIBase string `json:"api_base,omitempty"` // required to enable, e.g. "http://gpu-manager:8080/v1"
APIKey string `json:"api_key,omitempty"` // optional; omitted entirely when empty
Model string `json:"model,omitempty"` // TTS model; optional when the engine picks it from the voice
Voice string `json:"voice,omitempty"` // engine-specific voice ID, e.g. "fr_FR-gilles-low"
Format string `json:"format,omitempty"` // response_format; default "mp3", use "wav" for engines without an encoder
STTModel string `json:"stt_model,omitempty"` // transcription model; optional
}
// TtsGeminiConfig configures the Google Gemini TTS provider.
type TtsGeminiConfig struct {
APIKey string `json:"api_key,omitempty"` // required; encrypted at rest
APIBase string `json:"api_base,omitempty"` // custom endpoint (optional; SSRF-gated)
Voice string `json:"voice,omitempty"` // default "Kore"
Model string `json:"model,omitempty"` // default "gemini-2.5-flash-preview-tts"
Speakers string `json:"speakers,omitempty"` // JSON-encoded []SpeakerVoice for multi-speaker mode
}
// 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
}
}
}