Przeglądaj źródła

feat(fs-e2b): implement cancellable sandbox byte windows

imccyu 1 miesiąc temu
rodzic
commit
4429763445

+ 1 - 1
packages/e2b/fs-e2b/README.md

@@ -44,7 +44,7 @@ Mounting it does not copy or mirror your local files — the sandbox's working d
 
 ### Reading files
 
-The agent can read a file's whole contents, stream large files, or read raw bytes up to a size cap. Binary files and files that are not valid UTF-8 text are refused with a clear message instead of being garbled; reads past the size cap fail with a message naming the limit.
+The agent can read a file's whole contents, stream large files, or read raw bytes up to a size cap or as one byte window. Binary files and files that are not valid UTF-8 text are refused with a clear message instead of being garbled; reads past the size cap fail with a message naming the limit.
 
 ### Writing and editing files
 

+ 1 - 1
packages/e2b/fs-e2b/README.zh.md

@@ -44,7 +44,7 @@ kind: "package-reference"
 
 ### 读取文件
 
-agent 可以读取文件的完整内容、流式读取大文件,或在大小上限内读取原始字节。二进制文件与不是有效 UTF-8 文本的文件会被明确拒绝而不是乱码显示;超过大小上限的读取会以指明上限的消息失败。
+agent 可以读取文件的完整内容、流式读取大文件,或在大小上限内或按字节窗口读取原始字节。二进制文件与不是有效 UTF-8 文本的文件会被明确拒绝而不是乱码显示;超过大小上限的读取会以指明上限的消息失败。
 
 ### 写入与编辑文件
 

+ 46 - 0
packages/e2b/fs-e2b/src/index.ts

@@ -292,6 +292,52 @@ export class E2BFileSystem extends FileSystem {
     return whole
   }
 
+  override async readByteRange(target: FsTarget, range: { offset: number; length: number }, signal?: AbortSignal): Promise<Uint8Array> {
+    const sandbox = await this.ctx.e2b.getSandbox()
+    await this.requireRegular(target, signal)
+    if (range.length === 0) return new Uint8Array(0)
+    // The SDK streams only from the file's start: skip to `offset`, keep
+    // `length` bytes, and cancel the stream there, so no more than the window
+    // beyond the skipped prefix is ever transferred.
+    const stream = await openReadStream(sandbox, target, signal)
+    const reader = stream.getReader()
+    const window = new Uint8Array(range.length)
+    const end = range.offset + range.length
+    let position = 0
+    let filled = 0
+    let drained = false
+    try {
+      while (filled < range.length) {
+        assertNotAborted(signal, 'read')
+        const next = await reader.read()
+        if (next.done) {
+          drained = true
+          break
+        }
+        const from = Math.max(range.offset, position)
+        const to = Math.min(end, position + next.value.byteLength)
+        if (to > from) {
+          window.set(next.value.subarray(from - position, to - position), filled)
+          filled += to - from
+        }
+        position += next.value.byteLength
+      }
+    } catch (error: unknown) {
+      throw mapError(error, 'read', target.displayPath, signal)
+    } finally {
+      if (!drained) {
+        try {
+          await reader.cancel()
+        } catch (_streamCancellationFailure) {
+          // The window is complete or the read already failed; a cancellation
+          // failure on the abandoned remote stream adds nothing actionable.
+        }
+      }
+      reader.releaseLock()
+    }
+    return filled === range.length ? window : window.subarray(0, filled)
+  }
+
   override async streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>> {
     const sandbox = await this.ctx.e2b.getSandbox()
     await this.requireRegular(target, signal)