重构:把剩余 12 个顶层包全部迁到 internal/
继续之前的重构:把根目录残留的非数据/资源类子目录(agents、agenttool、ai、 autoreply、completion、conversation、llm、message、mqtpp、stream、 toolmanager、toolrouter)一并搬到 internal/ 下,并把全工程引用它们的 import 路径从 "meshtastic_mqtt_server/<x>" 重写为 "meshtastic_mqtt_server/internal/<x>"。 变更 - git mv 12 个顶层目录进 internal/,无函数体改动。 - 全工程 sed 把 import path 加上 internal/ 前缀,包括 main.go、 internal/bot/bot_service.go、internal/store/bot_store.go 中对 mqtpp 的引用,以及 ai 子系统内部 agents/agenttool/autoreply/conversation/ llm/toolmanager/toolrouter 之间的相互引用。 验证 - go build ./... 通过;go test ./... 全部包通过。 - AI 自动回复链路(main → ai.NewService → autoreply.Service → botSender) 保持不变,仅 import 路径调整。 - 根目录最终只剩 main.go / main_test.go / internal/ 加 go.mod / go.sum 与数据资源目录(dist、doc、firmware、py、win、meshmap_frontend)。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
package toolmanager
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"meshtastic_mqtt_server/internal/agenttool"
|
||||
)
|
||||
|
||||
// Manager manages loaded AI tools
|
||||
type Manager struct {
|
||||
tools map[string]agenttool.LoadedTool
|
||||
order []string
|
||||
}
|
||||
|
||||
// Load loads tools from the given directory
|
||||
// If directory doesn't exist or is empty, automatically loads all registered tools
|
||||
func Load(root string, options agenttool.LoadOptions) (*Manager, error) {
|
||||
manager := &Manager{tools: map[string]agenttool.LoadedTool{}}
|
||||
|
||||
// Try to read directory
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("failed to read tools directory: %w", err)
|
||||
}
|
||||
// Directory doesn't exist, continue to load all registered tools
|
||||
entries = []os.DirEntry{}
|
||||
}
|
||||
|
||||
// Load tools from directory if they exist
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := strings.ToLower(strings.TrimSpace(entry.Name()))
|
||||
descriptor, ok := agenttool.Lookup(name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
tool, err := descriptor.Load(filepath.Join(root, entry.Name()), options)
|
||||
if err != nil {
|
||||
manager.Close()
|
||||
return nil, fmt.Errorf("failed to load tool %s: %w", name, err)
|
||||
}
|
||||
if tool == nil {
|
||||
continue
|
||||
}
|
||||
toolName := strings.ToLower(strings.TrimSpace(tool.Name()))
|
||||
if toolName == "" {
|
||||
toolName = name
|
||||
}
|
||||
if _, ok := manager.tools[toolName]; ok {
|
||||
manager.Close()
|
||||
return nil, fmt.Errorf("duplicate tool name: %s", toolName)
|
||||
}
|
||||
manager.tools[toolName] = tool
|
||||
manager.order = append(manager.order, toolName)
|
||||
}
|
||||
|
||||
// If no tools loaded from directory, automatically load all registered tools
|
||||
if len(manager.tools) == 0 {
|
||||
registeredTools := agenttool.Names()
|
||||
for _, name := range registeredTools {
|
||||
descriptor, ok := agenttool.Lookup(name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Use empty path for tools that don't require configuration files
|
||||
tool, err := descriptor.Load("", options)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if tool == nil {
|
||||
continue
|
||||
}
|
||||
toolName := strings.ToLower(strings.TrimSpace(tool.Name()))
|
||||
if toolName == "" {
|
||||
toolName = name
|
||||
}
|
||||
if _, ok := manager.tools[toolName]; ok {
|
||||
continue
|
||||
}
|
||||
manager.tools[toolName] = tool
|
||||
manager.order = append(manager.order, toolName)
|
||||
}
|
||||
}
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
// NewForTest creates a manager with preloaded tools for testing
|
||||
func NewForTest(tools ...agenttool.LoadedTool) *Manager {
|
||||
manager := &Manager{tools: map[string]agenttool.LoadedTool{}}
|
||||
for _, tool := range tools {
|
||||
if tool == nil {
|
||||
continue
|
||||
}
|
||||
name := strings.ToLower(strings.TrimSpace(tool.Name()))
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := manager.tools[name]; !ok {
|
||||
manager.order = append(manager.order, name)
|
||||
}
|
||||
manager.tools[name] = tool
|
||||
}
|
||||
return manager
|
||||
}
|
||||
|
||||
// Tools returns all loaded tools
|
||||
func (m *Manager) Tools() []agenttool.LoadedTool {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
tools := make([]agenttool.LoadedTool, 0, len(m.order))
|
||||
for _, name := range m.order {
|
||||
if tool := m.tools[name]; tool != nil {
|
||||
tools = append(tools, tool)
|
||||
}
|
||||
}
|
||||
return tools
|
||||
}
|
||||
|
||||
// Get returns a tool by name
|
||||
func (m *Manager) Get(name string) (agenttool.LoadedTool, bool) {
|
||||
if m == nil {
|
||||
return nil, false
|
||||
}
|
||||
tool, ok := m.tools[strings.ToLower(strings.TrimSpace(name))]
|
||||
return tool, ok
|
||||
}
|
||||
|
||||
// RawState returns the raw state of a tool
|
||||
func (m *Manager) RawState(name string) (any, bool) {
|
||||
tool, ok := m.Get(name)
|
||||
if !ok || tool == nil {
|
||||
return nil, false
|
||||
}
|
||||
return tool.RawState(), true
|
||||
}
|
||||
|
||||
// Close closes all tools
|
||||
func (m *Manager) Close() error {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
var errs []string
|
||||
for _, tool := range m.Tools() {
|
||||
if closer, ok := tool.(interface{ Close() error }); ok {
|
||||
if err := closer.Close(); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
sort.Strings(errs)
|
||||
return errors.New(strings.Join(errs, "; "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user