mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-10 16:10:59 +00:00
f3f4c67b36
Multi-agent AI gateway with WebSocket RPC, HTTP API, and messaging channel integrations. Go port of OpenClaw with multi-tenant PostgreSQL, per-user isolation, security hardening, and production observability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
96 lines
2.7 KiB
Go
96 lines
2.7 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"sync"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
// DynamicToolLoader loads custom tools from the database and registers them into tool registries.
|
|
type DynamicToolLoader struct {
|
|
store store.CustomToolStore
|
|
workspace string
|
|
mu sync.Mutex
|
|
// Track names of globally registered custom tools (for reload/unregister).
|
|
globalNames map[string]bool
|
|
}
|
|
|
|
// NewDynamicToolLoader creates a loader for custom tools from the database.
|
|
func NewDynamicToolLoader(s store.CustomToolStore, workspace string) *DynamicToolLoader {
|
|
return &DynamicToolLoader{
|
|
store: s,
|
|
workspace: workspace,
|
|
globalNames: make(map[string]bool),
|
|
}
|
|
}
|
|
|
|
// LoadGlobal loads all global custom tools (agent_id IS NULL) and registers them into the registry.
|
|
// Skips tools whose names collide with existing built-in or MCP tools.
|
|
func (l *DynamicToolLoader) LoadGlobal(ctx context.Context, reg *Registry) error {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
defs, err := l.store.ListGlobal(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
registered := 0
|
|
for _, def := range defs {
|
|
if _, exists := reg.Get(def.Name); exists {
|
|
slog.Warn("custom_tools: skipping global tool (name collision with built-in/MCP)",
|
|
"tool", def.Name)
|
|
continue
|
|
}
|
|
reg.Register(NewDynamicTool(def, l.workspace))
|
|
l.globalNames[def.Name] = true
|
|
registered++
|
|
}
|
|
|
|
if registered > 0 {
|
|
slog.Info("custom_tools: loaded global tools", "count", registered)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LoadForAgent loads per-agent custom tools and returns a cloned registry with them added.
|
|
// Returns nil if the agent has no custom tools (caller should use global registry).
|
|
func (l *DynamicToolLoader) LoadForAgent(ctx context.Context, globalReg *Registry, agentID uuid.UUID) (*Registry, error) {
|
|
defs, err := l.store.ListByAgent(ctx, agentID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(defs) == 0 {
|
|
return nil, nil // no per-agent tools
|
|
}
|
|
|
|
clone := globalReg.Clone()
|
|
for _, def := range defs {
|
|
clone.Register(NewDynamicTool(def, l.workspace))
|
|
}
|
|
|
|
slog.Debug("custom_tools: loaded per-agent tools", "agent_id", agentID, "count", len(defs))
|
|
return clone, nil
|
|
}
|
|
|
|
// ReloadGlobal unregisters all previously loaded global tools and re-loads from DB.
|
|
// Used on cache invalidation events.
|
|
func (l *DynamicToolLoader) ReloadGlobal(ctx context.Context, reg *Registry) {
|
|
l.mu.Lock()
|
|
// Unregister old global custom tools
|
|
for name := range l.globalNames {
|
|
reg.Unregister(name)
|
|
}
|
|
l.globalNames = make(map[string]bool)
|
|
l.mu.Unlock()
|
|
|
|
// Re-load (LoadGlobal acquires its own lock)
|
|
if err := l.LoadGlobal(ctx, reg); err != nil {
|
|
slog.Warn("custom_tools: failed to reload global tools", "error", err)
|
|
}
|
|
}
|