package handlers import ( "context" "encoding/json" "errors" "io/fs" "os" "sort" ) // DefaultListMaxEntries caps the number of directory entries returned to bound // payload size for large directories. const DefaultListMaxEntries = 1000 // FsListHandler handles fs/list: a bounded directory listing inside the worktree. type FsListHandler struct { MaxEntries int } // NewFsListHandler constructs an FsListHandler with default caps. func NewFsListHandler() *FsListHandler { return &FsListHandler{MaxEntries: DefaultListMaxEntries} } func (h *FsListHandler) maxEntries() int { if h.MaxEntries > 0 { return h.MaxEntries } return DefaultListMaxEntries } type fsListParams struct { SessionID string `json:"sessionId"` Path string `json:"path"` } type fsDirEntry struct { Name string `json:"name"` IsDir bool `json:"is_dir"` Size int64 `json:"size"` } type fsListResult struct { Entries []fsDirEntry `json:"entries"` Truncated bool `json:"truncated"` } // Handle lists the directory at sess.CwdPath/path. Returns FsError for path // escape (-32001) or read failure. Results are sorted (dirs first, then name). func (h *FsListHandler) Handle(_ context.Context, sess SessionContext, paramsJSON json.RawMessage) FsResult { var p fsListParams if err := json.Unmarshal(paramsJSON, &p); err != nil { return FsResult{Err: &FsError{Code: -32602, Message: "invalid params: " + err.Error()}} } abs, err := resolveSafePath(sess.CwdPath, p.Path) if err != nil { return FsResult{Err: &FsError{Code: -32001, Message: err.Error()}} } dirents, err := os.ReadDir(abs) if err != nil { if errors.Is(err, fs.ErrNotExist) { return FsResult{Err: &FsError{Code: -32603, Message: "directory not found"}} } return FsResult{Err: &FsError{Code: -32603, Message: "list: " + err.Error()}} } max := h.maxEntries() out := fsListResult{Entries: make([]fsDirEntry, 0, len(dirents))} for _, de := range dirents { if len(out.Entries) >= max { out.Truncated = true break } e := fsDirEntry{Name: de.Name(), IsDir: de.IsDir()} if info, ierr := de.Info(); ierr == nil && !de.IsDir() { e.Size = info.Size() } out.Entries = append(out.Entries, e) } sort.SliceStable(out.Entries, func(i, j int) bool { if out.Entries[i].IsDir != out.Entries[j].IsDir { return out.Entries[i].IsDir // dirs first } return out.Entries[i].Name < out.Entries[j].Name }) res, _ := json.Marshal(out) return FsResult{OK: res} }