~kris/9p

llm9p

ref: 35c20f3e6e008970ccdc9ea460682c578bd6d35a llm9p/internal/llmfs/session_dir.go -rw-r--r-- 4.5 KiB
35c20f3e — pdfinn fix(client): replace empty text content block with placeholder 6 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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