package llmfs import ( "context" "io" "log" "strings" "sync" "github.com/NERVsystems/llm9p/internal/llm" "github.com/NERVsystems/llm9p/internal/protocol" ) // CompactThreshold is the percentage of context limit at which auto-compaction triggers const CompactThreshold = 0.80 // AskFile is the main interaction file - write a prompt, read the response type AskFile struct { *protocol.BaseFile client llm.Backend mu sync.RWMutex lastResponse string } // NewAskFile creates the ask file func NewAskFile(client llm.Backend) *AskFile { return &AskFile{ BaseFile: protocol.NewBaseFile("ask", 0666), client: client, } } func (f *AskFile) Read(p []byte, offset int64) (int, error) { f.mu.RLock() content := f.lastResponse f.mu.RUnlock() // Add newline if not present if content != "" && !strings.HasSuffix(content, "\n") { content += "\n" } if offset >= int64(len(content)) { return 0, io.EOF } n := copy(p, content[offset:]) return n, nil } func (f *AskFile) Write(p []byte, offset int64) (int, error) { prompt := strings.TrimSpace(string(p)) if prompt == "" { return len(p), nil // Empty write is a no-op } ctx := context.Background() // Check if we need to auto-compact before processing tokens := f.client.TotalTokens() limit := f.client.ContextLimit() threshold := int(float64(limit) * CompactThreshold) if tokens > threshold { log.Printf("llm9p: auto-compacting at %d/%d tokens (%.0f%% threshold)", tokens, limit, CompactThreshold*100) if err := f.client.Compact(ctx); err != nil { log.Printf("llm9p: auto-compact failed: %v", err) // Continue anyway - better to try than to fail } else { log.Printf("llm9p: auto-compact complete, now at %d tokens", f.client.TotalTokens()) } } // Make the API call response, err := f.client.Ask(ctx, prompt) if err != nil { // Store error as response so it can be read f.mu.Lock() f.lastResponse = "Error: " + err.Error() f.mu.Unlock() return len(p), nil // Return success so client knows write completed } f.mu.Lock() f.lastResponse = response f.mu.Unlock() return len(p), nil } func (f *AskFile) Stat() protocol.Stat { s := f.BaseFile.Stat() f.mu.RLock() content := f.lastResponse f.mu.RUnlock() if content != "" && !strings.HasSuffix(content, "\n") { content += "\n" } s.Length = uint64(len(content)) return s }