Repository navigation
Conversation
fs.read is text only: a file with NUL or invalid UTF-8 is refused as
Binary, and nothing else in the SDK returns a file's bytes, so a binary
file cannot be read back after fs.write. This adds the byte read the
issue proposes; the issue's owner decision is still pending, so this is
the proposed shape, not a settled one.
- fs.read_bytes(ref, path, offset = 0, length?) answers { kind,
snapshot, root, path, bytes, size, hasMore }. It reads one window at
the same immutable snapshot as fs.read (Branch, Commit or Layer).
`length` defaults to and is capped at the 48 KiB text window, so a
whole-file read never holds a large file in memory; a larger file is
paged by advancing offset by the returned byte count.
- An offset past the end answers empty bytes with hasMore: false. A
directory is refused IsDirectory, a symlink NotFile, a missing path
NoSuchPath, and a negative or unsafe offset/length InvalidInput.
- The engine reads through layerfs-content's existing read_range over
the call's read caches, so it is the same object walk as blame's byte
windows; telemetry is `sdk.bytes`. No storage, binding or
control-record change: the binding already returns SdkValue::Bytes as
a Node Buffer.
- sdk/fs surface: the @layerfs/agent wrapper, the `layerfs fs
read_bytes` command (--offset, --length), the CLI's JSON bytes output
(already { base64 }), and the agent/cli docs tables.
Tests: added engine/crates/layerfs-api/tests/sdk_read_bytes.rs (a
48 KiB+1000-byte file read in two windows, cap of a longer request,
offset past EOF, zero length; directory/symlink/missing/negative
refusals). The sdk_method_golden transcript gains six fs.read_bytes
lines. The JS fixture gains SDK and CLI cases; binary_write.mjs checks
a binary round-trip, paging, past-EOF and refusals; packaging.mjs
counts fs methods 14 -> 15. No tests deleted.
Checks (macOS): cargo +1.85.1 test -p layerfs-api --test sdk_read_bytes
--test sdk_method_golden (3 passed); clippy --locked --all-targets -D
warnings; cargo fmt --all -- --check; test --locked --all-targets
--no-run; tools/check_product_boundary.py PASS (959 files); tools
tests 155 passed; node api/agent/test/calls.mjs PASS (41 cases); node
--test api/cli/test/cli.test.mjs (77 passed); packaging surface and
lifecycle pass. The focused Rust tests ran with rusqlite's bundled
SQLite enabled locally (the host SQLite is 3.54.0, outside the overlay
profile); the manifest is unchanged in this commit. Not run here:
api/agent/test/binary_write.mjs and the Linux FUSE targets, which need
the real native binding and /dev/fuse on Linux.
Production LOC: 102763 -> 102848 (delta +85)
core 38395 -> 38395 (0), engine 61709 -> 61783 (+74), api 2659 ->
2670 (+11); reference absent (retired R9c; last counted 65,417 at
tree 498dd19). Method: python3 tools/production_loc.py --root on
`git archive` of the parent (582bc78) and of the staged tree
9ae38d1.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
fs.read_bytes(ref, path, offset = 0, length?)to the SDK, CLI and docs, so a file written as bytes can be read back.fs.readremains text only and still refuses a non-UTF-8 file asBinary.Answer:
{ kind, snapshot, root, path, bytes, size, hasMore }.fs.read(Branch, Commit or Layer).lengthdefaults to and is capped at the 48 KiB text window (window::BYTES); page by advancingoffsetby the returned byte count.offsetpast EOF → emptybytes,hasMore: false.IsDirectoryfor a directory,NotFilefor a symlink,NoSuchPathfor a missing path,InvalidInputfor a negative/unsafeoffsetorlength.layerfs_content::file::read_rangeover the call's read caches (the same byte-window path blame uses); telemetrysdk.bytes. No storage, binding or control-record change — the binding already returnsSdkValue::Bytesas a NodeBuffer.@layerfs/agentwrapper,layerfs fs read_bytes <ref> <path> [--offset <n>] [--length <n>], and the agent docs / architecture text.Note on the issue's decision gate
#417 is marked "Decision needed (owner)". This PR implements the proposed shape from the issue (same method name, same
{ bytes, size, hasMore }answer, same refusals and 48 KiB cap). If the owner settles on a different shape before merge, this branch is the thing to adjust; nothing is released by opening it.Tests
engine/crates/layerfs-api/tests/sdk_read_bytes.rs: 48 KiB + 1000-byte file read in two windows, cap of an oversizedlength, offset past EOF, zerolength; directory / symlink / missing / negative refusals.sdk_method_goldentranscript gains sixfs.read_byteslines (whole, offset+length, past EOF,IsDirectory, negative offset,NoSuchPath).binary_write.mjsround-trip/paging/EOF/refusals,packaging.mjsfs method count 14 → 15.Checks run (macOS)
cargo +1.85.1 test -p layerfs-api --test sdk_read_bytes --test sdk_method_golden— 3 passedcargo +1.85.1 clippy --locked --all-targets -- -D warnings— cleancargo +1.85.1 fmt --all -- --check— cleancargo +1.85.1 test --locked --all-targets --no-run— buildstools/check_product_boundary.py— PASS (959 files)toolsunit tests — 155 passednode api/agent/test/calls.mjs— PASS (41 cases)node --test api/cli/test/cli.test.mjs— 77 passednode api/agent/test/packaging.mjs surface|lifecycle— PASSThe focused Rust tests were run with rusqlite
bundledenabled locally (host SQLite 3.54.0 is outside the overlay profile); that manifest change is not in the commit.Not run locally:
api/agent/test/binary_write.mjsand the Linux FUSE targets — they need the real native binding and/dev/fuseon Linux.Production LOC
102763 → 102848 (delta +85): engine +74, api +11, core 0. Counted with
tools/production_loc.py --rootongit archiveof parent582bc786fand staged tree9ae38d1f41.