package llmfs
import (
"fmt"
"io"
"strconv"
"github.com/NERVsystems/llm9p/internal/llm"
"github.com/NERVsystems/llm9p/internal/protocol"
)
// SessionDir represents a single session directory: /n/llm/N/
// Contains: ask, compact, context, ctl, model, temperature, system, thinking, prefill, usage
type SessionDir struct {
*protocol.BaseFile
sm *llm.SessionManager
id int
}
// NewSessionDir creates a session directory for the given session ID.
func NewSessionDir(sm *llm.SessionManager, id int) *SessionDir {
return &SessionDir{
BaseFile: protocol.NewBaseFile(strconv.Itoa(id), protocol.DMDIR|0555),
sm: sm,
id: id,
}
}
// Children returns the files in this session directory.
func (d *SessionDir) Children() []protocol.File {
session := d.sm.Get(d.id)
if session == nil {
return nil
}
return []protocol.File{
NewSessionAskFile(d.sm, d.id),
NewSessionCompactFile(d.sm, d.id),
NewSessionContextFile(d.sm, d.id),
NewSessionCtlFile(d.sm, d.id),
NewSessionModelFile(d.sm, d.id),
NewSessionTemperatureFile(d.sm, d.id),
NewSessionSystemFile(d.sm, d.id),
NewSessionThinkingFile(d.sm, d.id),
NewSessionPrefillFile(d.sm, d.id),
NewSessionToolsFile(d.sm, d.id),
NewSessionUsageFile(d.sm, d.id),
}
}
// Lookup finds a child file by name.
func (d *SessionDir) Lookup(name string) (protocol.File, error) {
session := d.sm.Get(d.id)
if session == nil {
return nil, protocol.ErrNotFound
}
switch name {
case "ask":
return NewSessionAskFile(d.sm, d.id), nil
case "compact":
return NewSessionCompactFile(d.sm, d.id), nil
case "context":
return NewSessionContextFile(d.sm, d.id), nil
case "ctl":
return NewSessionCtlFile(d.sm, d.id), nil
case "model":
return NewSessionModelFile(d.sm, d.id), nil
case "temperature":
return NewSessionTemperatureFile(d.sm, d.id), nil
case "system":
return NewSessionSystemFile(d.sm, d.id), nil
case "thinking":
return NewSessionThinkingFile(d.sm, d.id), nil
case "prefill":
return NewSessionPrefillFile(d.sm, d.id), nil
case "tools":
return NewSessionToolsFile(d.sm, d.id), nil
case "usage":
return NewSessionUsageFile(d.sm, d.id), nil
default:
return nil, protocol.ErrNotFound
}
}
// Read returns directory listing as packed stat entries.
func (d *SessionDir) Read(p []byte, offset int64) (int, error) {
var buf []byte
for _, f := range d.Children() {
stat := f.Stat()
entry := make([]byte, 256)
n := stat.Encode(entry)
buf = append(buf, entry[:n]...)
}
if offset >= int64(len(buf)) {
return 0, io.EOF
}
n := copy(p, buf[offset:])
return n, nil
}
// Stat returns the directory's metadata.
func (d *SessionDir) Stat() protocol.Stat {
s := d.BaseFile.Stat()
s.Qid.Type = protocol.QTDIR
return s
}
// SessionsDir is the root /n/llm directory.
// Contains only the "new" file plus dynamically created session directories.
type SessionsDir struct {
*protocol.BaseFile
sm *llm.SessionManager
newFile *NewFile
}
// NewSessionsDir creates the root LLM directory.
func NewSessionsDir(sm *llm.SessionManager) *SessionsDir {
return &SessionsDir{
BaseFile: protocol.NewBaseFile("llm", protocol.DMDIR|0555),
sm: sm,
newFile: NewNewFile(sm),
}
}
// Children returns the files in the root directory.
// This includes "new" plus all active session directories.
func (d *SessionsDir) Children() []protocol.File {
children := []protocol.File{d.newFile}
// Add session directories for all active sessions
for _, id := range d.sm.ListSessions() {
children = append(children, NewSessionDir(d.sm, id))
}
return children
}
// Lookup finds a child by name.
func (d *SessionsDir) Lookup(name string) (protocol.File, error) {
// Check for "new" file
if name == "new" {
return d.newFile, nil
}
// Try to parse as session ID
id, err := strconv.Atoi(name)
if err != nil {
return nil, protocol.ErrNotFound
}
// Check if session exists
session := d.sm.Get(id)
if session == nil {
return nil, protocol.ErrNotFound
}
return NewSessionDir(d.sm, id), nil
}
// Read returns directory listing as packed stat entries.
func (d *SessionsDir) Read(p []byte, offset int64) (int, error) {
var buf []byte
for _, f := range d.Children() {
stat := f.Stat()
entry := make([]byte, 256)
n := stat.Encode(entry)
buf = append(buf, entry[:n]...)
}
if offset >= int64(len(buf)) {
return 0, io.EOF
}
n := copy(p, buf[offset:])
return n, nil
}
// Stat returns the directory's metadata.
func (d *SessionsDir) Stat() protocol.Stat {
s := d.BaseFile.Stat()
s.Qid.Type = protocol.QTDIR
return s
}
// Silence unused import warning
var _ = fmt.Sprint