refactor(config): reconcile defaults with main
This commit is contained in:
parent
718a5e7c75
commit
e6e724a827
3 changed files with 175 additions and 40 deletions
|
|
@ -7,6 +7,7 @@ import (
|
|||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
|
@ -251,6 +252,8 @@ type AgentDefaults struct {
|
|||
SubTurn SubTurnConfig `json:"subturn" envPrefix:"PICOCLAW_AGENTS_DEFAULTS_SUBTURN_"`
|
||||
ToolFeedback ToolFeedbackConfig `json:"tool_feedback,omitempty"`
|
||||
SplitOnMarker bool `json:"split_on_marker" env:"PICOCLAW_AGENTS_DEFAULTS_SPLIT_ON_MARKER"` // split messages on <|[SPLIT]|> marker
|
||||
ContextManager string `json:"context_manager,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_CONTEXT_MANAGER"`
|
||||
ContextManagerConfig json.RawMessage `json:"context_manager_config,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_CONTEXT_MANAGER_CONFIG"`
|
||||
}
|
||||
|
||||
const DefaultMaxMediaSize = 20 * 1024 * 1024 // 20 MB
|
||||
|
|
@ -298,6 +301,8 @@ type ChannelsConfig struct {
|
|||
Pico PicoConfig `json:"pico" yaml:"pico,omitempty"`
|
||||
PicoClient PicoClientConfig `json:"pico_client" yaml:"pico_client,omitempty"`
|
||||
IRC IRCConfig `json:"irc" yaml:"irc,omitempty"`
|
||||
VK VKConfig `json:"vk" yaml:"vk,omitempty"`
|
||||
TeamsWebhook TeamsWebhookConfig `json:"teams_webhook" yaml:"teams_webhook,omitempty"`
|
||||
}
|
||||
|
||||
// GroupTriggerConfig controls when the bot responds in group chats.
|
||||
|
|
@ -552,6 +557,34 @@ type IRCConfig struct {
|
|||
ReasoningChannelID string `json:"reasoning_channel_id" yaml:"-"`
|
||||
}
|
||||
|
||||
type VKConfig struct {
|
||||
Enabled bool `json:"enabled" yaml:"-" env:"PICOCLAW_CHANNELS_VK_ENABLED"`
|
||||
Token SecureString `json:"token,omitzero" yaml:"token,omitempty" env:"PICOCLAW_CHANNELS_VK_TOKEN"`
|
||||
GroupID int `json:"group_id" yaml:"-" env:"PICOCLAW_CHANNELS_VK_GROUP_ID"`
|
||||
AllowFrom FlexibleStringSlice `json:"allow_from" yaml:"-" env:"PICOCLAW_CHANNELS_VK_ALLOW_FROM"`
|
||||
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty" yaml:"-"`
|
||||
Typing TypingConfig `json:"typing,omitempty" yaml:"-"`
|
||||
Placeholder PlaceholderConfig `json:"placeholder,omitempty" yaml:"-"`
|
||||
ReasoningChannelID string `json:"reasoning_channel_id" yaml:"-" env:"PICOCLAW_CHANNELS_VK_REASONING_CHANNEL_ID"`
|
||||
}
|
||||
|
||||
func (c *VKConfig) SetToken(token string) {
|
||||
c.Token = *NewSecureString(token)
|
||||
}
|
||||
|
||||
// TeamsWebhookConfig configures the output-only Microsoft Teams webhook channel.
|
||||
// Multiple webhook targets can be configured and selected via ChatID at send time.
|
||||
type TeamsWebhookConfig struct {
|
||||
Enabled bool `json:"enabled" yaml:"-" env:"PICOCLAW_CHANNELS_TEAMS_WEBHOOK_ENABLED"`
|
||||
Webhooks map[string]TeamsWebhookTarget `json:"webhooks" yaml:"webhooks,omitempty"`
|
||||
}
|
||||
|
||||
// TeamsWebhookTarget represents a single Teams webhook destination.
|
||||
type TeamsWebhookTarget struct {
|
||||
WebhookURL SecureString `json:"webhook_url,omitzero" yaml:"webhook_url,omitempty"`
|
||||
Title string `json:"title,omitempty" yaml:"-"`
|
||||
}
|
||||
|
||||
type HeartbeatConfig struct {
|
||||
Enabled bool `json:"enabled" env:"PICOCLAW_HEARTBEAT_ENABLED"`
|
||||
Interval int `json:"interval" env:"PICOCLAW_HEARTBEAT_INTERVAL"` // minutes, min 5
|
||||
|
|
@ -564,6 +597,7 @@ type DevicesConfig struct {
|
|||
|
||||
type VoiceConfig struct {
|
||||
ModelName string `json:"model_name,omitempty" env:"PICOCLAW_VOICE_MODEL_NAME"`
|
||||
TTSModelName string `json:"tts_model_name,omitempty" env:"PICOCLAW_VOICE_TTS_MODEL_NAME"`
|
||||
EchoTranscription bool `json:"echo_transcription" env:"PICOCLAW_VOICE_ECHO_TRANSCRIPTION"`
|
||||
ElevenLabsAPIKey string `json:"elevenlabs_api_key,omitempty" env:"PICOCLAW_VOICE_ELEVENLABS_API_KEY"`
|
||||
}
|
||||
|
|
@ -596,6 +630,7 @@ type ModelConfig struct {
|
|||
RequestTimeout int `json:"request_timeout,omitempty"`
|
||||
ThinkingLevel string `json:"thinking_level,omitempty"` // Extended thinking: off|low|medium|high|xhigh|adaptive
|
||||
ExtraBody map[string]any `json:"extra_body,omitempty"` // Additional fields to inject into request body
|
||||
CustomHeaders map[string]string `json:"custom_headers,omitempty"` // Additional headers to inject into every HTTP request
|
||||
|
||||
APIKeys SecureStrings `json:"api_keys,omitzero" yaml:"api_keys,omitempty"` // API authentication keys (multiple keys for failover)
|
||||
|
||||
|
|
@ -603,6 +638,8 @@ type ModelConfig struct {
|
|||
// existing configs, the field is inferred during load: models with API keys
|
||||
// or the reserved "local-model" name are auto-enabled.
|
||||
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
|
||||
// UserAgent is the user agent string to use for HTTP requests.
|
||||
UserAgent string `json:"user_agent,omitempty" yaml:"-"`
|
||||
|
||||
// isVirtual marks this model as a virtual model generated from multi-key expansion.
|
||||
// Virtual models should not be persisted to config files.
|
||||
|
|
@ -805,9 +842,26 @@ type MediaCleanupConfig struct {
|
|||
|
||||
type ReadFileToolConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Mode string `json:"mode"`
|
||||
MaxReadFileSize int `json:"max_read_file_size"`
|
||||
}
|
||||
|
||||
const (
|
||||
ReadFileModeBytes = "bytes"
|
||||
ReadFileModeLines = "lines"
|
||||
)
|
||||
|
||||
func (c ReadFileToolConfig) EffectiveMode() string {
|
||||
switch strings.ToLower(strings.TrimSpace(c.Mode)) {
|
||||
case ReadFileModeLines:
|
||||
return ReadFileModeLines
|
||||
case "", ReadFileModeBytes:
|
||||
return ReadFileModeBytes
|
||||
default:
|
||||
return ReadFileModeBytes
|
||||
}
|
||||
}
|
||||
|
||||
type ToolsConfig struct {
|
||||
AllowReadPaths []string `json:"allow_read_paths" yaml:"-" env:"PICOCLAW_TOOLS_ALLOW_READ_PATHS"`
|
||||
AllowWritePaths []string `json:"allow_write_paths" yaml:"-" env:"PICOCLAW_TOOLS_ALLOW_WRITE_PATHS"`
|
||||
|
|
@ -834,6 +888,7 @@ type ToolsConfig struct {
|
|||
Message ToolConfig `json:"message" yaml:"-" envPrefix:"PICOCLAW_TOOLS_MESSAGE_"`
|
||||
ReadFile ReadFileToolConfig `json:"read_file" yaml:"-" envPrefix:"PICOCLAW_TOOLS_READ_FILE_"`
|
||||
SendFile ToolConfig `json:"send_file" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SEND_FILE_"`
|
||||
SendTTS ToolConfig `json:"send_tts" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SEND_TTS_"`
|
||||
Spawn ToolConfig `json:"spawn" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SPAWN_"`
|
||||
SpawnStatus ToolConfig `json:"spawn_status" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SPAWN_STATUS_"`
|
||||
SPI ToolConfig `json:"spi" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SPI_"`
|
||||
|
|
@ -909,10 +964,21 @@ type MCPServerConfig struct {
|
|||
type MCPConfig struct {
|
||||
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_MCP_"`
|
||||
Discovery ToolDiscoveryConfig ` json:"discovery"`
|
||||
// MaxInlineTextChars controls how much MCP text stays inline before it is saved as an artifact.
|
||||
MaxInlineTextChars int `json:"max_inline_text_chars,omitempty" env:"PICOCLAW_TOOLS_MCP_MAX_INLINE_TEXT_CHARS"`
|
||||
// Servers is a map of server name to server configuration
|
||||
Servers map[string]MCPServerConfig `json:"servers,omitempty"`
|
||||
}
|
||||
|
||||
const DefaultMCPMaxInlineTextChars = 16 * 1024
|
||||
|
||||
func (c *MCPConfig) GetMaxInlineTextChars() int {
|
||||
if c.MaxInlineTextChars > 0 {
|
||||
return c.MaxInlineTextChars
|
||||
}
|
||||
return DefaultMCPMaxInlineTextChars
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
logger.Debugf("loading config from %s", path)
|
||||
|
||||
|
|
@ -1210,6 +1276,8 @@ func expandMultiKeyModels(models []*ModelConfig) []*ModelConfig {
|
|||
RequestTimeout: m.RequestTimeout,
|
||||
ThinkingLevel: m.ThinkingLevel,
|
||||
ExtraBody: m.ExtraBody,
|
||||
CustomHeaders: m.CustomHeaders,
|
||||
UserAgent: m.UserAgent,
|
||||
isVirtual: true,
|
||||
}
|
||||
expanded = append(expanded, additionalEntry)
|
||||
|
|
@ -1230,6 +1298,8 @@ func expandMultiKeyModels(models []*ModelConfig) []*ModelConfig {
|
|||
RequestTimeout: m.RequestTimeout,
|
||||
ThinkingLevel: m.ThinkingLevel,
|
||||
ExtraBody: m.ExtraBody,
|
||||
CustomHeaders: m.CustomHeaders,
|
||||
UserAgent: m.UserAgent,
|
||||
APIKeys: SimpleSecureStrings(keys[0]),
|
||||
}
|
||||
|
||||
|
|
@ -1286,6 +1356,8 @@ func (t *ToolsConfig) IsToolEnabled(name string) bool {
|
|||
return t.WebFetch.Enabled
|
||||
case "send_file":
|
||||
return t.SendFile.Enabled
|
||||
case "send_tts":
|
||||
return t.SendTTS.Enabled
|
||||
case "write_file":
|
||||
return t.WriteFile.Enabled
|
||||
case "mcp":
|
||||
|
|
|
|||
|
|
@ -253,6 +253,41 @@ func TestAgentConfig_ParsesDispatchRules(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDefaultConfig_MCPMaxInlineTextChars(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
if cfg.Tools.MCP.GetMaxInlineTextChars() != DefaultMCPMaxInlineTextChars {
|
||||
t.Fatalf(
|
||||
"DefaultConfig().Tools.MCP.GetMaxInlineTextChars() = %d, want %d",
|
||||
cfg.Tools.MCP.GetMaxInlineTextChars(),
|
||||
DefaultMCPMaxInlineTextChars,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfig_MCPMaxInlineTextChars(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
configPath := filepath.Join(dir, "config.json")
|
||||
raw := `{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"max_inline_text_chars": 2048
|
||||
}
|
||||
}
|
||||
}`
|
||||
if err := os.WriteFile(configPath, []byte(raw), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile(configPath): %v", err)
|
||||
}
|
||||
|
||||
cfg, err := LoadConfig(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig() error: %v", err)
|
||||
}
|
||||
if got := cfg.Tools.MCP.GetMaxInlineTextChars(); got != 2048 {
|
||||
t.Fatalf("cfg.Tools.MCP.GetMaxInlineTextChars() = %d, want 2048", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDefaultConfig_HeartbeatEnabled verifies heartbeat is enabled by default
|
||||
func TestDefaultConfig_HeartbeatEnabled(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
|
|
@ -331,6 +366,13 @@ func TestDefaultConfig_Channels(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDefaultConfig_ReadFileMode(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
if cfg.Tools.ReadFile.EffectiveMode() != ReadFileModeBytes {
|
||||
t.Fatalf("expected default read_file mode %q, got %q", ReadFileModeBytes, cfg.Tools.ReadFile.EffectiveMode())
|
||||
}
|
||||
}
|
||||
|
||||
// TestDefaultConfig_WebTools verifies web tools config
|
||||
func TestDefaultConfig_WebTools(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
|
|
|
|||
|
|
@ -184,6 +184,13 @@ func DefaultConfig() *Config {
|
|||
APIBase: "https://api.deepseek.com/v1",
|
||||
},
|
||||
|
||||
// Venice AI - https://venice.ai
|
||||
{
|
||||
ModelName: "venice-uncensored",
|
||||
Model: "venice/venice-uncensored",
|
||||
APIBase: "https://api.venice.ai/api/v1",
|
||||
},
|
||||
|
||||
// Google Gemini - https://ai.google.dev/
|
||||
{
|
||||
ModelName: "gemini-2.0-flash",
|
||||
|
|
@ -334,6 +341,13 @@ func DefaultConfig() *Config {
|
|||
APIBase: "http://localhost:8000/v1",
|
||||
},
|
||||
|
||||
// LM Studio (local) - http://localhost:1234
|
||||
{
|
||||
ModelName: "lmstudio-local",
|
||||
Model: "lmstudio/openai/gpt-oss-20b",
|
||||
APIBase: "http://localhost:1234/v1",
|
||||
},
|
||||
|
||||
// Azure OpenAI - https://portal.azure.com
|
||||
// model_name is a user-friendly alias; the model field's path after "azure/" is your deployment name
|
||||
{
|
||||
|
|
@ -433,6 +447,9 @@ func DefaultConfig() *Config {
|
|||
SendFile: ToolConfig{
|
||||
Enabled: true,
|
||||
},
|
||||
SendTTS: ToolConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
MCP: MCPConfig{
|
||||
ToolConfig: ToolConfig{
|
||||
Enabled: false,
|
||||
|
|
@ -444,6 +461,7 @@ func DefaultConfig() *Config {
|
|||
UseBM25: true,
|
||||
UseRegex: false,
|
||||
},
|
||||
MaxInlineTextChars: DefaultMCPMaxInlineTextChars,
|
||||
Servers: map[string]MCPServerConfig{},
|
||||
},
|
||||
AppendFile: ToolConfig{
|
||||
|
|
@ -469,6 +487,7 @@ func DefaultConfig() *Config {
|
|||
},
|
||||
ReadFile: ReadFileToolConfig{
|
||||
Enabled: true,
|
||||
Mode: ReadFileModeBytes,
|
||||
MaxReadFileSize: 64 * 1024, // 64KB
|
||||
},
|
||||
Spawn: ToolConfig{
|
||||
|
|
@ -500,7 +519,9 @@ func DefaultConfig() *Config {
|
|||
},
|
||||
Voice: VoiceConfig{
|
||||
ModelName: "",
|
||||
TTSModelName: "",
|
||||
EchoTranscription: false,
|
||||
ElevenLabsAPIKey: "",
|
||||
},
|
||||
BuildInfo: BuildInfo{
|
||||
Version: Version,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue