* update
This commit is contained in:
parent
cadf39c9ef
commit
9cf640daaf
15 changed files with 625 additions and 249 deletions
|
|
@ -71,7 +71,8 @@ func gatewayCmd(debug bool) error {
|
|||
})
|
||||
|
||||
// Setup cron tool and service
|
||||
execTimeout := time.Duration(cfg.Tools.Cron.ExecTimeoutMinutes) * time.Minute
|
||||
cronCfg := cron_tool.GetCronConfig(cfg)
|
||||
execTimeout := time.Duration(cronCfg.ExecTimeoutMinutes) * time.Minute
|
||||
cronService := setupCronTool(
|
||||
agentLoop,
|
||||
msgBus,
|
||||
|
|
@ -233,8 +234,9 @@ func setupCronTool(
|
|||
cronService := cron.NewCronService(cronStorePath, nil)
|
||||
|
||||
// Create and register CronTool
|
||||
if cfg.Tools.Cron.Enabled {
|
||||
cronTool := cron_tool.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, cfg)
|
||||
cronToolCfg := cron_tool.GetCronConfig(cfg)
|
||||
if cronToolCfg.Enabled {
|
||||
cronTool := cron_tool.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, cfg.GetTool("cron"))
|
||||
agentLoop.RegisterTool(cronTool)
|
||||
|
||||
// Set the onJob handler
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/skills"
|
||||
"github.com/sipeed/picoclaw/pkg/tools/find_skills"
|
||||
"github.com/sipeed/picoclaw/pkg/utils"
|
||||
)
|
||||
|
||||
|
|
@ -62,10 +63,8 @@ func skillsInstallFromRegistry(cfg *config.Config, registryName, slug string) er
|
|||
|
||||
fmt.Printf("Installing skill '%s' from %s registry...\n", slug, registryName)
|
||||
|
||||
registryMgr := skills.NewRegistryManagerFromConfig(skills.RegistryConfig{
|
||||
MaxConcurrentSearches: cfg.Tools.Skills.MaxConcurrentSearches,
|
||||
ClawHub: skills.ClawHubConfig(cfg.Tools.Skills.Registries.ClawHub),
|
||||
})
|
||||
skillsCfg := find_skills.GetSkillsConfig(cfg)
|
||||
registryMgr := skills.NewRegistryManagerFromConfig(skillsCfg)
|
||||
|
||||
registry := registryMgr.GetRegistry(registryName)
|
||||
if registry == nil {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ func registerSharedTools(
|
|||
}
|
||||
|
||||
// Message tool
|
||||
if cfg.Tools.Message.Enabled {
|
||||
if cfg.ToolEnabled("message") {
|
||||
messageTool := message.NewMessageTool()
|
||||
messageTool.SetSendCallback(func(channel, chatID, content string) error {
|
||||
msgBus.PublishOutbound(bus.OutboundMessage{
|
||||
|
|
@ -108,7 +108,7 @@ func registerSharedTools(
|
|||
}
|
||||
|
||||
// Spawn tool with allowlist checker
|
||||
if cfg.Tools.Spawn.Enabled {
|
||||
if cfg.ToolEnabled("spawn") {
|
||||
subagentManager := subagent.NewSubagentManager(provider, agent.Model, agent.Workspace, msgBus)
|
||||
subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature)
|
||||
spawnTool := subagent.NewSpawnTool(subagentManager)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ type Config struct {
|
|||
Providers ProvidersConfig `json:"providers,omitempty"`
|
||||
ModelList []ModelConfig `json:"model_list"` // New model-centric provider configuration
|
||||
Gateway GatewayConfig `json:"gateway"`
|
||||
Tools ToolsConfig `json:"tools"`
|
||||
Tools ToolsConfig `json:"tools,omitempty"`
|
||||
ToolList []ToolConfig `json:"tool_list"`
|
||||
Heartbeat HeartbeatConfig `json:"heartbeat"`
|
||||
Devices DevicesConfig `json:"devices"`
|
||||
}
|
||||
|
|
@ -468,7 +469,9 @@ type CronToolConfig struct {
|
|||
}
|
||||
|
||||
type ToolConfig struct {
|
||||
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_ENABLED"` // Default env var, can be overridden per tool
|
||||
Name string `json:"name" env:"PICOCLAW_TOOLS_{{.Name}}_ENABLED"` // Used for env var parsing, not required in JSON if using struct field names
|
||||
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_ENABLED"`
|
||||
Extra map[string]any `json:"extra,omitempty"` // Catch-all for any additional tool-specific configs
|
||||
}
|
||||
|
||||
type ExecConfig struct {
|
||||
|
|
@ -564,6 +567,15 @@ func LoadConfig(path string) (*Config, error) {
|
|||
cfg.ModelList = nil
|
||||
}
|
||||
|
||||
// Pre-scan: if user provides legacy "tools" config, we need to clear the default
|
||||
// ToolList so migration can properly convert tools to tool_list
|
||||
var raw map[string]any
|
||||
if err := json.Unmarshal(data, &raw); err == nil {
|
||||
if _, hasTools := raw["tools"]; hasTools {
|
||||
cfg.ToolList = nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -577,6 +589,12 @@ func LoadConfig(path string) (*Config, error) {
|
|||
cfg.ModelList = ConvertProvidersToModelList(cfg)
|
||||
}
|
||||
|
||||
// Auto-migrate: convert legacy tools config to tool_list for backward compatibility
|
||||
if len(cfg.ToolList) == 0 && cfg.hasToolsConfig() {
|
||||
cfg.ToolList = ConvertToolsToToolList(cfg.Tools)
|
||||
cfg.Tools = ToolsConfig{}
|
||||
}
|
||||
|
||||
// Validate model_list for uniqueness and required fields
|
||||
if err := cfg.ValidateModelList(); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -595,6 +613,109 @@ func SaveConfig(path string, cfg *Config) error {
|
|||
return fileutil.WriteFileAtomic(path, data, 0o600)
|
||||
}
|
||||
|
||||
func (c *Config) hasToolsConfig() bool {
|
||||
return c.Tools.Web.Enabled || c.Tools.Web.Proxy != "" ||
|
||||
c.Tools.Cron.Enabled ||
|
||||
c.Tools.ReadFile.Enabled || c.Tools.WriteFile.Enabled ||
|
||||
c.Tools.EditFile.Enabled || c.Tools.AppendFile.Enabled ||
|
||||
c.Tools.ListDir.Enabled || c.Tools.Exec.Enabled ||
|
||||
c.Tools.FindSkills.Enabled || c.Tools.InstallSkill.Enabled ||
|
||||
c.Tools.Spawn.Enabled || c.Tools.Message.Enabled ||
|
||||
c.Tools.I2C.Enabled || c.Tools.SPI.Enabled ||
|
||||
c.Tools.Skills.MaxConcurrentSearches > 0
|
||||
}
|
||||
|
||||
func ConvertToolsToToolList(tools ToolsConfig) []ToolConfig {
|
||||
var toolList []ToolConfig
|
||||
|
||||
if tools.Web.Enabled || tools.Web.Proxy != "" || tools.Web.Brave.APIKey != "" ||
|
||||
tools.Web.Tavily.APIKey != "" || tools.Web.DuckDuckGo.Enabled ||
|
||||
tools.Web.Perplexity.APIKey != "" {
|
||||
toolList = append(toolList, ToolConfig{Name: "web", Enabled: true, Extra: map[string]any{
|
||||
"brave": tools.Web.Brave,
|
||||
"tavily": tools.Web.Tavily,
|
||||
"duckduckgo": tools.Web.DuckDuckGo,
|
||||
"perplexity": tools.Web.Perplexity,
|
||||
"proxy": tools.Web.Proxy,
|
||||
}})
|
||||
}
|
||||
|
||||
if tools.Cron.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "cron", Enabled: true, Extra: map[string]any{
|
||||
"exec_timeout_minutes": tools.Cron.ExecTimeoutMinutes,
|
||||
}})
|
||||
}
|
||||
|
||||
if tools.ReadFile.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "read-file", Enabled: true})
|
||||
}
|
||||
if tools.WriteFile.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "write-file", Enabled: true})
|
||||
}
|
||||
if tools.EditFile.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "edit-file", Enabled: true})
|
||||
}
|
||||
if tools.AppendFile.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "append-file", Enabled: true})
|
||||
}
|
||||
if tools.ListDir.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "list-dir", Enabled: true})
|
||||
}
|
||||
|
||||
if tools.Exec.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "exec", Enabled: true, Extra: map[string]any{
|
||||
"enable_deny_patterns": tools.Exec.EnableDenyPatterns,
|
||||
"custom_deny_patterns": tools.Exec.CustomDenyPatterns,
|
||||
}})
|
||||
}
|
||||
|
||||
if tools.FindSkills.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "find-skills", Enabled: true})
|
||||
}
|
||||
if tools.InstallSkill.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "install-skill", Enabled: true})
|
||||
}
|
||||
|
||||
if tools.Spawn.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "spawn", Enabled: true})
|
||||
}
|
||||
|
||||
if tools.Message.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "message", Enabled: true})
|
||||
}
|
||||
|
||||
if tools.I2C.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "i2c", Enabled: true})
|
||||
}
|
||||
if tools.SPI.Enabled {
|
||||
toolList = append(toolList, ToolConfig{Name: "spi", Enabled: true})
|
||||
}
|
||||
|
||||
if tools.Skills.MaxConcurrentSearches > 0 || tools.Skills.SearchCache.MaxSize > 0 {
|
||||
toolList = append(toolList, ToolConfig{Name: "skills", Enabled: true, Extra: map[string]any{
|
||||
"registries": tools.Skills.Registries,
|
||||
"max_concurrent_searches": tools.Skills.MaxConcurrentSearches,
|
||||
"search_cache": tools.Skills.SearchCache,
|
||||
}})
|
||||
}
|
||||
|
||||
return toolList
|
||||
}
|
||||
|
||||
func (c *Config) GetTool(name string) *ToolConfig {
|
||||
for i := range c.ToolList {
|
||||
if c.ToolList[i].Name == name {
|
||||
return &c.ToolList[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) ToolEnabled(name string) bool {
|
||||
tc := c.GetTool(name)
|
||||
return tc != nil && tc.Enabled
|
||||
}
|
||||
|
||||
func (c *Config) WorkspacePath() string {
|
||||
return expandHome(c.Agents.Defaults.Workspace)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -284,22 +284,6 @@ func TestDefaultConfig_Channels(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestDefaultConfig_WebTools verifies web tools config
|
||||
func TestDefaultConfig_WebTools(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
|
||||
// Verify web tools defaults
|
||||
if cfg.Tools.Web.Brave.MaxResults != 5 {
|
||||
t.Error("Expected Brave MaxResults 5, got ", cfg.Tools.Web.Brave.MaxResults)
|
||||
}
|
||||
if cfg.Tools.Web.Brave.APIKey != "" {
|
||||
t.Error("Brave API key should be empty by default")
|
||||
}
|
||||
if cfg.Tools.Web.DuckDuckGo.MaxResults != 5 {
|
||||
t.Error("Expected DuckDuckGo MaxResults 5, got ", cfg.Tools.Web.DuckDuckGo.MaxResults)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveConfig_FilePermissions(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("file permission bits are not enforced on Windows")
|
||||
|
|
@ -393,27 +377,6 @@ func TestLoadConfig_OpenAIWebSearchCanBeDisabled(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadConfig_WebToolsProxy(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "config.json")
|
||||
configJSON := `{
|
||||
"agents": {"defaults":{"workspace":"./workspace","model":"gpt4","max_tokens":8192,"max_tool_iterations":20}},
|
||||
"model_list": [{"model_name":"gpt4","model":"openai/gpt-5.2","api_key":"x"}],
|
||||
"tools": {"web":{"proxy":"http://127.0.0.1:7890"}}
|
||||
}`
|
||||
if err := os.WriteFile(configPath, []byte(configJSON), 0o600); err != nil {
|
||||
t.Fatalf("os.WriteFile() error: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := LoadConfig(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig() error: %v", err)
|
||||
}
|
||||
if cfg.Tools.Web.Proxy != "http://127.0.0.1:7890" {
|
||||
t.Fatalf("Tools.Web.Proxy = %q, want %q", cfg.Tools.Web.Proxy, "http://127.0.0.1:7890")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDefaultConfig_DMScope verifies the default dm_scope value
|
||||
func TestDefaultConfig_DMScope(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
|
|
|
|||
|
|
@ -275,85 +275,117 @@ func DefaultConfig() *Config {
|
|||
Host: "127.0.0.1",
|
||||
Port: 18790,
|
||||
},
|
||||
Tools: ToolsConfig{
|
||||
Web: WebToolsConfig{
|
||||
Proxy: "",
|
||||
Brave: BraveConfig{
|
||||
Enabled: false,
|
||||
APIKey: "",
|
||||
MaxResults: 5,
|
||||
},
|
||||
DuckDuckGo: DuckDuckGoConfig{
|
||||
Enabled: true,
|
||||
MaxResults: 5,
|
||||
},
|
||||
Perplexity: PerplexityConfig{
|
||||
Enabled: false,
|
||||
APIKey: "",
|
||||
MaxResults: 5,
|
||||
ToolList: []ToolConfig{
|
||||
{
|
||||
Name: "read-file",
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
Name: "write-file",
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
Name: "edit-file",
|
||||
Enabled: false,
|
||||
},
|
||||
{
|
||||
Name: "append-file",
|
||||
Enabled: false,
|
||||
},
|
||||
{
|
||||
Name: "list-dir",
|
||||
Enabled: false,
|
||||
},
|
||||
{
|
||||
Name: "exec",
|
||||
Enabled: true,
|
||||
Extra: map[string]any{
|
||||
"enable_deny_patterns": true,
|
||||
"custom_deny_patterns": []string{},
|
||||
},
|
||||
},
|
||||
Cron: CronToolConfig{
|
||||
Enabled: true,
|
||||
ExecTimeoutMinutes: 5,
|
||||
},
|
||||
// File tools - each individually configurable
|
||||
ReadFile: ToolConfig{
|
||||
{
|
||||
Name: "find-skills",
|
||||
Enabled: true,
|
||||
},
|
||||
WriteFile: ToolConfig{
|
||||
{
|
||||
Name: "install-skill",
|
||||
Enabled: true,
|
||||
},
|
||||
EditFile: ToolConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
AppendFile: ToolConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
ListDir: ToolConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
// Exec tool
|
||||
Exec: ExecConfig{
|
||||
Enabled: true,
|
||||
EnableDenyPatterns: true,
|
||||
},
|
||||
// Skills tools
|
||||
FindSkills: ToolConfig{
|
||||
{
|
||||
Name: "spawn",
|
||||
Enabled: true,
|
||||
},
|
||||
InstallSkill: ToolConfig{
|
||||
{
|
||||
Name: "message",
|
||||
Enabled: true,
|
||||
},
|
||||
// Subagent tools
|
||||
Spawn: ToolConfig{
|
||||
{
|
||||
Name: "web",
|
||||
Enabled: true,
|
||||
},
|
||||
// Message tool
|
||||
Message: ToolConfig{
|
||||
Enabled: true,
|
||||
},
|
||||
// Hardware tools
|
||||
I2C: ToolConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
SPI: ToolConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
Skills: SkillsToolsConfig{
|
||||
Registries: SkillsRegistriesConfig{
|
||||
ClawHub: ClawHubRegistryConfig{
|
||||
Enabled: true,
|
||||
BaseURL: "https://clawhub.ai",
|
||||
Extra: map[string]any{
|
||||
"brave": map[string]any{
|
||||
"enabled": false,
|
||||
"api_key": "",
|
||||
"max_results": 5,
|
||||
},
|
||||
"tavily": map[string]any{
|
||||
"enabled": false,
|
||||
"api_key": "",
|
||||
"max_results": 5,
|
||||
},
|
||||
"duckduckgo": map[string]any{
|
||||
"enabled": true,
|
||||
"max_results": 5,
|
||||
},
|
||||
"perplexity": map[string]any{
|
||||
"enabled": false,
|
||||
"api_key": "",
|
||||
"max_results": 5,
|
||||
},
|
||||
"proxy": "",
|
||||
},
|
||||
MaxConcurrentSearches: 2,
|
||||
SearchCache: SearchCacheConfig{
|
||||
MaxSize: 50,
|
||||
TTLSeconds: 300,
|
||||
},
|
||||
{
|
||||
Name: "cron",
|
||||
Enabled: true,
|
||||
Extra: map[string]any{
|
||||
"exec_timeout_minutes": 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "i2c",
|
||||
Enabled: false,
|
||||
},
|
||||
{
|
||||
Name: "spi",
|
||||
Enabled: false,
|
||||
},
|
||||
{
|
||||
Name: "skills",
|
||||
Enabled: true,
|
||||
Extra: map[string]any{
|
||||
"registries": map[string]any{
|
||||
"clawhub": map[string]any{
|
||||
"enabled": true,
|
||||
"base_url": "https://clawhub.ai",
|
||||
"search_path": "/api/v1/search",
|
||||
"skills_path": "/api/v1/skills",
|
||||
"download_path": "/api/v1/download",
|
||||
"timeout": 30,
|
||||
"max_zip_size": 10485760,
|
||||
"max_response_size": 5242880,
|
||||
},
|
||||
},
|
||||
"max_concurrent_searches": 2,
|
||||
"search_cache": map[string]any{
|
||||
"max_size": 50,
|
||||
"ttl_seconds": 300,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Tools: ToolsConfig{},
|
||||
Heartbeat: HeartbeatConfig{
|
||||
Enabled: true,
|
||||
Interval: 30,
|
||||
|
|
|
|||
88
pkg/config/parse.go
Normal file
88
pkg/config/parse.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package config
|
||||
|
||||
func GetMap(data map[string]any, key string) (map[string]any, bool) {
|
||||
v, ok := data[key]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
m, ok := v.(map[string]any)
|
||||
return m, ok
|
||||
}
|
||||
|
||||
func GetString(data map[string]any, key string) (string, bool) {
|
||||
v, ok := data[key]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
s, ok := v.(string)
|
||||
return s, ok
|
||||
}
|
||||
|
||||
func GetStringOrDefault(data map[string]any, key string, defaultVal string) string {
|
||||
if v, ok := GetString(data, key); ok {
|
||||
return v
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func GetFloat(data map[string]any, key string) (float64, bool) {
|
||||
v, ok := data[key]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
f, ok := v.(float64)
|
||||
return f, ok
|
||||
}
|
||||
|
||||
func GetBool(data map[string]any, key string) (bool, bool) {
|
||||
v, ok := data[key]
|
||||
if !ok {
|
||||
return false, false
|
||||
}
|
||||
b, ok := v.(bool)
|
||||
return b, ok
|
||||
}
|
||||
|
||||
func GetBoolOrDefault(data map[string]any, key string, defaultVal bool) bool {
|
||||
if v, ok := GetBool(data, key); ok {
|
||||
return v
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func GetStringSlice(data map[string]any, key string) []string {
|
||||
v, ok := data[key]
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
arr, ok := v.([]any)
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
result := make([]string, 0, len(arr))
|
||||
for _, item := range arr {
|
||||
if s, ok := item.(string); ok {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetInt(m map[string]any, key string) (int, bool) {
|
||||
if v, ok := m[key]; ok {
|
||||
switch n := v.(type) {
|
||||
case float64:
|
||||
return int(n), true
|
||||
case int:
|
||||
return n, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func GetIntOrDefault(data map[string]any, key string, defaultVal int) int {
|
||||
if v, ok := GetInt(data, key); ok {
|
||||
return v
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
|
@ -71,37 +71,37 @@ func ConvertConfig(data map[string]any) (*config.Config, []string, error) {
|
|||
cfg := config.DefaultConfig()
|
||||
var warnings []string
|
||||
|
||||
if agents, ok := getMap(data, "agents"); ok {
|
||||
if defaults, ok := getMap(agents, "defaults"); ok {
|
||||
if agents, ok := config.GetMap(data, "agents"); ok {
|
||||
if defaults, ok := config.GetMap(agents, "defaults"); ok {
|
||||
// Prefer model_name, fallback to model for backward compatibility
|
||||
if v, ok := getString(defaults, "model_name"); ok {
|
||||
if v, ok := config.GetString(defaults, "model_name"); ok {
|
||||
cfg.Agents.Defaults.ModelName = v
|
||||
} else if v, ok := getString(defaults, "model"); ok {
|
||||
} else if v, ok := config.GetString(defaults, "model"); ok {
|
||||
cfg.Agents.Defaults.Model = v
|
||||
}
|
||||
if v, ok := getFloat(defaults, "max_tokens"); ok {
|
||||
if v, ok := config.GetFloat(defaults, "max_tokens"); ok {
|
||||
cfg.Agents.Defaults.MaxTokens = int(v)
|
||||
}
|
||||
if v, ok := getFloat(defaults, "temperature"); ok {
|
||||
if v, ok := config.GetFloat(defaults, "temperature"); ok {
|
||||
cfg.Agents.Defaults.Temperature = &v
|
||||
}
|
||||
if v, ok := getFloat(defaults, "max_tool_iterations"); ok {
|
||||
if v, ok := config.GetFloat(defaults, "max_tool_iterations"); ok {
|
||||
cfg.Agents.Defaults.MaxToolIterations = int(v)
|
||||
}
|
||||
if v, ok := getString(defaults, "workspace"); ok {
|
||||
if v, ok := config.GetString(defaults, "workspace"); ok {
|
||||
cfg.Agents.Defaults.Workspace = rewriteWorkspacePath(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if providers, ok := getMap(data, "providers"); ok {
|
||||
if providers, ok := config.GetMap(data, "providers"); ok {
|
||||
for name, val := range providers {
|
||||
pMap, ok := val.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
apiKey, _ := getString(pMap, "api_key")
|
||||
apiBase, _ := getString(pMap, "api_base")
|
||||
apiKey, _ := config.GetString(pMap, "api_key")
|
||||
apiBase, _ := config.GetString(pMap, "api_base")
|
||||
|
||||
if !supportedProviders[name] {
|
||||
if apiKey != "" || apiBase != "" {
|
||||
|
|
@ -117,7 +117,7 @@ func ConvertConfig(data map[string]any) (*config.Config, []string, error) {
|
|||
case "openai":
|
||||
cfg.Providers.OpenAI = config.OpenAIProviderConfig{
|
||||
ProviderConfig: pc,
|
||||
WebSearch: getBoolOrDefault(pMap, "web_search", true),
|
||||
WebSearch: config.GetBoolOrDefault(pMap, "web_search", true),
|
||||
}
|
||||
case "openrouter":
|
||||
cfg.Providers.OpenRouter = pc
|
||||
|
|
@ -133,7 +133,7 @@ func ConvertConfig(data map[string]any) (*config.Config, []string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if channels, ok := getMap(data, "channels"); ok {
|
||||
if channels, ok := config.GetMap(data, "channels"); ok {
|
||||
for name, val := range channels {
|
||||
cMap, ok := val.(map[string]any)
|
||||
if !ok {
|
||||
|
|
@ -143,94 +143,94 @@ func ConvertConfig(data map[string]any) (*config.Config, []string, error) {
|
|||
warnings = append(warnings, fmt.Sprintf("Channel '%s' not supported in PicoClaw, skipping", name))
|
||||
continue
|
||||
}
|
||||
enabled, _ := getBool(cMap, "enabled")
|
||||
allowFrom := getStringSlice(cMap, "allow_from")
|
||||
enabled, _ := config.GetBool(cMap, "enabled")
|
||||
allowFrom := config.GetStringSlice(cMap, "allow_from")
|
||||
|
||||
switch name {
|
||||
case "telegram":
|
||||
cfg.Channels.Telegram.Enabled = enabled
|
||||
cfg.Channels.Telegram.AllowFrom = allowFrom
|
||||
if v, ok := getString(cMap, "token"); ok {
|
||||
if v, ok := config.GetString(cMap, "token"); ok {
|
||||
cfg.Channels.Telegram.Token = v
|
||||
}
|
||||
case "discord":
|
||||
cfg.Channels.Discord.Enabled = enabled
|
||||
cfg.Channels.Discord.AllowFrom = allowFrom
|
||||
if v, ok := getString(cMap, "token"); ok {
|
||||
if v, ok := config.GetString(cMap, "token"); ok {
|
||||
cfg.Channels.Discord.Token = v
|
||||
}
|
||||
case "whatsapp":
|
||||
cfg.Channels.WhatsApp.Enabled = enabled
|
||||
cfg.Channels.WhatsApp.AllowFrom = allowFrom
|
||||
if v, ok := getString(cMap, "bridge_url"); ok {
|
||||
if v, ok := config.GetString(cMap, "bridge_url"); ok {
|
||||
cfg.Channels.WhatsApp.BridgeURL = v
|
||||
}
|
||||
case "feishu":
|
||||
cfg.Channels.Feishu.Enabled = enabled
|
||||
cfg.Channels.Feishu.AllowFrom = allowFrom
|
||||
if v, ok := getString(cMap, "app_id"); ok {
|
||||
if v, ok := config.GetString(cMap, "app_id"); ok {
|
||||
cfg.Channels.Feishu.AppID = v
|
||||
}
|
||||
if v, ok := getString(cMap, "app_secret"); ok {
|
||||
if v, ok := config.GetString(cMap, "app_secret"); ok {
|
||||
cfg.Channels.Feishu.AppSecret = v
|
||||
}
|
||||
if v, ok := getString(cMap, "encrypt_key"); ok {
|
||||
if v, ok := config.GetString(cMap, "encrypt_key"); ok {
|
||||
cfg.Channels.Feishu.EncryptKey = v
|
||||
}
|
||||
if v, ok := getString(cMap, "verification_token"); ok {
|
||||
if v, ok := config.GetString(cMap, "verification_token"); ok {
|
||||
cfg.Channels.Feishu.VerificationToken = v
|
||||
}
|
||||
case "qq":
|
||||
cfg.Channels.QQ.Enabled = enabled
|
||||
cfg.Channels.QQ.AllowFrom = allowFrom
|
||||
if v, ok := getString(cMap, "app_id"); ok {
|
||||
if v, ok := config.GetString(cMap, "app_id"); ok {
|
||||
cfg.Channels.QQ.AppID = v
|
||||
}
|
||||
if v, ok := getString(cMap, "app_secret"); ok {
|
||||
if v, ok := config.GetString(cMap, "app_secret"); ok {
|
||||
cfg.Channels.QQ.AppSecret = v
|
||||
}
|
||||
case "dingtalk":
|
||||
cfg.Channels.DingTalk.Enabled = enabled
|
||||
cfg.Channels.DingTalk.AllowFrom = allowFrom
|
||||
if v, ok := getString(cMap, "client_id"); ok {
|
||||
if v, ok := config.GetString(cMap, "client_id"); ok {
|
||||
cfg.Channels.DingTalk.ClientID = v
|
||||
}
|
||||
if v, ok := getString(cMap, "client_secret"); ok {
|
||||
if v, ok := config.GetString(cMap, "client_secret"); ok {
|
||||
cfg.Channels.DingTalk.ClientSecret = v
|
||||
}
|
||||
case "maixcam":
|
||||
cfg.Channels.MaixCam.Enabled = enabled
|
||||
cfg.Channels.MaixCam.AllowFrom = allowFrom
|
||||
if v, ok := getString(cMap, "host"); ok {
|
||||
if v, ok := config.GetString(cMap, "host"); ok {
|
||||
cfg.Channels.MaixCam.Host = v
|
||||
}
|
||||
if v, ok := getFloat(cMap, "port"); ok {
|
||||
if v, ok := config.GetFloat(cMap, "port"); ok {
|
||||
cfg.Channels.MaixCam.Port = int(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if gateway, ok := getMap(data, "gateway"); ok {
|
||||
if v, ok := getString(gateway, "host"); ok {
|
||||
if gateway, ok := config.GetMap(data, "gateway"); ok {
|
||||
if v, ok := config.GetString(gateway, "host"); ok {
|
||||
cfg.Gateway.Host = v
|
||||
}
|
||||
if v, ok := getFloat(gateway, "port"); ok {
|
||||
if v, ok := config.GetFloat(gateway, "port"); ok {
|
||||
cfg.Gateway.Port = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
if tools, ok := getMap(data, "tools"); ok {
|
||||
if web, ok := getMap(tools, "web"); ok {
|
||||
if tools, ok := config.GetMap(data, "tools"); ok {
|
||||
if web, ok := config.GetMap(tools, "web"); ok {
|
||||
// Migrate old "search" config to "brave" if api_key is present
|
||||
if search, ok := getMap(web, "search"); ok {
|
||||
if v, ok := getString(search, "api_key"); ok {
|
||||
if search, ok := config.GetMap(web, "search"); ok {
|
||||
if v, ok := config.GetString(search, "api_key"); ok {
|
||||
cfg.Tools.Web.Brave.APIKey = v
|
||||
if v != "" {
|
||||
cfg.Tools.Web.Brave.Enabled = true
|
||||
}
|
||||
}
|
||||
if v, ok := getFloat(search, "max_results"); ok {
|
||||
if v, ok := config.GetFloat(search, "max_results"); ok {
|
||||
cfg.Tools.Web.Brave.MaxResults = int(v)
|
||||
cfg.Tools.Web.DuckDuckGo.MaxResults = int(v)
|
||||
}
|
||||
|
|
@ -345,64 +345,3 @@ func rewriteWorkspacePath(path string) string {
|
|||
path = strings.Replace(path, ".openclaw", ".picoclaw", 1)
|
||||
return path
|
||||
}
|
||||
|
||||
func getMap(data map[string]any, key string) (map[string]any, bool) {
|
||||
v, ok := data[key]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
m, ok := v.(map[string]any)
|
||||
return m, ok
|
||||
}
|
||||
|
||||
func getString(data map[string]any, key string) (string, bool) {
|
||||
v, ok := data[key]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
s, ok := v.(string)
|
||||
return s, ok
|
||||
}
|
||||
|
||||
func getFloat(data map[string]any, key string) (float64, bool) {
|
||||
v, ok := data[key]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
f, ok := v.(float64)
|
||||
return f, ok
|
||||
}
|
||||
|
||||
func getBool(data map[string]any, key string) (bool, bool) {
|
||||
v, ok := data[key]
|
||||
if !ok {
|
||||
return false, false
|
||||
}
|
||||
b, ok := v.(bool)
|
||||
return b, ok
|
||||
}
|
||||
|
||||
func getBoolOrDefault(data map[string]any, key string, defaultVal bool) bool {
|
||||
if v, ok := getBool(data, key); ok {
|
||||
return v
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func getStringSlice(data map[string]any, key string) []string {
|
||||
v, ok := data[key]
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
arr, ok := v.([]any)
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
result := make([]string, 0, len(arr))
|
||||
for _, item := range arr {
|
||||
if s, ok := item.(string); ok {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
32
pkg/tools/cron/config.go
Normal file
32
pkg/tools/cron/config.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package cron
|
||||
|
||||
import (
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
type CronToolConfig struct {
|
||||
Enabled bool
|
||||
ExecTimeoutMinutes int
|
||||
}
|
||||
|
||||
func GetCronConfig(cfg *config.Config) CronToolConfig {
|
||||
tc := cfg.GetTool("cron")
|
||||
if tc == nil {
|
||||
return CronToolConfig{}
|
||||
}
|
||||
return ParseCronConfig(tc)
|
||||
}
|
||||
|
||||
func ParseCronConfig(tc *config.ToolConfig) CronToolConfig {
|
||||
if tc == nil || !tc.Enabled {
|
||||
return CronToolConfig{}
|
||||
}
|
||||
extra := tc.Extra
|
||||
if extra == nil {
|
||||
return CronToolConfig{Enabled: true}
|
||||
}
|
||||
return CronToolConfig{
|
||||
Enabled: true,
|
||||
ExecTimeoutMinutes: config.GetIntOrDefault(extra, "exec_timeout_minutes", 5),
|
||||
}
|
||||
}
|
||||
|
|
@ -34,10 +34,17 @@ type CronTool struct {
|
|||
// execTimeout: 0 means no timeout, >0 sets the timeout duration
|
||||
func NewCronTool(
|
||||
cronService *cron.CronService, executor JobExecutor, msgBus *bus.MessageBus, workspace string, restrict bool,
|
||||
execTimeout time.Duration, config *config.Config,
|
||||
execTimeout time.Duration, toolConfig *config.ToolConfig,
|
||||
) *CronTool {
|
||||
execTool := exec.NewExecToolWithConfig(workspace, restrict, config)
|
||||
execTool.SetTimeout(execTimeout)
|
||||
execTool := exec.NewExecToolWithConfig(workspace, restrict, toolConfig)
|
||||
if execTimeout > 0 {
|
||||
execTool.SetTimeout(execTimeout)
|
||||
} else if toolConfig != nil {
|
||||
cronCfg := ParseCronConfig(toolConfig)
|
||||
if cronCfg.ExecTimeoutMinutes > 0 {
|
||||
execTool.SetTimeout(time.Duration(cronCfg.ExecTimeoutMinutes) * time.Minute)
|
||||
}
|
||||
}
|
||||
return &CronTool{
|
||||
cronService: cronService,
|
||||
executor: executor,
|
||||
|
|
|
|||
34
pkg/tools/exec/config.go
Normal file
34
pkg/tools/exec/config.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package exec
|
||||
|
||||
import (
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
type ExecToolConfig struct {
|
||||
Enabled bool
|
||||
EnableDenyPatterns bool
|
||||
CustomDenyPatterns []string
|
||||
}
|
||||
|
||||
func GetExecConfig(cfg *config.Config) ExecToolConfig {
|
||||
tc := cfg.GetTool("exec")
|
||||
if tc == nil {
|
||||
return ExecToolConfig{}
|
||||
}
|
||||
return ParseExecConfig(tc)
|
||||
}
|
||||
|
||||
func ParseExecConfig(tc *config.ToolConfig) ExecToolConfig {
|
||||
if tc == nil || !tc.Enabled {
|
||||
return ExecToolConfig{}
|
||||
}
|
||||
extra := tc.Extra
|
||||
if extra == nil {
|
||||
return ExecToolConfig{Enabled: true}
|
||||
}
|
||||
return ExecToolConfig{
|
||||
Enabled: true,
|
||||
EnableDenyPatterns: config.GetBoolOrDefault(extra, "enable_deny_patterns", true),
|
||||
CustomDenyPatterns: config.GetStringSlice(extra, "custom_deny_patterns"),
|
||||
}
|
||||
}
|
||||
|
|
@ -74,11 +74,11 @@ func NewExecTool(workingDir string, restrict bool) *ExecTool {
|
|||
return NewExecToolWithConfig(workingDir, restrict, nil)
|
||||
}
|
||||
|
||||
func NewExecToolWithConfig(workingDir string, restrict bool, config *config.Config) *ExecTool {
|
||||
func NewExecToolWithConfig(workingDir string, restrict bool, toolConfig *config.ToolConfig) *ExecTool {
|
||||
denyPatterns := make([]*regexp.Regexp, 0)
|
||||
|
||||
if config != nil {
|
||||
execConfig := config.Tools.Exec
|
||||
if toolConfig != nil {
|
||||
execConfig := ParseExecConfig(toolConfig)
|
||||
enableDenyPatterns := execConfig.EnableDenyPatterns
|
||||
if enableDenyPatterns {
|
||||
denyPatterns = append(denyPatterns, defaultDenyPatterns...)
|
||||
|
|
|
|||
70
pkg/tools/find_skills/config.go
Normal file
70
pkg/tools/find_skills/config.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package find_skills
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/skills"
|
||||
)
|
||||
|
||||
func GetSkillsConfig(cfg *config.Config) skills.RegistryConfig {
|
||||
tc := cfg.GetTool("skills")
|
||||
if tc == nil || !tc.Enabled {
|
||||
return skills.RegistryConfig{}
|
||||
}
|
||||
extra := tc.Extra
|
||||
if extra == nil {
|
||||
return skills.RegistryConfig{}
|
||||
}
|
||||
skillsCfg := skills.RegistryConfig{}
|
||||
|
||||
if v, ok := extra["registries"]; ok {
|
||||
if m, ok := v.(map[string]any); ok {
|
||||
if clawhub, ok := m["clawhub"]; ok {
|
||||
if cm, ok := clawhub.(map[string]any); ok {
|
||||
skillsCfg.ClawHub = skills.ClawHubConfig{
|
||||
Enabled: config.GetBoolOrDefault(cm, "enabled", false),
|
||||
BaseURL: config.GetStringOrDefault(cm, "base_url", ""),
|
||||
SearchPath: config.GetStringOrDefault(cm, "search_path", ""),
|
||||
SkillsPath: config.GetStringOrDefault(cm, "skills_path", ""),
|
||||
DownloadPath: config.GetStringOrDefault(cm, "download_path", ""),
|
||||
Timeout: config.GetIntOrDefault(cm, "timeout", 30),
|
||||
MaxZipSize: config.GetIntOrDefault(cm, "max_zip_size", 1024*1024*100),
|
||||
MaxResponseSize: config.GetIntOrDefault(cm, "max_response_size", 1024*1024*50),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
skillsCfg.MaxConcurrentSearches = config.GetIntOrDefault(extra, "max_concurrent_searches", 2)
|
||||
return skillsCfg
|
||||
}
|
||||
|
||||
func GetSearchCache(cfg *config.Config) *skills.SearchCache {
|
||||
maxSize, ttlSeconds := GetSearchCacheConfig(cfg)
|
||||
return skills.NewSearchCache(maxSize, time.Duration(ttlSeconds)*time.Second)
|
||||
}
|
||||
|
||||
func GetSearchCacheConfig(cfg *config.Config) (maxSize int, ttlSeconds int) {
|
||||
tc := cfg.GetTool("skills")
|
||||
if tc == nil || !tc.Enabled {
|
||||
return 50, 300
|
||||
}
|
||||
extra := tc.Extra
|
||||
if extra == nil {
|
||||
return 50, 300
|
||||
}
|
||||
if v, ok := extra["search_cache"]; ok {
|
||||
if m, ok := v.(map[string]any); ok {
|
||||
maxSize = config.GetIntOrDefault(m, "max_size", 50)
|
||||
ttlSeconds = config.GetIntOrDefault(m, "ttl_seconds", 300)
|
||||
}
|
||||
}
|
||||
if maxSize <= 0 {
|
||||
maxSize = 50
|
||||
}
|
||||
if ttlSeconds <= 0 {
|
||||
ttlSeconds = 300
|
||||
}
|
||||
return maxSize, ttlSeconds
|
||||
}
|
||||
|
|
@ -42,78 +42,74 @@ func NewToolRegistry(cfg *config.Config, workspace string, restrict bool) *ToolR
|
|||
}
|
||||
|
||||
// File tools - each with individual configuration
|
||||
if cfg.Tools.ReadFile.Enabled {
|
||||
if cfg.ToolEnabled("read-file") {
|
||||
toolsRegistry.Register(read_file.NewReadFileTool(workspace, restrict))
|
||||
}
|
||||
if cfg.Tools.WriteFile.Enabled {
|
||||
if cfg.ToolEnabled("write-file") {
|
||||
toolsRegistry.Register(write_file.NewWriteFileTool(workspace, restrict))
|
||||
}
|
||||
if cfg.Tools.EditFile.Enabled {
|
||||
if cfg.ToolEnabled("edit-file") {
|
||||
toolsRegistry.Register(edit_file.NewEditFileTool(workspace, restrict))
|
||||
}
|
||||
if cfg.Tools.AppendFile.Enabled {
|
||||
if cfg.ToolEnabled("append-file") {
|
||||
toolsRegistry.Register(append_file.NewAppendFileTool(workspace, restrict))
|
||||
}
|
||||
if cfg.Tools.ListDir.Enabled {
|
||||
if cfg.ToolEnabled("list-dir") {
|
||||
toolsRegistry.Register(list_dir.NewListDirTool(workspace, restrict))
|
||||
}
|
||||
|
||||
// Exec tool
|
||||
if cfg.Tools.Exec.Enabled {
|
||||
toolsRegistry.Register(exec.NewExecToolWithConfig(workspace, restrict, cfg))
|
||||
if cfg.ToolEnabled("exec") {
|
||||
toolsRegistry.Register(exec.NewExecToolWithConfig(workspace, restrict, cfg.GetTool("exec")))
|
||||
}
|
||||
|
||||
// Web tools
|
||||
webCfg := web_search.GetWebToolsConfig(cfg)
|
||||
if searchTool := web_search.NewWebSearchTool(web_search.WebSearchToolOptions{
|
||||
BraveAPIKey: cfg.Tools.Web.Brave.APIKey,
|
||||
BraveMaxResults: cfg.Tools.Web.Brave.MaxResults,
|
||||
BraveEnabled: cfg.Tools.Web.Brave.Enabled,
|
||||
TavilyAPIKey: cfg.Tools.Web.Tavily.APIKey,
|
||||
TavilyBaseURL: cfg.Tools.Web.Tavily.BaseURL,
|
||||
TavilyMaxResults: cfg.Tools.Web.Tavily.MaxResults,
|
||||
TavilyEnabled: cfg.Tools.Web.Tavily.Enabled,
|
||||
DuckDuckGoMaxResults: cfg.Tools.Web.DuckDuckGo.MaxResults,
|
||||
DuckDuckGoEnabled: cfg.Tools.Web.DuckDuckGo.Enabled,
|
||||
PerplexityAPIKey: cfg.Tools.Web.Perplexity.APIKey,
|
||||
PerplexityMaxResults: cfg.Tools.Web.Perplexity.MaxResults,
|
||||
PerplexityEnabled: cfg.Tools.Web.Perplexity.Enabled,
|
||||
Proxy: cfg.Tools.Web.Proxy,
|
||||
BraveAPIKey: webCfg.Brave.APIKey,
|
||||
BraveMaxResults: webCfg.Brave.MaxResults,
|
||||
BraveEnabled: webCfg.Brave.Enabled,
|
||||
TavilyAPIKey: webCfg.Tavily.APIKey,
|
||||
TavilyBaseURL: webCfg.Tavily.BaseURL,
|
||||
TavilyMaxResults: webCfg.Tavily.MaxResults,
|
||||
TavilyEnabled: webCfg.Tavily.Enabled,
|
||||
DuckDuckGoMaxResults: webCfg.DuckDuckGo.MaxResults,
|
||||
DuckDuckGoEnabled: webCfg.DuckDuckGo.Enabled,
|
||||
PerplexityAPIKey: webCfg.Perplexity.APIKey,
|
||||
PerplexityMaxResults: webCfg.Perplexity.MaxResults,
|
||||
PerplexityEnabled: webCfg.Perplexity.Enabled,
|
||||
Proxy: webCfg.Proxy,
|
||||
}); searchTool != nil {
|
||||
toolsRegistry.Register(searchTool)
|
||||
}
|
||||
toolsRegistry.Register(web_fetch.NewWebFetchToolWithProxy(50000, cfg.Tools.Web.Proxy))
|
||||
toolsRegistry.Register(web_fetch.NewWebFetchToolWithProxy(50000, webCfg.Proxy))
|
||||
|
||||
// Hardware tools (I2C, SPI) - Linux only, returns error on other platforms
|
||||
if cfg.Tools.I2C.Enabled {
|
||||
if cfg.ToolEnabled("i2c") {
|
||||
toolsRegistry.Register(i2c.NewI2CTool())
|
||||
}
|
||||
if cfg.Tools.SPI.Enabled {
|
||||
if cfg.ToolEnabled("spi") {
|
||||
toolsRegistry.Register(spi.NewSPITool())
|
||||
}
|
||||
|
||||
// Skill discovery and installation tools
|
||||
registryMgr := skills.NewRegistryManagerFromConfig(skills.RegistryConfig{
|
||||
MaxConcurrentSearches: cfg.Tools.Skills.MaxConcurrentSearches,
|
||||
ClawHub: skills.ClawHubConfig(cfg.Tools.Skills.Registries.ClawHub),
|
||||
})
|
||||
searchCache := skills.NewSearchCache(
|
||||
cfg.Tools.Skills.SearchCache.MaxSize,
|
||||
time.Duration(cfg.Tools.Skills.SearchCache.TTLSeconds)*time.Second,
|
||||
)
|
||||
if cfg.Tools.FindSkills.Enabled {
|
||||
skillsCfg := find_skills.GetSkillsConfig(cfg)
|
||||
registryMgr := skills.NewRegistryManagerFromConfig(skillsCfg)
|
||||
searchCache := find_skills.GetSearchCache(cfg)
|
||||
if cfg.ToolEnabled("find-skills") {
|
||||
toolsRegistry.Register(find_skills.NewFindSkillsTool(registryMgr, searchCache))
|
||||
}
|
||||
if cfg.Tools.InstallSkill.Enabled {
|
||||
if cfg.ToolEnabled("install-skill") {
|
||||
toolsRegistry.Register(install_skill.NewInstallSkillTool(registryMgr, workspace))
|
||||
}
|
||||
|
||||
// Message tool
|
||||
if cfg.Tools.Message.Enabled {
|
||||
if cfg.ToolEnabled("message") {
|
||||
toolsRegistry.Register(message.NewMessageTool())
|
||||
}
|
||||
|
||||
// // Spawn tool
|
||||
// if cfg.Tools.Spawn.Enabled {
|
||||
// if cfg.ToolEnabled("spawn") {
|
||||
// // Note: Spawn tool is registered separately in agent loop
|
||||
// }
|
||||
|
||||
|
|
|
|||
93
pkg/tools/web_search/config.go
Normal file
93
pkg/tools/web_search/config.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package web_search
|
||||
|
||||
import (
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
type WebToolsConfig struct {
|
||||
Brave BraveConfig
|
||||
Tavily TavilyConfig
|
||||
DuckDuckGo DuckDuckGoConfig
|
||||
Perplexity PerplexityConfig
|
||||
Proxy string
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type BraveConfig struct {
|
||||
Enabled bool
|
||||
APIKey string
|
||||
MaxResults int
|
||||
}
|
||||
|
||||
type TavilyConfig struct {
|
||||
Enabled bool
|
||||
APIKey string
|
||||
BaseURL string
|
||||
MaxResults int
|
||||
}
|
||||
|
||||
type DuckDuckGoConfig struct {
|
||||
Enabled bool
|
||||
MaxResults int
|
||||
}
|
||||
|
||||
type PerplexityConfig struct {
|
||||
Enabled bool
|
||||
APIKey string
|
||||
MaxResults int
|
||||
}
|
||||
|
||||
func GetWebToolsConfig(cfg *config.Config) WebToolsConfig {
|
||||
tc := cfg.GetTool("web")
|
||||
if tc == nil || !tc.Enabled {
|
||||
return WebToolsConfig{}
|
||||
}
|
||||
extra := tc.Extra
|
||||
if extra == nil {
|
||||
return WebToolsConfig{Enabled: true}
|
||||
}
|
||||
webCfg := WebToolsConfig{Enabled: true}
|
||||
|
||||
if v, ok := extra["brave"]; ok {
|
||||
if m, ok := v.(map[string]any); ok {
|
||||
webCfg.Brave = BraveConfig{
|
||||
Enabled: config.GetBoolOrDefault(m, "enabled", false),
|
||||
APIKey: config.GetStringOrDefault(m, "api_key", ""),
|
||||
MaxResults: config.GetIntOrDefault(m, "max_results", 5),
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := extra["tavily"]; ok {
|
||||
if m, ok := v.(map[string]any); ok {
|
||||
webCfg.Tavily = TavilyConfig{
|
||||
Enabled: config.GetBoolOrDefault(m, "enabled", false),
|
||||
APIKey: config.GetStringOrDefault(m, "api_key", ""),
|
||||
MaxResults: config.GetIntOrDefault(m, "max_results", 5),
|
||||
BaseURL: config.GetStringOrDefault(m, "base_url", ""),
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := extra["duckduckgo"]; ok {
|
||||
if m, ok := v.(map[string]any); ok {
|
||||
webCfg.DuckDuckGo = DuckDuckGoConfig{
|
||||
Enabled: config.GetBoolOrDefault(m, "enabled", false),
|
||||
MaxResults: config.GetIntOrDefault(m, "max_results", 5),
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := extra["perplexity"]; ok {
|
||||
if m, ok := v.(map[string]any); ok {
|
||||
webCfg.Perplexity = PerplexityConfig{
|
||||
Enabled: config.GetBoolOrDefault(m, "enabled", false),
|
||||
APIKey: config.GetStringOrDefault(m, "api_key", ""),
|
||||
MaxResults: config.GetIntOrDefault(m, "max_results", 5),
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := extra["proxy"]; ok {
|
||||
if s, ok := v.(string); ok {
|
||||
webCfg.Proxy = s
|
||||
}
|
||||
}
|
||||
return webCfg
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue