213 lines
5.9 KiB
Go
213 lines
5.9 KiB
Go
package fs
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"ollie/agent"
|
|
"ollie/backend"
|
|
"ollie/elevate"
|
|
olog "ollie/log"
|
|
"ollie/skills"
|
|
"ollie/toolsrv"
|
|
)
|
|
|
|
// ModelCache caches the model list from all backends.
|
|
// Created by NewRoot and used by the /models handler.
|
|
// Also invalidated by root-level /ctl.
|
|
type ModelCache struct {
|
|
mu sync.Mutex
|
|
data []byte
|
|
fetched bool
|
|
}
|
|
|
|
func NewModelCache() *ModelCache { return &ModelCache{} }
|
|
|
|
func (c *ModelCache) Get() []byte {
|
|
c.mu.Lock()
|
|
if !c.fetched {
|
|
c.mu.Unlock()
|
|
c.refresh()
|
|
c.mu.Lock()
|
|
}
|
|
defer c.mu.Unlock()
|
|
return c.data
|
|
}
|
|
|
|
func (c *ModelCache) Invalidate() {
|
|
c.mu.Lock()
|
|
c.fetched = false
|
|
c.data = nil
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
func (c *ModelCache) refresh() {
|
|
var sb strings.Builder
|
|
for _, name := range backend.Backends() {
|
|
b, err := backend.NewWithName(name)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, m := range b.Models(context.Background()) {
|
|
sb.WriteString(name)
|
|
sb.WriteByte('\t')
|
|
sb.WriteString(m)
|
|
sb.WriteByte('\n')
|
|
}
|
|
}
|
|
c.mu.Lock()
|
|
c.data = []byte(sb.String())
|
|
c.fetched = true
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// HandlerCtx carries runtime dependencies for handler functions.
|
|
// The wire step populates the correct subset per node scope.
|
|
// Handlers assert their scope by checking which pointer is non-nil.
|
|
type HandlerCtx struct {
|
|
// Root-level (always available).
|
|
Root *Tree
|
|
Log *olog.Logger
|
|
ToolReg *toolsrv.Registry
|
|
Skills *skills.Registry
|
|
Models *ModelCache
|
|
Elevate *elevate.Broker
|
|
Invalidate func() // called by root ctl "invalidate"
|
|
Shutdown func() // called by root ctl "kill"
|
|
|
|
// Session scope.
|
|
Session *Session
|
|
Kill func()
|
|
Rename func(string) error
|
|
SaveFn func([]byte) error
|
|
|
|
// Agent scope.
|
|
Agent *agent.Agent
|
|
AgentLog *AgentLog
|
|
}
|
|
|
|
// BlockFn is the signature for blocking/streaming reads.
|
|
type BlockFn func(ctx context.Context, base string) ([]byte, string, error)
|
|
|
|
// Structural permissions — used by Tree for mounted children and synthetic files.
|
|
const (
|
|
PermChildDir os.FileMode = 0555
|
|
PermIdx os.FileMode = 0444
|
|
PermMkdir os.FileMode = 0755
|
|
PermMkdirPrivate os.FileMode = 0700
|
|
)
|
|
|
|
// ListFn resolves dynamic directory children at runtime.
|
|
type ListFn func(ctx HandlerCtx) ([]FsNodeDecl, error)
|
|
|
|
// FsNodeDecl describes one node in the 9P namespace.
|
|
// The entire filesystem is a single recursive tree of these.
|
|
type FsNodeDecl struct {
|
|
// Identity.
|
|
Name string
|
|
UID string // "" = inherit from parent
|
|
GID string // "" = inherit from parent
|
|
Mode os.FileMode // 0 = inherit parent default
|
|
|
|
// Tstat — metadata override (non-nil bypasses Name/UID/GID/Mode/size defaulting).
|
|
Stat func() os.FileInfo
|
|
|
|
// Twalk — children (implies directory; at most one set).
|
|
Children []FsNodeDecl // static, known at compile time
|
|
List func(HandlerCtx) ([]FsNodeDecl, error) // dynamic, resolved at runtime
|
|
|
|
// Tread (non-blocking) + Twrite.
|
|
Read func(ctx HandlerCtx) ([]byte, error)
|
|
Write func(ctx HandlerCtx, data []byte) error
|
|
|
|
// Tread — blocking variants (at most one set).
|
|
BlockOnce func(ctx HandlerCtx, cctx context.Context, base string) ([]byte, string, error)
|
|
Stream func(ctx HandlerCtx, cctx context.Context, base string) ([]byte, string, error)
|
|
|
|
// Request — rdwr: write triggers result, read returns it once.
|
|
Request func(ctx HandlerCtx, data []byte) ([]byte, error)
|
|
|
|
// Tcreate + Tremove + Trename (directory ops).
|
|
Create func(ctx HandlerCtx, name string) error
|
|
Remove func(ctx HandlerCtx) error
|
|
Rename func(newName string) error
|
|
}
|
|
|
|
// NodeOption configures a FsNodeDecl at construction time.
|
|
type NodeOption func(*FsNodeDecl)
|
|
|
|
// ── EDSL constructors ────────────────────────────────────────────
|
|
|
|
// Dir declares a static directory node, optionally with NodeOptions (GID, UID).
|
|
func Dir(name string, args ...any) FsNodeDecl {
|
|
d := FsNodeDecl{Name: name}
|
|
for _, a := range args {
|
|
switch v := a.(type) {
|
|
case FsNodeDecl:
|
|
d.Children = append(d.Children, v)
|
|
case NodeOption:
|
|
v(&d)
|
|
}
|
|
}
|
|
return d
|
|
}
|
|
|
|
// TemplateDir declares a dynamic directory node whose children are
|
|
// resolved at runtime by list. The list function is a factory that
|
|
// returns fully-built subtrees — no blueprint children needed here.
|
|
func TemplateDir(name string, list ListFn) FsNodeDecl {
|
|
return FsNodeDecl{Name: name, List: list}
|
|
}
|
|
|
|
// Leaf declares a file node (non-directory).
|
|
func Leaf(name string, mode os.FileMode, opts ...NodeOption) FsNodeDecl {
|
|
d := FsNodeDecl{Name: name, Mode: mode}
|
|
for _, o := range opts {
|
|
o(&d)
|
|
}
|
|
return d
|
|
}
|
|
|
|
// ── Options ──────────────────────────────────────────────────────
|
|
|
|
func Read(fn func(HandlerCtx) ([]byte, error)) NodeOption {
|
|
return func(d *FsNodeDecl) { d.Read = fn }
|
|
}
|
|
|
|
func Write(fn func(HandlerCtx, []byte) error) NodeOption {
|
|
return func(d *FsNodeDecl) { d.Write = fn }
|
|
}
|
|
|
|
func Stream(fn func(HandlerCtx, context.Context, string) ([]byte, string, error)) NodeOption {
|
|
return func(d *FsNodeDecl) { d.Stream = fn }
|
|
}
|
|
|
|
func BlockOnce(fn func(HandlerCtx, context.Context, string) ([]byte, string, error)) NodeOption {
|
|
return func(d *FsNodeDecl) { d.BlockOnce = fn }
|
|
}
|
|
|
|
func Request(fn func(HandlerCtx, []byte) ([]byte, error)) NodeOption {
|
|
return func(d *FsNodeDecl) { d.Request = fn }
|
|
}
|
|
|
|
func CreateFile(fn func(HandlerCtx, string) error) NodeOption {
|
|
return func(d *FsNodeDecl) { d.Create = fn }
|
|
}
|
|
|
|
func Remove(fn func(HandlerCtx) error) NodeOption {
|
|
return func(d *FsNodeDecl) { d.Remove = fn }
|
|
}
|
|
|
|
func Stat(fn func() os.FileInfo) NodeOption {
|
|
return func(d *FsNodeDecl) { d.Stat = fn }
|
|
}
|
|
|
|
func UID(uid string) NodeOption {
|
|
return func(d *FsNodeDecl) { d.UID = uid }
|
|
}
|
|
|
|
func GID(gid string) NodeOption {
|
|
return func(d *FsNodeDecl) { d.GID = gid }
|
|
} |