mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-18 21:30:48 +08:00
parallel optimization groundwork
This commit is contained in:
157
pkg/tools/bootstrap.go
Normal file
157
pkg/tools/bootstrap.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func BootstrapDefaultTools(ctx context.Context, opts BootstrapOptions) (*BootstrapResult, error) {
|
||||
if opts.Config == nil {
|
||||
return nil, fmt.Errorf("config is required")
|
||||
}
|
||||
workspace := strings.TrimSpace(opts.Workspace)
|
||||
if workspace == "" {
|
||||
workspace = opts.Config.WorkspacePath()
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
registry := NewToolRegistry()
|
||||
processManager := opts.ProcessManager
|
||||
if processManager == nil {
|
||||
processManager = NewProcessManager(workspace)
|
||||
}
|
||||
|
||||
registerFilesystemTools(registry, workspace)
|
||||
registerShellTools(registry, opts, workspace, processManager)
|
||||
registerCronTools(registry, opts)
|
||||
|
||||
maxParallelCalls, parallelSafe := bootstrapParallelConfig(opts.Config)
|
||||
registerWebTools(registry, opts, maxParallelCalls, parallelSafe)
|
||||
registerMCPTools(ctx, registry, opts, workspace)
|
||||
registerMessageTool(registry, opts.MessageBus)
|
||||
|
||||
subagentManager, subagentRouter := registerSubagentTools(registry, opts, workspace)
|
||||
registerSessionTools(registry, opts)
|
||||
registerMemoryTools(registry, workspace)
|
||||
|
||||
registry.Register(NewParallelTool(registry, maxParallelCalls, parallelSafe))
|
||||
registry.Register(NewBrowserTool())
|
||||
registry.Register(NewCameraTool(workspace))
|
||||
registry.Register(NewSystemInfoTool())
|
||||
|
||||
return &BootstrapResult{
|
||||
Registry: registry,
|
||||
ProcessManager: processManager,
|
||||
SubagentManager: subagentManager,
|
||||
SubagentRouter: subagentRouter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func registerFilesystemTools(registry *ToolRegistry, workspace string) {
|
||||
registry.Register(NewReadFileTool(workspace))
|
||||
registry.Register(NewWriteFileTool(workspace))
|
||||
registry.Register(NewListDirTool(workspace))
|
||||
registry.Register(NewSkillExecTool(workspace))
|
||||
registry.Register(NewEditFileTool(workspace))
|
||||
}
|
||||
|
||||
func registerShellTools(registry *ToolRegistry, opts BootstrapOptions, workspace string, processManager *ProcessManager) {
|
||||
registry.Register(NewExecTool(opts.Config.Tools.Shell, workspace, processManager))
|
||||
registry.Register(NewProcessTool(processManager))
|
||||
}
|
||||
|
||||
func registerCronTools(registry *ToolRegistry, opts BootstrapOptions) {
|
||||
if opts.CronService == nil {
|
||||
return
|
||||
}
|
||||
registry.Register(NewRemindTool(opts.CronService))
|
||||
registry.Register(NewCronTool(opts.CronService))
|
||||
}
|
||||
|
||||
func bootstrapParallelConfig(cfg *config.Config) (int, map[string]struct{}) {
|
||||
maxParallelCalls := cfg.Agents.Defaults.Execution.ToolMaxParallelCalls
|
||||
if maxParallelCalls <= 0 {
|
||||
maxParallelCalls = 4
|
||||
}
|
||||
parallelSafe := make(map[string]struct{})
|
||||
for _, name := range cfg.Agents.Defaults.Execution.ToolParallelSafeNames {
|
||||
trimmed := strings.TrimSpace(name)
|
||||
if trimmed != "" {
|
||||
parallelSafe[trimmed] = struct{}{}
|
||||
}
|
||||
}
|
||||
return maxParallelCalls, parallelSafe
|
||||
}
|
||||
|
||||
func registerWebTools(registry *ToolRegistry, opts BootstrapOptions, maxParallelCalls int, parallelSafe map[string]struct{}) {
|
||||
searchCfg := opts.Config.Tools.Web.Search
|
||||
registry.Register(NewWebSearchTool(searchCfg.APIKey, searchCfg.MaxResults))
|
||||
webFetchTool := NewWebFetchTool(50000)
|
||||
registry.Register(webFetchTool)
|
||||
registry.Register(NewParallelFetchTool(webFetchTool, maxParallelCalls, parallelSafe))
|
||||
}
|
||||
|
||||
func registerMCPTools(ctx context.Context, registry *ToolRegistry, opts BootstrapOptions, workspace string) {
|
||||
mcpCfg := opts.Config.Tools.MCP
|
||||
if !mcpCfg.Enabled {
|
||||
return
|
||||
}
|
||||
mcpTool := NewMCPTool(workspace, mcpCfg)
|
||||
registry.Register(mcpTool)
|
||||
timeoutSec := mcpCfg.RequestTimeoutSec
|
||||
if timeoutSec <= 0 {
|
||||
timeoutSec = 20
|
||||
}
|
||||
discoveryCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSec)*time.Second)
|
||||
defer cancel()
|
||||
for _, remoteTool := range mcpTool.DiscoverTools(discoveryCtx) {
|
||||
registry.Register(remoteTool)
|
||||
}
|
||||
}
|
||||
|
||||
func registerMessageTool(registry *ToolRegistry, msgBus *bus.MessageBus) {
|
||||
messageTool := NewMessageTool()
|
||||
if msgBus != nil {
|
||||
messageTool.SetSendCallback(func(channel, chatID, action, content, media, messageID, emoji string, buttons [][]bus.Button) error {
|
||||
msgBus.PublishOutbound(bus.OutboundMessage{
|
||||
Channel: channel,
|
||||
ChatID: chatID,
|
||||
Content: content,
|
||||
Media: media,
|
||||
Buttons: buttons,
|
||||
Action: action,
|
||||
MessageID: messageID,
|
||||
Emoji: emoji,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
registry.Register(messageTool)
|
||||
}
|
||||
|
||||
func registerSubagentTools(registry *ToolRegistry, opts BootstrapOptions, workspace string) (*SubagentManager, *SubagentRouter) {
|
||||
subagentManager := NewSubagentManager(opts.Provider, workspace, opts.MessageBus)
|
||||
subagentRouter := NewSubagentRouter(subagentManager)
|
||||
registry.Register(NewSpawnTool(subagentManager))
|
||||
if store := subagentManager.ProfileStore(); store != nil {
|
||||
registry.Register(NewSubagentProfileTool(store))
|
||||
}
|
||||
return subagentManager, subagentRouter
|
||||
}
|
||||
|
||||
func registerSessionTools(registry *ToolRegistry, opts BootstrapOptions) {
|
||||
registry.Register(NewSessionsTool(opts.SessionList, opts.SessionHistory))
|
||||
}
|
||||
|
||||
func registerMemoryTools(registry *ToolRegistry, workspace string) {
|
||||
registry.Register(NewMemorySearchTool(workspace))
|
||||
registry.Register(NewMemoryGetTool(workspace))
|
||||
registry.Register(NewMemoryWriteTool(workspace))
|
||||
}
|
||||
30
pkg/tools/bootstrap_options.go
Normal file
30
pkg/tools/bootstrap_options.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/cron"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
type SessionListFunc func(limit int) []SessionInfo
|
||||
|
||||
type SessionHistoryFunc func(key string, limit int) []providers.Message
|
||||
|
||||
type BootstrapOptions struct {
|
||||
Config *config.Config
|
||||
Workspace string
|
||||
MessageBus *bus.MessageBus
|
||||
CronService *cron.CronService
|
||||
Provider providers.LLMProvider
|
||||
ProcessManager *ProcessManager
|
||||
SessionList SessionListFunc
|
||||
SessionHistory SessionHistoryFunc
|
||||
}
|
||||
|
||||
type BootstrapResult struct {
|
||||
Registry *ToolRegistry
|
||||
ProcessManager *ProcessManager
|
||||
SubagentManager *SubagentManager
|
||||
SubagentRouter *SubagentRouter
|
||||
}
|
||||
61
pkg/tools/bootstrap_test.go
Normal file
61
pkg/tools/bootstrap_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func TestBootstrapDefaultToolsRegistersExpectedLocalTools(t *testing.T) {
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Agents.Defaults.Workspace = t.TempDir()
|
||||
cfg.Tools.MCP.Enabled = false
|
||||
|
||||
result, err := BootstrapDefaultTools(t.Context(), BootstrapOptions{
|
||||
Config: cfg,
|
||||
Workspace: cfg.WorkspacePath(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("bootstrap default tools: %v", err)
|
||||
}
|
||||
if result == nil || result.Registry == nil {
|
||||
t.Fatalf("expected registry in bootstrap result")
|
||||
}
|
||||
if result.ProcessManager == nil {
|
||||
t.Fatalf("expected process manager in bootstrap result")
|
||||
}
|
||||
if result.SubagentManager == nil || result.SubagentRouter == nil {
|
||||
t.Fatalf("expected subagent manager and router in bootstrap result")
|
||||
}
|
||||
|
||||
got := result.Registry.List()
|
||||
sort.Strings(got)
|
||||
want := []string{
|
||||
"browser",
|
||||
"camera_snap",
|
||||
"edit_file",
|
||||
"exec",
|
||||
"list_dir",
|
||||
"memory_get",
|
||||
"memory_search",
|
||||
"memory_write",
|
||||
"message",
|
||||
"parallel",
|
||||
"parallel_fetch",
|
||||
"process",
|
||||
"read_file",
|
||||
"sessions",
|
||||
"skill_exec",
|
||||
"spawn",
|
||||
"subagent_profile",
|
||||
"system_info",
|
||||
"web_fetch",
|
||||
"web_search",
|
||||
"write_file",
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("default tool names mismatch\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
@@ -346,13 +346,30 @@ type mcpClient struct {
|
||||
cmd *exec.Cmd
|
||||
stdin io.WriteCloser
|
||||
reader *bufio.Reader
|
||||
stderr bytes.Buffer
|
||||
stderr mcpSafeBuffer
|
||||
|
||||
writeMu sync.Mutex
|
||||
waiters sync.Map
|
||||
nextID atomic.Int64
|
||||
}
|
||||
|
||||
type mcpSafeBuffer struct {
|
||||
mu sync.Mutex
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *mcpSafeBuffer) Write(p []byte) (int, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.buf.Write(p)
|
||||
}
|
||||
|
||||
func (b *mcpSafeBuffer) String() string {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.buf.String()
|
||||
}
|
||||
|
||||
type mcpInbound struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID interface{} `json:"id,omitempty"`
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestRemindTool_UsesToolContextForDeliveryTarget(t *testing.T) {
|
||||
tool.SetContext("telegram", "chat-123")
|
||||
|
||||
_, err := tool.Execute(context.Background(), map[string]interface{}{
|
||||
"message": "鍠濇按",
|
||||
"message": "喝水",
|
||||
"time_expr": "10m",
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user