toolsrv: move Registry to cmd/toolsrv, keep only client SDK in shared package

Split toolsrv/registry.go:
- cmd/toolsrv/registry.go: Registry type with session-scoped tool state
  (Load, Unload, Loaded, Lookup, Revision methods)
- toolsrv/registry.go: shared types only (ToolInfo, ToolsPath, DiscoverTools)

The toolsrv/ package is now a pure client SDK:
- Dial, Conn for 9P connection
- ToolInfo, ToolResult, HostInfo types
- DiscoverTools for listing available tools

Server-only code lives in cmd/toolsrv/:
- Registry for session-scoped tool state
- 9P handlers and execution logic
- Sandbox integration
This commit is contained in:
Levi Neely 2026-08-10 20:57:32 +02:00
parent a0315a558a
commit e8239927de
6 changed files with 121 additions and 117 deletions

View File

@ -47,7 +47,7 @@ type FS9P struct {
cwd string
env map[string]string
registry *toolsrv.Registry
registry *Registry
sessID string
yolo bool
@ -90,7 +90,7 @@ func NewFS9P(cwd string) *FS9P {
}
// SetRegistry configures the tool registry and session ID.
func (fs *FS9P) SetRegistry(r *toolsrv.Registry, sessID string) {
func (fs *FS9P) SetRegistry(r *Registry, sessID string) {
fs.mu.Lock()
fs.registry = r
fs.sessID = sessID

View File

@ -5,8 +5,6 @@ import (
"strings"
"testing"
"time"
"ollie/toolsrv"
)
func TestFS9P_Basic(t *testing.T) {
@ -78,7 +76,7 @@ func TestFS9P_ProcLimit(t *testing.T) {
fs.procLimit = 2
// Create a fake registry and session ID
reg, err := toolsrv.NewRegistry()
reg, err := NewRegistry()
if err != nil {
t.Skip("no tools directory available")
}

View File

@ -24,7 +24,6 @@ import (
"ollie/env"
"ollie/fsedsl"
"ollie/toolsrv"
)
var (
@ -55,7 +54,7 @@ func main() {
os.Setenv("PATH", binDir+":"+os.Getenv("PATH"))
// Create tool registry
toolReg, _ := toolsrv.NewRegistry()
toolReg, _ := NewRegistry()
sid := *sessionID
if sid == "" {
sid = os.Getenv("OLLIE_SESSION_ID") // fallback to env var

114
cmd/toolsrv/registry.go Normal file
View File

@ -0,0 +1,114 @@
// registry.go - Session-scoped tool registry for toolsrv.
package main
import (
"fmt"
"sort"
"sync"
"ollie/toolsrv"
)
// Registry tracks loaded tools per session.
type Registry struct {
mu sync.RWMutex
sessions map[string]map[string]toolsrv.ToolInfo
revisions map[string]uint64
}
// NewRegistry creates a new tool registry.
func NewRegistry() (*Registry, error) {
r := &Registry{
sessions: make(map[string]map[string]toolsrv.ToolInfo),
revisions: make(map[string]uint64),
}
return r, nil
}
// discover scans the tools directory and returns all valid tool infos.
func (r *Registry) discover() map[string]toolsrv.ToolInfo {
infos := toolsrv.DiscoverTools()
tools := make(map[string]toolsrv.ToolInfo, len(infos))
for _, info := range infos {
tools[info.Name] = info
}
return tools
}
// Load loads a tool into a session's registry.
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]toolsrv.ToolInfo)
}
r.sessions[sessionID][name] = tool
r.revisions[sessionID]++
return nil
}
// Unload removes a tool from a session's registry.
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
}
// Loaded returns all tools loaded for a session.
func (r *Registry) Loaded(sessionID string) []toolsrv.ToolInfo {
r.mu.RLock()
defer r.mu.RUnlock()
sessionTools, ok := r.sessions[sessionID]
if !ok {
return nil
}
var loaded []toolsrv.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
}
// Lookup returns a tool by name from a session's registry.
func (r *Registry) Lookup(sessionID, name string) (toolsrv.ToolInfo, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
sessionTools, ok := r.sessions[sessionID]
if !ok {
return toolsrv.ToolInfo{}, false
}
tool, exists := sessionTools[name]
return tool, exists
}
// Revision returns the revision counter for a session's registry.
func (r *Registry) Revision(sessionID string) uint64 {
r.mu.RLock()
defer r.mu.RUnlock()
return r.revisions[sessionID]
}

View File

@ -9,7 +9,6 @@ import (
"sync"
"ollie/fsedsl"
"ollie/toolsrv"
)
// Server9P holds the state for the 9P tool server.
@ -18,7 +17,7 @@ type Server9P struct {
secret string // set on first auth, verified on subsequent
token string // session token returned after auth
registry *toolsrv.Registry // tool registry
registry *Registry // tool registry
sessID string // session ID for registry scoping
yolo bool // skip sandbox
@ -34,7 +33,7 @@ func NewServer9P() *Server9P {
}
// SetRegistry configures the tool registry and session ID.
func (s *Server9P) SetRegistry(r *toolsrv.Registry, sessID string) {
func (s *Server9P) SetRegistry(r *Registry, sessID string) {
s.mu.Lock()
s.registry = r
s.sessID = sessID

View File

@ -1,121 +1,15 @@
// registry.go - Tool discovery and shared types.
package toolsrv
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"ollie/paths"
)
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 {
infos := DiscoverTools()
tools := make(map[string]ToolInfo, len(infos))
for _, info := range infos {
tools[info.Name] = info
}
return tools
}
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
}
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]
}
// ToolInfo describes a tool provided by a server.
type ToolInfo struct {
Server string