* 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
|
// 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(
|
cronService := setupCronTool(
|
||||||
agentLoop,
|
agentLoop,
|
||||||
msgBus,
|
msgBus,
|
||||||
|
|
@ -233,8 +234,9 @@ func setupCronTool(
|
||||||
cronService := cron.NewCronService(cronStorePath, nil)
|
cronService := cron.NewCronService(cronStorePath, nil)
|
||||||
|
|
||||||
// Create and register CronTool
|
// Create and register CronTool
|
||||||
if cfg.Tools.Cron.Enabled {
|
cronToolCfg := cron_tool.GetCronConfig(cfg)
|
||||||
cronTool := cron_tool.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, cfg)
|
if cronToolCfg.Enabled {
|
||||||
|
cronTool := cron_tool.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, cfg.GetTool("cron"))
|
||||||
agentLoop.RegisterTool(cronTool)
|
agentLoop.RegisterTool(cronTool)
|
||||||
|
|
||||||
// Set the onJob handler
|
// Set the onJob handler
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/skills"
|
"github.com/sipeed/picoclaw/pkg/skills"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/tools/find_skills"
|
||||||
"github.com/sipeed/picoclaw/pkg/utils"
|
"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)
|
fmt.Printf("Installing skill '%s' from %s registry...\n", slug, registryName)
|
||||||
|
|
||||||
registryMgr := skills.NewRegistryManagerFromConfig(skills.RegistryConfig{
|
skillsCfg := find_skills.GetSkillsConfig(cfg)
|
||||||
MaxConcurrentSearches: cfg.Tools.Skills.MaxConcurrentSearches,
|
registryMgr := skills.NewRegistryManagerFromConfig(skillsCfg)
|
||||||
ClawHub: skills.ClawHubConfig(cfg.Tools.Skills.Registries.ClawHub),
|
|
||||||
})
|
|
||||||
|
|
||||||
registry := registryMgr.GetRegistry(registryName)
|
registry := registryMgr.GetRegistry(registryName)
|
||||||
if registry == nil {
|
if registry == nil {
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ func registerSharedTools(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message tool
|
// Message tool
|
||||||
if cfg.Tools.Message.Enabled {
|
if cfg.ToolEnabled("message") {
|
||||||
messageTool := message.NewMessageTool()
|
messageTool := message.NewMessageTool()
|
||||||
messageTool.SetSendCallback(func(channel, chatID, content string) error {
|
messageTool.SetSendCallback(func(channel, chatID, content string) error {
|
||||||
msgBus.PublishOutbound(bus.OutboundMessage{
|
msgBus.PublishOutbound(bus.OutboundMessage{
|
||||||
|
|
@ -108,7 +108,7 @@ func registerSharedTools(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn tool with allowlist checker
|
// Spawn tool with allowlist checker
|
||||||
if cfg.Tools.Spawn.Enabled {
|
if cfg.ToolEnabled("spawn") {
|
||||||
subagentManager := subagent.NewSubagentManager(provider, agent.Model, agent.Workspace, msgBus)
|
subagentManager := subagent.NewSubagentManager(provider, agent.Model, agent.Workspace, msgBus)
|
||||||
subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature)
|
subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature)
|
||||||
spawnTool := subagent.NewSpawnTool(subagentManager)
|
spawnTool := subagent.NewSpawnTool(subagentManager)
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,8 @@ type Config struct {
|
||||||
Providers ProvidersConfig `json:"providers,omitempty"`
|
Providers ProvidersConfig `json:"providers,omitempty"`
|
||||||
ModelList []ModelConfig `json:"model_list"` // New model-centric provider configuration
|
ModelList []ModelConfig `json:"model_list"` // New model-centric provider configuration
|
||||||
Gateway GatewayConfig `json:"gateway"`
|
Gateway GatewayConfig `json:"gateway"`
|
||||||
Tools ToolsConfig `json:"tools"`
|
Tools ToolsConfig `json:"tools,omitempty"`
|
||||||
|
ToolList []ToolConfig `json:"tool_list"`
|
||||||
Heartbeat HeartbeatConfig `json:"heartbeat"`
|
Heartbeat HeartbeatConfig `json:"heartbeat"`
|
||||||
Devices DevicesConfig `json:"devices"`
|
Devices DevicesConfig `json:"devices"`
|
||||||
}
|
}
|
||||||
|
|
@ -468,7 +469,9 @@ type CronToolConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolConfig 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 {
|
type ExecConfig struct {
|
||||||
|
|
@ -564,6 +567,15 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
cfg.ModelList = nil
|
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 {
|
if err := json.Unmarshal(data, cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -577,6 +589,12 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
cfg.ModelList = ConvertProvidersToModelList(cfg)
|
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
|
// Validate model_list for uniqueness and required fields
|
||||||
if err := cfg.ValidateModelList(); err != nil {
|
if err := cfg.ValidateModelList(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -595,6 +613,109 @@ func SaveConfig(path string, cfg *Config) error {
|
||||||
return fileutil.WriteFileAtomic(path, data, 0o600)
|
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 {
|
func (c *Config) WorkspacePath() string {
|
||||||
return expandHome(c.Agents.Defaults.Workspace)
|
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) {
|
func TestSaveConfig_FilePermissions(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("file permission bits are not enforced on 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
|
// TestDefaultConfig_DMScope verifies the default dm_scope value
|
||||||
func TestDefaultConfig_DMScope(t *testing.T) {
|
func TestDefaultConfig_DMScope(t *testing.T) {
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
|
||||||
|
|
@ -275,85 +275,117 @@ func DefaultConfig() *Config {
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 18790,
|
Port: 18790,
|
||||||
},
|
},
|
||||||
Tools: ToolsConfig{
|
ToolList: []ToolConfig{
|
||||||
Web: WebToolsConfig{
|
{
|
||||||
Proxy: "",
|
Name: "read-file",
|
||||||
Brave: BraveConfig{
|
|
||||||
Enabled: false,
|
|
||||||
APIKey: "",
|
|
||||||
MaxResults: 5,
|
|
||||||
},
|
|
||||||
DuckDuckGo: DuckDuckGoConfig{
|
|
||||||
Enabled: true,
|
|
||||||
MaxResults: 5,
|
|
||||||
},
|
|
||||||
Perplexity: PerplexityConfig{
|
|
||||||
Enabled: false,
|
|
||||||
APIKey: "",
|
|
||||||
MaxResults: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Cron: CronToolConfig{
|
|
||||||
Enabled: true,
|
|
||||||
ExecTimeoutMinutes: 5,
|
|
||||||
},
|
|
||||||
// File tools - each individually configurable
|
|
||||||
ReadFile: ToolConfig{
|
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
WriteFile: ToolConfig{
|
{
|
||||||
|
Name: "write-file",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
EditFile: ToolConfig{
|
{
|
||||||
|
Name: "edit-file",
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
},
|
},
|
||||||
AppendFile: ToolConfig{
|
{
|
||||||
|
Name: "append-file",
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
},
|
},
|
||||||
ListDir: ToolConfig{
|
{
|
||||||
|
Name: "list-dir",
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
},
|
},
|
||||||
// Exec tool
|
{
|
||||||
Exec: ExecConfig{
|
Name: "exec",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
EnableDenyPatterns: true,
|
Extra: map[string]any{
|
||||||
|
"enable_deny_patterns": true,
|
||||||
|
"custom_deny_patterns": []string{},
|
||||||
},
|
},
|
||||||
// Skills tools
|
},
|
||||||
FindSkills: ToolConfig{
|
{
|
||||||
|
Name: "find-skills",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
InstallSkill: ToolConfig{
|
{
|
||||||
|
Name: "install-skill",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
// Subagent tools
|
{
|
||||||
Spawn: ToolConfig{
|
Name: "spawn",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
// Message tool
|
{
|
||||||
Message: ToolConfig{
|
Name: "message",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
// Hardware tools
|
{
|
||||||
I2C: ToolConfig{
|
Name: "web",
|
||||||
|
Enabled: true,
|
||||||
|
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": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "cron",
|
||||||
|
Enabled: true,
|
||||||
|
Extra: map[string]any{
|
||||||
|
"exec_timeout_minutes": 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "i2c",
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
},
|
},
|
||||||
SPI: ToolConfig{
|
{
|
||||||
|
Name: "spi",
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
},
|
},
|
||||||
Skills: SkillsToolsConfig{
|
{
|
||||||
Registries: SkillsRegistriesConfig{
|
Name: "skills",
|
||||||
ClawHub: ClawHubRegistryConfig{
|
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
BaseURL: "https://clawhub.ai",
|
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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MaxConcurrentSearches: 2,
|
"max_concurrent_searches": 2,
|
||||||
SearchCache: SearchCacheConfig{
|
"search_cache": map[string]any{
|
||||||
MaxSize: 50,
|
"max_size": 50,
|
||||||
TTLSeconds: 300,
|
"ttl_seconds": 300,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
Tools: ToolsConfig{},
|
||||||
Heartbeat: HeartbeatConfig{
|
Heartbeat: HeartbeatConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Interval: 30,
|
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()
|
cfg := config.DefaultConfig()
|
||||||
var warnings []string
|
var warnings []string
|
||||||
|
|
||||||
if agents, ok := getMap(data, "agents"); ok {
|
if agents, ok := config.GetMap(data, "agents"); ok {
|
||||||
if defaults, ok := getMap(agents, "defaults"); ok {
|
if defaults, ok := config.GetMap(agents, "defaults"); ok {
|
||||||
// Prefer model_name, fallback to model for backward compatibility
|
// 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
|
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
|
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)
|
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
|
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)
|
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)
|
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 {
|
for name, val := range providers {
|
||||||
pMap, ok := val.(map[string]any)
|
pMap, ok := val.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
apiKey, _ := getString(pMap, "api_key")
|
apiKey, _ := config.GetString(pMap, "api_key")
|
||||||
apiBase, _ := getString(pMap, "api_base")
|
apiBase, _ := config.GetString(pMap, "api_base")
|
||||||
|
|
||||||
if !supportedProviders[name] {
|
if !supportedProviders[name] {
|
||||||
if apiKey != "" || apiBase != "" {
|
if apiKey != "" || apiBase != "" {
|
||||||
|
|
@ -117,7 +117,7 @@ func ConvertConfig(data map[string]any) (*config.Config, []string, error) {
|
||||||
case "openai":
|
case "openai":
|
||||||
cfg.Providers.OpenAI = config.OpenAIProviderConfig{
|
cfg.Providers.OpenAI = config.OpenAIProviderConfig{
|
||||||
ProviderConfig: pc,
|
ProviderConfig: pc,
|
||||||
WebSearch: getBoolOrDefault(pMap, "web_search", true),
|
WebSearch: config.GetBoolOrDefault(pMap, "web_search", true),
|
||||||
}
|
}
|
||||||
case "openrouter":
|
case "openrouter":
|
||||||
cfg.Providers.OpenRouter = pc
|
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 {
|
for name, val := range channels {
|
||||||
cMap, ok := val.(map[string]any)
|
cMap, ok := val.(map[string]any)
|
||||||
if !ok {
|
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))
|
warnings = append(warnings, fmt.Sprintf("Channel '%s' not supported in PicoClaw, skipping", name))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
enabled, _ := getBool(cMap, "enabled")
|
enabled, _ := config.GetBool(cMap, "enabled")
|
||||||
allowFrom := getStringSlice(cMap, "allow_from")
|
allowFrom := config.GetStringSlice(cMap, "allow_from")
|
||||||
|
|
||||||
switch name {
|
switch name {
|
||||||
case "telegram":
|
case "telegram":
|
||||||
cfg.Channels.Telegram.Enabled = enabled
|
cfg.Channels.Telegram.Enabled = enabled
|
||||||
cfg.Channels.Telegram.AllowFrom = allowFrom
|
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
|
cfg.Channels.Telegram.Token = v
|
||||||
}
|
}
|
||||||
case "discord":
|
case "discord":
|
||||||
cfg.Channels.Discord.Enabled = enabled
|
cfg.Channels.Discord.Enabled = enabled
|
||||||
cfg.Channels.Discord.AllowFrom = allowFrom
|
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
|
cfg.Channels.Discord.Token = v
|
||||||
}
|
}
|
||||||
case "whatsapp":
|
case "whatsapp":
|
||||||
cfg.Channels.WhatsApp.Enabled = enabled
|
cfg.Channels.WhatsApp.Enabled = enabled
|
||||||
cfg.Channels.WhatsApp.AllowFrom = allowFrom
|
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
|
cfg.Channels.WhatsApp.BridgeURL = v
|
||||||
}
|
}
|
||||||
case "feishu":
|
case "feishu":
|
||||||
cfg.Channels.Feishu.Enabled = enabled
|
cfg.Channels.Feishu.Enabled = enabled
|
||||||
cfg.Channels.Feishu.AllowFrom = allowFrom
|
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
|
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
|
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
|
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
|
cfg.Channels.Feishu.VerificationToken = v
|
||||||
}
|
}
|
||||||
case "qq":
|
case "qq":
|
||||||
cfg.Channels.QQ.Enabled = enabled
|
cfg.Channels.QQ.Enabled = enabled
|
||||||
cfg.Channels.QQ.AllowFrom = allowFrom
|
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
|
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
|
cfg.Channels.QQ.AppSecret = v
|
||||||
}
|
}
|
||||||
case "dingtalk":
|
case "dingtalk":
|
||||||
cfg.Channels.DingTalk.Enabled = enabled
|
cfg.Channels.DingTalk.Enabled = enabled
|
||||||
cfg.Channels.DingTalk.AllowFrom = allowFrom
|
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
|
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
|
cfg.Channels.DingTalk.ClientSecret = v
|
||||||
}
|
}
|
||||||
case "maixcam":
|
case "maixcam":
|
||||||
cfg.Channels.MaixCam.Enabled = enabled
|
cfg.Channels.MaixCam.Enabled = enabled
|
||||||
cfg.Channels.MaixCam.AllowFrom = allowFrom
|
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
|
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)
|
cfg.Channels.MaixCam.Port = int(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if gateway, ok := getMap(data, "gateway"); ok {
|
if gateway, ok := config.GetMap(data, "gateway"); ok {
|
||||||
if v, ok := getString(gateway, "host"); ok {
|
if v, ok := config.GetString(gateway, "host"); ok {
|
||||||
cfg.Gateway.Host = v
|
cfg.Gateway.Host = v
|
||||||
}
|
}
|
||||||
if v, ok := getFloat(gateway, "port"); ok {
|
if v, ok := config.GetFloat(gateway, "port"); ok {
|
||||||
cfg.Gateway.Port = int(v)
|
cfg.Gateway.Port = int(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tools, ok := getMap(data, "tools"); ok {
|
if tools, ok := config.GetMap(data, "tools"); ok {
|
||||||
if web, ok := getMap(tools, "web"); ok {
|
if web, ok := config.GetMap(tools, "web"); ok {
|
||||||
// Migrate old "search" config to "brave" if api_key is present
|
// Migrate old "search" config to "brave" if api_key is present
|
||||||
if search, ok := getMap(web, "search"); ok {
|
if search, ok := config.GetMap(web, "search"); ok {
|
||||||
if v, ok := getString(search, "api_key"); ok {
|
if v, ok := config.GetString(search, "api_key"); ok {
|
||||||
cfg.Tools.Web.Brave.APIKey = v
|
cfg.Tools.Web.Brave.APIKey = v
|
||||||
if v != "" {
|
if v != "" {
|
||||||
cfg.Tools.Web.Brave.Enabled = true
|
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.Brave.MaxResults = int(v)
|
||||||
cfg.Tools.Web.DuckDuckGo.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)
|
path = strings.Replace(path, ".openclaw", ".picoclaw", 1)
|
||||||
return path
|
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
|
// execTimeout: 0 means no timeout, >0 sets the timeout duration
|
||||||
func NewCronTool(
|
func NewCronTool(
|
||||||
cronService *cron.CronService, executor JobExecutor, msgBus *bus.MessageBus, workspace string, restrict bool,
|
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 {
|
) *CronTool {
|
||||||
execTool := exec.NewExecToolWithConfig(workspace, restrict, config)
|
execTool := exec.NewExecToolWithConfig(workspace, restrict, toolConfig)
|
||||||
|
if execTimeout > 0 {
|
||||||
execTool.SetTimeout(execTimeout)
|
execTool.SetTimeout(execTimeout)
|
||||||
|
} else if toolConfig != nil {
|
||||||
|
cronCfg := ParseCronConfig(toolConfig)
|
||||||
|
if cronCfg.ExecTimeoutMinutes > 0 {
|
||||||
|
execTool.SetTimeout(time.Duration(cronCfg.ExecTimeoutMinutes) * time.Minute)
|
||||||
|
}
|
||||||
|
}
|
||||||
return &CronTool{
|
return &CronTool{
|
||||||
cronService: cronService,
|
cronService: cronService,
|
||||||
executor: executor,
|
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)
|
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)
|
denyPatterns := make([]*regexp.Regexp, 0)
|
||||||
|
|
||||||
if config != nil {
|
if toolConfig != nil {
|
||||||
execConfig := config.Tools.Exec
|
execConfig := ParseExecConfig(toolConfig)
|
||||||
enableDenyPatterns := execConfig.EnableDenyPatterns
|
enableDenyPatterns := execConfig.EnableDenyPatterns
|
||||||
if enableDenyPatterns {
|
if enableDenyPatterns {
|
||||||
denyPatterns = append(denyPatterns, defaultDenyPatterns...)
|
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
|
// File tools - each with individual configuration
|
||||||
if cfg.Tools.ReadFile.Enabled {
|
if cfg.ToolEnabled("read-file") {
|
||||||
toolsRegistry.Register(read_file.NewReadFileTool(workspace, restrict))
|
toolsRegistry.Register(read_file.NewReadFileTool(workspace, restrict))
|
||||||
}
|
}
|
||||||
if cfg.Tools.WriteFile.Enabled {
|
if cfg.ToolEnabled("write-file") {
|
||||||
toolsRegistry.Register(write_file.NewWriteFileTool(workspace, restrict))
|
toolsRegistry.Register(write_file.NewWriteFileTool(workspace, restrict))
|
||||||
}
|
}
|
||||||
if cfg.Tools.EditFile.Enabled {
|
if cfg.ToolEnabled("edit-file") {
|
||||||
toolsRegistry.Register(edit_file.NewEditFileTool(workspace, restrict))
|
toolsRegistry.Register(edit_file.NewEditFileTool(workspace, restrict))
|
||||||
}
|
}
|
||||||
if cfg.Tools.AppendFile.Enabled {
|
if cfg.ToolEnabled("append-file") {
|
||||||
toolsRegistry.Register(append_file.NewAppendFileTool(workspace, restrict))
|
toolsRegistry.Register(append_file.NewAppendFileTool(workspace, restrict))
|
||||||
}
|
}
|
||||||
if cfg.Tools.ListDir.Enabled {
|
if cfg.ToolEnabled("list-dir") {
|
||||||
toolsRegistry.Register(list_dir.NewListDirTool(workspace, restrict))
|
toolsRegistry.Register(list_dir.NewListDirTool(workspace, restrict))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec tool
|
// Exec tool
|
||||||
if cfg.Tools.Exec.Enabled {
|
if cfg.ToolEnabled("exec") {
|
||||||
toolsRegistry.Register(exec.NewExecToolWithConfig(workspace, restrict, cfg))
|
toolsRegistry.Register(exec.NewExecToolWithConfig(workspace, restrict, cfg.GetTool("exec")))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Web tools
|
// Web tools
|
||||||
|
webCfg := web_search.GetWebToolsConfig(cfg)
|
||||||
if searchTool := web_search.NewWebSearchTool(web_search.WebSearchToolOptions{
|
if searchTool := web_search.NewWebSearchTool(web_search.WebSearchToolOptions{
|
||||||
BraveAPIKey: cfg.Tools.Web.Brave.APIKey,
|
BraveAPIKey: webCfg.Brave.APIKey,
|
||||||
BraveMaxResults: cfg.Tools.Web.Brave.MaxResults,
|
BraveMaxResults: webCfg.Brave.MaxResults,
|
||||||
BraveEnabled: cfg.Tools.Web.Brave.Enabled,
|
BraveEnabled: webCfg.Brave.Enabled,
|
||||||
TavilyAPIKey: cfg.Tools.Web.Tavily.APIKey,
|
TavilyAPIKey: webCfg.Tavily.APIKey,
|
||||||
TavilyBaseURL: cfg.Tools.Web.Tavily.BaseURL,
|
TavilyBaseURL: webCfg.Tavily.BaseURL,
|
||||||
TavilyMaxResults: cfg.Tools.Web.Tavily.MaxResults,
|
TavilyMaxResults: webCfg.Tavily.MaxResults,
|
||||||
TavilyEnabled: cfg.Tools.Web.Tavily.Enabled,
|
TavilyEnabled: webCfg.Tavily.Enabled,
|
||||||
DuckDuckGoMaxResults: cfg.Tools.Web.DuckDuckGo.MaxResults,
|
DuckDuckGoMaxResults: webCfg.DuckDuckGo.MaxResults,
|
||||||
DuckDuckGoEnabled: cfg.Tools.Web.DuckDuckGo.Enabled,
|
DuckDuckGoEnabled: webCfg.DuckDuckGo.Enabled,
|
||||||
PerplexityAPIKey: cfg.Tools.Web.Perplexity.APIKey,
|
PerplexityAPIKey: webCfg.Perplexity.APIKey,
|
||||||
PerplexityMaxResults: cfg.Tools.Web.Perplexity.MaxResults,
|
PerplexityMaxResults: webCfg.Perplexity.MaxResults,
|
||||||
PerplexityEnabled: cfg.Tools.Web.Perplexity.Enabled,
|
PerplexityEnabled: webCfg.Perplexity.Enabled,
|
||||||
Proxy: cfg.Tools.Web.Proxy,
|
Proxy: webCfg.Proxy,
|
||||||
}); searchTool != nil {
|
}); searchTool != nil {
|
||||||
toolsRegistry.Register(searchTool)
|
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
|
// Hardware tools (I2C, SPI) - Linux only, returns error on other platforms
|
||||||
if cfg.Tools.I2C.Enabled {
|
if cfg.ToolEnabled("i2c") {
|
||||||
toolsRegistry.Register(i2c.NewI2CTool())
|
toolsRegistry.Register(i2c.NewI2CTool())
|
||||||
}
|
}
|
||||||
if cfg.Tools.SPI.Enabled {
|
if cfg.ToolEnabled("spi") {
|
||||||
toolsRegistry.Register(spi.NewSPITool())
|
toolsRegistry.Register(spi.NewSPITool())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skill discovery and installation tools
|
// Skill discovery and installation tools
|
||||||
registryMgr := skills.NewRegistryManagerFromConfig(skills.RegistryConfig{
|
skillsCfg := find_skills.GetSkillsConfig(cfg)
|
||||||
MaxConcurrentSearches: cfg.Tools.Skills.MaxConcurrentSearches,
|
registryMgr := skills.NewRegistryManagerFromConfig(skillsCfg)
|
||||||
ClawHub: skills.ClawHubConfig(cfg.Tools.Skills.Registries.ClawHub),
|
searchCache := find_skills.GetSearchCache(cfg)
|
||||||
})
|
if cfg.ToolEnabled("find-skills") {
|
||||||
searchCache := skills.NewSearchCache(
|
|
||||||
cfg.Tools.Skills.SearchCache.MaxSize,
|
|
||||||
time.Duration(cfg.Tools.Skills.SearchCache.TTLSeconds)*time.Second,
|
|
||||||
)
|
|
||||||
if cfg.Tools.FindSkills.Enabled {
|
|
||||||
toolsRegistry.Register(find_skills.NewFindSkillsTool(registryMgr, searchCache))
|
toolsRegistry.Register(find_skills.NewFindSkillsTool(registryMgr, searchCache))
|
||||||
}
|
}
|
||||||
if cfg.Tools.InstallSkill.Enabled {
|
if cfg.ToolEnabled("install-skill") {
|
||||||
toolsRegistry.Register(install_skill.NewInstallSkillTool(registryMgr, workspace))
|
toolsRegistry.Register(install_skill.NewInstallSkillTool(registryMgr, workspace))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message tool
|
// Message tool
|
||||||
if cfg.Tools.Message.Enabled {
|
if cfg.ToolEnabled("message") {
|
||||||
toolsRegistry.Register(message.NewMessageTool())
|
toolsRegistry.Register(message.NewMessageTool())
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Spawn tool
|
// // Spawn tool
|
||||||
// if cfg.Tools.Spawn.Enabled {
|
// if cfg.ToolEnabled("spawn") {
|
||||||
// // Note: Spawn tool is registered separately in agent loop
|
// // 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