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 CheckpointSink
- type DiffHunk
- type DiffLine
- type EditTool
- func (t *EditTool) Description() string
- func (t *EditTool) Execute(ctx context.Context, logger *slog.Logger, input json.RawMessage) (tools.Result, error)
- func (t *EditTool) Name() string
- func (t *EditTool) Schema() json.RawMessage
- func (t *EditTool) WithCheckpoints(s CheckpointSink) *EditTool
- func (t *EditTool) WithLSPSync(s LSPSyncSink) *EditTool
- type FileDiff
- type GlobTool
- type LSPSyncSink
- 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
- func (t *WriteTool) Description() string
- func (t *WriteTool) Execute(ctx context.Context, logger *slog.Logger, input json.RawMessage) (tools.Result, error)
- func (t *WriteTool) Name() string
- func (t *WriteTool) Schema() json.RawMessage
- func (t *WriteTool) WithCheckpoints(s CheckpointSink) *WriteTool
- func (t *WriteTool) WithLSPSync(s LSPSyncSink) *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 ¶ added in v1.4.3
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 CheckpointSink ¶ added in v1.8.2
type CheckpointSink interface {
// CaptureBefore records absPath's current on-disk bytes before the caller
// overwrites the file. Idempotent per path within a turn (the first call
// wins, preserving the earliest before-image). Best-effort by contract: it
// must never block or error the edit.
CaptureBefore(absPath string)
}
CheckpointSink receives the pre-mutation state of files the edit/write tools are about to change, so the runtime's checkpoint/rewind engine can later restore them. The interface is declared here, on the consumer side, so pkg/tools/fs stays free of an internal/checkpoint import; the runtime wires a concrete implementation in via WithCheckpoints.
A nil sink disables capture entirely — the tools nil-check before calling, and the runtime only installs a sink when checkpointing is enabled — so the feature has zero cost on the edit hot path when it is off.
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
func (*EditTool) WithCheckpoints ¶ added in v1.8.2
func (t *EditTool) WithCheckpoints(s CheckpointSink) *EditTool
WithCheckpoints installs the checkpoint/rewind capture sink (nil-safe). The runtime calls this at tool construction; tests and embedders that omit it get a no-op capture path. Returns the receiver for fluent construction.
func (*EditTool) WithLSPSync ¶ added in v1.11.0
func (t *EditTool) WithLSPSync(s LSPSyncSink) *EditTool
WithLSPSync installs the LSP-sync notification sink (nil-safe). The runtime calls this at tool construction; tests and embedders that omit it get a no-op path — edits behave exactly as before this feature existed. Returns the receiver for fluent construction.
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 LSPSyncSink ¶ added in v1.11.0
type LSPSyncSink interface {
// NotifyEdited reports that absPath now holds newContent. The didChange
// dispatch itself is best-effort and, by default, asynchronous — it must
// never block or fail the edit. The returned string is a rendered
// diagnostics block to append to the tool's own Result.Content; it is
// non-empty only when the runtime's synchronous opt-in tier is enabled
// and diagnostics for this file arrived within its bounded wait. Empty in
// every other case (including whenever the synchronous tier is off).
NotifyEdited(absPath, newContent string) string
}
LSPSyncSink is notified after the edit/write tools mutate a file, so the runtime's LSP layer can re-analyze it and the model sees real, fresh diagnostics for what it just wrote — instead of editing blind until a later bash build happens to trip over the error. Declared consumer-side (mirrors CheckpointSink) so pkg/tools/fs imports neither pkg/tools/lsp nor internal/*; the runtime wires a concrete implementation in via WithLSPSync. A nil sink disables it entirely — the tools nil-check before calling, and the runtime only installs a sink when an LSP manager exists — so the feature has zero cost on the edit hot path when LSP is off.
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.
type WriteTool ¶
type WriteTool struct {
// contains filtered or unexported fields
}
func NewWrite ¶
func NewWrite(tracker *ReadTracker, workdir string) *WriteTool
func (*WriteTool) Description ¶
func (*WriteTool) Schema ¶
func (t *WriteTool) Schema() json.RawMessage
func (*WriteTool) WithCheckpoints ¶ added in v1.8.2
func (t *WriteTool) WithCheckpoints(s CheckpointSink) *WriteTool
WithCheckpoints installs the checkpoint/rewind capture sink (nil-safe). The runtime calls this at tool construction; tests and embedders that omit it get a no-op capture path. Returns the receiver for fluent construction.
func (*WriteTool) WithLSPSync ¶ added in v1.11.0
func (t *WriteTool) WithLSPSync(s LSPSyncSink) *WriteTool
WithLSPSync installs the LSP-sync notification sink (nil-safe). The runtime calls this at tool construction; tests and embedders that omit it get a no-op path — writes behave exactly as before this feature existed. Returns the receiver for fluent construction.