Documentation
¶
Overview ¶
Package fs exposes filesystem tools (Read, Write, Edit) as stateless singletons. Construction policy (eager vs lazy) is decided by the agent; this package only knows how to produce tool instances.
Index ¶
- Constants
- func HashContent(s string) [32]byte
- func Names() []tools.ToolName
- type DiffHunk
- type DiffLine
- type EditTool
- type FileDiff
- type GlobTool
- type ReadTool
- type ReadTracker
- func (t *ReadTracker) CanEdit(absPath string, currentMtime time.Time, currentHash [32]byte) (bool, string)
- func (t *ReadTracker) CanWrite(absPath string, currentMtime time.Time, currentHash [32]byte) (bool, string)
- func (t *ReadTracker) Forget(absPath string)
- func (t *ReadTracker) Lookup(absPath string) (readEntry, bool)
- func (t *ReadTracker) Record(absPath string, mtime time.Time, partial bool, contentHash [32]byte)
- func (t *ReadTracker) RecordRead(absPath string, mtime time.Time, partial bool, contentHash [32]byte)
- type WriteTool
Constants ¶
const ( OpCreate = "create" OpEdit = "edit" OpOverwrite = "overwrite" )
Op enumerates the kinds of file mutation a FileDiff describes. Kept as strings so the wire/UI side can render without importing this package.
const ( LineContext = "context" LineAdd = "add" LineRemove = "remove" )
LineKind enumerates how a single DiffLine relates to the change. Same semantics as a unified diff's prefix character.
const ContextLines = 3
ContextLines is how many unchanged lines we include above and below an edit hunk. Matches `diff -u` default.
const DefaultReadLimit = 2000
DefaultReadLimit caps an unbounded Read at this many lines. The model can pass an explicit larger limit when it really needs more, but the default protects the context window from accidental 50k-line dumps. Matches Claude Code's MAX_LINES_TO_READ.
const MaxEditFileSize = 1 << 30 // 1 GiB
MaxEditFileSize caps the on-disk byte size edit will load into memory. edit reads the whole file via readFileWithEncoding, so a multi-GB file would OOM the process; reject it instead. Mirrors ref FileEditTool's MAX_EDIT_FILE_SIZE (1 GiB).
Variables ¶
This section is empty.
Functions ¶
func HashContent ¶
HashContent returns the SHA-256 of s. Callers pass the LF-normalized full file content (what readFileWithEncoding produces) so the read site and the edit/write site hash the same representation and the staleness fallback can compare them. A zero [32]byte means "no full-content view" and disables the fallback.
Types ¶
type DiffHunk ¶
DiffHunk groups consecutive related lines, mirroring unified-diff hunks. Start counts are 1-based; Count is the number of lines in that hunk from the respective side (matches the "@@ -OldStart,OldCount +NewStart,NewCount @@" header).
type DiffLine ¶
type DiffLine struct {
Kind string // context / add / remove
Old int // 0 if not present on the old side
New int // 0 if not present on the new side
Text string
}
DiffLine carries one rendered row. Old and New are 1-based line numbers on each side; the one that doesn't apply (e.g. New on a "remove" line) is 0 so the UI can render an empty cell.
type EditTool ¶
type EditTool struct {
// contains filtered or unexported fields
}
func NewEdit ¶
func NewEdit(tracker *ReadTracker, workdir string) *EditTool
func (*EditTool) Description ¶
func (*EditTool) Schema ¶
func (t *EditTool) Schema() json.RawMessage
type FileDiff ¶
FileDiff is the structured payload write_file and edit_file attach to tools.Result.Metadata so UIs can render git-style diffs (red removes, green adds, line numbers in two columns) without parsing text.
LLMs never see this — Content carries the model-facing summary.
type GlobTool ¶
type GlobTool struct {
// contains filtered or unexported fields
}
func (*GlobTool) Description ¶
func (*GlobTool) Schema ¶
func (t *GlobTool) Schema() json.RawMessage
type ReadTool ¶
type ReadTool struct {
// contains filtered or unexported fields
}
func NewRead ¶
func NewRead(tracker *ReadTracker, workdir string) *ReadTool
func (*ReadTool) Description ¶
func (*ReadTool) Schema ¶
func (t *ReadTool) Schema() json.RawMessage
type ReadTracker ¶
type ReadTracker struct {
// contains filtered or unexported fields
}
ReadTracker records every read_file call the agent makes so that edit_file / write_file can refuse to mutate a file whose on-disk state has drifted from what the model has in context.
Two failure modes the tracker protects against, mirroring Claude Code's FileReadTool ↔ FileEditTool contract:
- Never read — the model is editing blind. Reject.
- Read but the file's bytes changed on disk afterwards — another process, or the user, edited it. Force a re-read so the model's old_string still reflects reality. Detected by an mtime advance, with a content-hash fallback so a touch / formatter / cloud-sync that bumps mtime without changing bytes does NOT force a re-read.
A truncated or offset read is NOT treated as a blocking "partial view": ref stores offset/limit but never blocks edits on them, and evva's edit path re-reads the full file and requires old_string to match uniquely, so editing after seeing only a slice is safe. IsPartialView is retained only to gate the read-dedup "file unchanged" stub.
Zero value is NOT usable; call NewReadTracker.
func NewReadTracker ¶
func NewReadTracker() *ReadTracker
func (*ReadTracker) CanEdit ¶
func (t *ReadTracker) CanEdit(absPath string, currentMtime time.Time, currentHash [32]byte) (bool, string)
CanEdit reports whether an edit_file call against absPath is permitted given the file's current mtime and content hash. When false, reason is the model-facing explanation (mirrors ref TS FileEditTool wording).
currentHash is the SHA-256 of the file's current full content (LF-normalized). It is only consulted when mtime indicates drift: if the recorded read carried a full-content hash and it still equals currentHash, the mtime bump is spurious (touch / formatter / cloud sync) and the edit proceeds — matching ref's full-read content comparison.
func (*ReadTracker) CanWrite ¶
func (t *ReadTracker) CanWrite(absPath string, currentMtime time.Time, currentHash [32]byte) (bool, string)
CanWrite reports whether write_file may overwrite absPath. Same gate as edit — overwriting a stale read is the same hazard as editing one.
func (*ReadTracker) Forget ¶
func (t *ReadTracker) Forget(absPath string)
Forget drops the entry for absPath. Used by tests; never called in production code.
func (*ReadTracker) Lookup ¶
func (t *ReadTracker) Lookup(absPath string) (readEntry, bool)
Lookup returns the recorded entry, or zero+false if absPath has never been recorded.
func (*ReadTracker) Record ¶
Record stores that absPath was read at the given mtime, with the partial flag indicating whether the read covered only a slice of the file and contentHash the SHA-256 of the full file content (zero to disable the staleness fallback). HasReadOffset is left false — callers that represent an actual Read tool invocation should use RecordRead instead so the dedup check can tell them apart from Edit/Write post-mutation updates.
func (*ReadTracker) RecordRead ¶
func (t *ReadTracker) RecordRead(absPath string, mtime time.Time, partial bool, contentHash [32]byte)
RecordRead is like Record but marks the entry as coming from an actual Read tool call. The Read tool's dedup check only fires for entries with HasReadOffset=true, so Edit/Write post-mutation updates (which call Record) don't cause spurious "File unchanged since last read" stubs.