This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-core/toolsrv/registry.go

178 lines
3.7 KiB
Go

package toolsrv
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"sync"
)
type Registry struct {
mu sync.RWMutex
sessions map[string]map[string]ToolInfo
revisions map[string]uint64
}
func NewRegistry() (*Registry, error) {
r := &Registry{
sessions: make(map[string]map[string]ToolInfo),
revisions: make(map[string]uint64),
}
// Validate that the tools directory is readable.
dir := ToolsPath()
if _, err := os.ReadDir(dir); err != nil {
return nil, fmt.Errorf("read tools dir %s: %w", dir, err)
}
return r, nil
}
// discover scans the tools directory and returns all valid tool infos.
func (r *Registry) discover() map[string]ToolInfo {
dir := ToolsPath()
entries, err := os.ReadDir(dir)
if err != nil {
return nil
}
tools := make(map[string]ToolInfo)
for _, e := range entries {
if e.IsDir() || e.Name() == "idx" || e.Name()[0] == '.' {
continue
}
data, err := os.ReadFile(filepath.Join(dir, e.Name()))
if err != nil {
continue
}
script := string(data)
if !strings.Contains(script, "ollie:prompt") {
continue
}
tools[e.Name()] = ParseToolInfo(e.Name(), script)
}
return tools
}
func (r *Registry) Summaries() []ToolInfo {
tools := r.discover()
var summaries []ToolInfo
for _, info := range tools {
summaries = append(summaries, ToolInfo{
Name: info.Name,
Description: info.Description,
})
}
sort.Slice(summaries, func(i, j int) bool { return summaries[i].Name < summaries[j].Name })
return summaries
}
func (r *Registry) Load(sessionID, name string) error {
tools := r.discover()
tool, exists := tools[name]
if !exists {
return fmt.Errorf("tool not found: %s", name)
}
r.mu.Lock()
defer r.mu.Unlock()
if r.sessions[sessionID] == nil {
r.sessions[sessionID] = make(map[string]ToolInfo)
}
r.sessions[sessionID][name] = tool
r.revisions[sessionID]++
return nil
}
func (r *Registry) Unload(sessionID, name string) error {
r.mu.Lock()
defer r.mu.Unlock()
sessionTools, ok := r.sessions[sessionID]
if !ok {
return nil
}
if _, exists := sessionTools[name]; !exists {
return nil
}
delete(sessionTools, name)
r.revisions[sessionID]++
return nil
}
func (r *Registry) Loaded(sessionID string) []ToolInfo {
r.mu.RLock()
defer r.mu.RUnlock()
sessionTools, ok := r.sessions[sessionID]
if !ok {
return nil
}
var loaded []ToolInfo
for _, info := range sessionTools {
loaded = append(loaded, info)
}
sort.Slice(loaded, func(i, j int) bool { return loaded[i].Name < loaded[j].Name })
return loaded
}
// RefreshLoaded re-reads all loaded tools from disk, updating their schemas
// and metadata in place. Called from tool_list to keep schemas current.
func (r *Registry) RefreshLoaded(sessionID string) {
r.mu.RLock()
sessionTools, ok := r.sessions[sessionID]
if !ok || len(sessionTools) == 0 {
r.mu.RUnlock()
return
}
names := make([]string, 0, len(sessionTools))
for name := range sessionTools {
names = append(names, name)
}
r.mu.RUnlock()
fresh := r.discover()
r.mu.Lock()
defer r.mu.Unlock()
changed := false
for _, name := range names {
if tool, exists := fresh[name]; exists {
r.sessions[sessionID][name] = tool
changed = true
} else {
// Tool was removed from disk — unload it.
delete(r.sessions[sessionID], name)
changed = true
}
}
if changed {
r.revisions[sessionID]++
}
}
func (r *Registry) Lookup(sessionID, name string) (ToolInfo, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
sessionTools, ok := r.sessions[sessionID]
if !ok {
return ToolInfo{}, false
}
tool, exists := sessionTools[name]
return tool, exists
}
func (r *Registry) Revision(sessionID string) uint64 {
r.mu.RLock()
defer r.mu.RUnlock()
return r.revisions[sessionID]
}