feat(fs): add a minimal read_image tool over the attachment and fs seams
The model reads a PNG/JPEG/WebP/GIF file, the bytes commit through the durable attachment lifecycle, and the tool result carries the real ImageBlock so the image enters context from the next request onward. FileSystem gains a bounded readBytes primitive (local + E2B providers); registration is conditional on the attachment store, and a strict execution gate refuses routes that do not declare image input, so a text route's durable history stays free of image blocks. llm-replay models may declare inputModalities, letting keyless ACP snapshots pin both the sha256-referenced success and the verbatim refusal. Supersedes the withdrawn route-scoped design of PR #598; the decision record is .agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.md.
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/filesystem.md
|
||||
filesystem.md: e0edad514b0c4d9108b18cc0b472600b29024c51
|
||||
filesystem.zh.md: 81c2d87b6a96740c7a99449a27c16331f5a0645a
|
||||
filesystem.md: 2013d2f2c9be4ac2cc16c1b0cf3685e961919a05
|
||||
filesystem.zh.md: 86e8cf3f2cf8c77ab4b53a438d86a980e13f4ac5
|
||||
|
||||
@@ -255,6 +255,7 @@ type FsErrorCode =
|
||||
| 'FS_NOT_DIRECTORY'
|
||||
| 'FS_NOT_TEXT'
|
||||
| 'FS_NOT_REGULAR_FILE'
|
||||
| 'FS_TOO_LARGE'
|
||||
| 'FS_PERMISSION_DENIED'
|
||||
| 'FS_SANDBOX_DENIED'
|
||||
| 'FS_IO_ERROR'
|
||||
@@ -372,6 +373,18 @@ abstract readText(target: FsTarget, signal?: AbortSignal): Promise<string>
|
||||
*/
|
||||
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
|
||||
|
||||
/**
|
||||
* Read the whole regular file as raw bytes with no decoding or binary
|
||||
* rejection. The bound lives at this seam so a backend can never buffer an
|
||||
* unbounded file: a target known or discovered to exceed `maxBytes` fails
|
||||
* with `FS_TOO_LARGE` instead of returning a truncated result.
|
||||
* @param target - the resolved target to read.
|
||||
* @param signal - aborts the read.
|
||||
* @param maxBytes - inclusive byte cap on the complete content.
|
||||
* @returns the full raw content, at most `maxBytes` long.
|
||||
*/
|
||||
abstract readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>
|
||||
|
||||
/**
|
||||
* List direct children of a directory in stable name order. Returns resolved
|
||||
* child targets plus cheap metadata only; never reads file contents.
|
||||
|
||||
@@ -255,6 +255,7 @@ type FsErrorCode =
|
||||
| 'FS_NOT_DIRECTORY'
|
||||
| 'FS_NOT_TEXT'
|
||||
| 'FS_NOT_REGULAR_FILE'
|
||||
| 'FS_TOO_LARGE'
|
||||
| 'FS_PERMISSION_DENIED'
|
||||
| 'FS_SANDBOX_DENIED'
|
||||
| 'FS_IO_ERROR'
|
||||
@@ -372,6 +373,18 @@ abstract readText(target: FsTarget, signal?: AbortSignal): Promise<string>
|
||||
*/
|
||||
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
|
||||
|
||||
/**
|
||||
* Read the whole regular file as raw bytes with no decoding or binary
|
||||
* rejection. The bound lives at this seam so a backend can never buffer an
|
||||
* unbounded file: a target known or discovered to exceed `maxBytes` fails
|
||||
* with `FS_TOO_LARGE` instead of returning a truncated result.
|
||||
* @param target - the resolved target to read.
|
||||
* @param signal - aborts the read.
|
||||
* @param maxBytes - inclusive byte cap on the complete content.
|
||||
* @returns the full raw content, at most `maxBytes` long.
|
||||
*/
|
||||
abstract readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>
|
||||
|
||||
/**
|
||||
* List direct children of a directory in stable name order. Returns resolved
|
||||
* child targets plus cheap metadata only; never reads file contents.
|
||||
|
||||
Reference in New Issue
Block a user