Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions vendor/agents/.changeset/bounded-workspace-ranges.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/shell": minor
---

Add optional bounded byte reads to WorkspaceFsLike and FileSystem. OPFS reads
native File slices under its existing locks; WorkspaceFileSystem validates ranges
and preserves compatibility with backends that only implement whole-file reads.
1 change: 1 addition & 0 deletions vendor/agents/docs/fork-diff.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ upstreamable additions, not claims that Workers need browser polyfills.
| Early model preparation and media: Think `prepareModel`, inference configuration and `tools/workspace.ts` materialize history/tool media for one selected model. | Rook model configuration; model-history-capability and read-tool wire/contract tests. | `beforeTurn` occurs too late to restore bytes excluded during history rendering. Keep the early owner seam until upstream supplies equivalent selection and per-render image/PDF capability controls. |
| Inference callback context: `think/src/inference-context.ts` binds admitted-turn model callbacks and tool iterator operations. | Real AI SDK mock-model streams in Think Chromium tests overlap ordinary/streaming tools and cleanup. | Native stream callbacks can enter outside their creator's scope. Compiler-assisted awaits cannot restore missing callback-entry context. Retire only when upstream/native context covers those same entry points. |
| Shell OPFS: `shell/src/browser/` implements host-selected directories, symlinks, Web Locks and staged writes; `filesystem.ts`, `extras.ts`, `helpers.ts`, `fs/mime-types.ts` share existing filesystem facts. | Rook shared workspace; real Worker OPFS tests cover failures, cancellation and concurrent writes. | Optional backend, not a second Rook filesystem. Runtime SQL/OPFS alone does not implement Shell's filesystem contract. Retire with a matching upstream backend. |
| Shell byte ranges: `WorkspaceFsLike`, `FileSystem`, `WorkspaceFileSystem` and `browser/opfs-workspace.ts` expose optional bounded reads. | Rook mounted-PDF shell performance; native Worker OPFS tests assert exact slices and legacy SQL adapter tests verify fallback. | Filesystem capability belongs in Shell. Existing implementations remain compatible; retire when upstream exposes equivalent range reads. |
| Shell metadata export and Git scans: package/build export `state-methods.ts`; `shell/src/git/index.ts` uses three `refresh:false` scan options. | Rook workspace connector; real browser Git test checks index bytes and changed-content detection. | Pure metadata avoids loading Codemode. Browser handles lack full POSIX stat identity; scans must not rewrite the index. Retire with equivalent public metadata and configurable/native scan behavior. |
| Voice flush/error/meter: `agents/src/voice/{types,audio-pipeline,voice-input}.ts` exposes optional `waitUntilClosed`, waits final audio before idle, propagates close failures and meters accepted PCM; background context keeps microtask ordering with `await Promise.resolve()` inside eager async entry. | Rook transcriber/root Agent; native Voice and browser dictation tests. | Final transcript must precede idle. `waitUntilReady` is already upstream. The implementation now lives in `agents/voice`; retire with equivalent upstream hooks; an unrelated response-settlement workaround cannot replace transcriber flush. |
| Package identity and syntax: six build scripts use tsdown’s `deps.neverBundle`, preserve constructor names and ES2021 async syntax; manifests expose optional browser and metadata leaves. Think aligns Chat to 4.38 and declares optional Slack/Discord adapter peers for those leaves. | Actor/facet identities and runtime async transform; built-package integration and export checks. | A consumer cannot recover renamed constructors or lowered async code. Retire when upstream builds preserve these contracts; never replace with source aliases. |
Expand Down
13 changes: 13 additions & 0 deletions vendor/agents/packages/shell/src/browser-tests/opfs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,16 @@ it("serializes appends from separate Workers using the same OPFS root", async ()
.sort()
);
});

it("reads bounded binary ranges through OPFS without reading a whole native file", async () => {
expect(await run(directory(), "byte-ranges")).toEqual({
prefix: [0, 1, 2],
suffix: [254, 255],
empty: [],
pastEnd: [],
missing: null,
directory: "EISDIR",
invalid: "EINVAL",
reads: [3, 2, 0, 0]
});
});
29 changes: 29 additions & 0 deletions vendor/agents/packages/shell/src/browser-tests/opfs.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,35 @@ async function run(name: string, operation: string, writer = "") {
const root = await storage.getDirectoryHandle(name, { create: true });
const ws = new OpfsWorkspace({ root });
switch (operation) {
case "byte-ranges": {
await ws.writeFileBytes(
"/data",
Uint8Array.from({ length: 256 }, (_, i) => i)
);
await ws.symlink("data", "/link");
const fs = ws;
const reads: number[] = [];
const arrayBuffer = Blob.prototype.arrayBuffer;
Blob.prototype.arrayBuffer = function () {
reads.push(this.size);
if (this.size > 3) throw new Error("Full-file read forbidden");
return arrayBuffer.call(this);
};
try {
return {
prefix: Array.from((await fs.readFileRange("/link", 0, 3)) ?? []),
suffix: Array.from((await fs.readFileRange("/data", 254, 10)) ?? []),
empty: Array.from((await fs.readFileRange("/data", 0, 0)) ?? []),
pastEnd: Array.from((await fs.readFileRange("/data", 300, 1)) ?? []),
missing: await fs.readFileRange("/missing", 0, 0),
directory: await errorCode(fs.readFileRange("/", 0, 0)),
invalid: await errorCode(fs.readFileRange("/data", -1, 1)),
reads
};
} finally {
Blob.prototype.arrayBuffer = arrayBuffer;
}
}
case "contract":
return {
memory: await fileContract(new InMemoryFs()),
Expand Down
23 changes: 22 additions & 1 deletion vendor/agents/packages/shell/src/browser/opfs-workspace.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { validateReadRange } from "../fs/read-range";
import type { FileInfo, Workspace, WorkspaceFsLike } from "../filesystem";
import {
MAX_WORKSPACE_PATH_LENGTH,
Expand Down Expand Up @@ -125,7 +126,23 @@ export class OpfsWorkspace implements WorkspaceFsLike {
return bytes === null ? null : TEXT_DECODER.decode(bytes);
}

async readFileRange(
path: string,
offset: number,
length: number
): Promise<Uint8Array | null> {
validateReadRange(offset, length);
return this.readBytes(path, { offset, length });
}

async readFileBytes(path: string): Promise<Uint8Array | null> {
return this.readBytes(path);
}

private async readBytes(
path: string,
range?: { offset: number; length: number }
): Promise<Uint8Array | null> {
const normalized = normalizePath(path);
return this.withSharedTree(async () => {
if (normalized === "/") {
Expand All @@ -139,7 +156,11 @@ export class OpfsWorkspace implements WorkspaceFsLike {
if (!native) return null;
if (native.kind !== "file")
throw fsError("EISDIR", `${path} is a directory`);
return new Uint8Array(await (await native.getFile()).arrayBuffer());
const file = await native.getFile();
const blob = range
? file.slice(range.offset, range.offset + range.length)
: file;
return new Uint8Array(await blob.arrayBuffer());
});
});
}
Expand Down
12 changes: 11 additions & 1 deletion vendor/agents/packages/shell/src/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,17 @@ export type WorkspaceFsLike = Pick<
| "symlink"
| "readlink"
| "glob"
>;
> & {
/** Optional bounded byte read; follows symlinks, returns null for missing files.
* Offset and length must be nonnegative safe integers with a safe sum.
* Reads past EOF return an empty buffer; short reads at EOF are allowed.
*/
readFileRange?(
path: string,
offset: number,
length: number
): Promise<Uint8Array | null>;
};

// ── Constants ────────────────────────────────────────────────────────

Expand Down
6 changes: 6 additions & 0 deletions vendor/agents/packages/shell/src/fs/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ export interface CpOptions {
export interface FileSystem {
readFile(path: string): Promise<string>;
readFileBytes(path: string): Promise<Uint8Array>;
/** Optional bounded byte read. Nonnegative safe integer range; missing paths throw ENOENT. */
readFileRange?(
path: string,
offset: number,
length: number
): Promise<Uint8Array>;
writeFile(path: string, content: string): Promise<void>;
writeFileBytes(path: string, content: Uint8Array): Promise<void>;
appendFile(path: string, content: string | Uint8Array): Promise<void>;
Expand Down
14 changes: 14 additions & 0 deletions vendor/agents/packages/shell/src/fs/read-range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** Validate byte ranges before calling APIs whose slicing rules accept negatives. */
export function validateReadRange(offset: number, length: number): void {
if (
!Number.isSafeInteger(offset) ||
offset < 0 ||
!Number.isSafeInteger(length) ||
length < 0 ||
!Number.isSafeInteger(offset + length)
) {
throw Object.assign(new RangeError("EINVAL: invalid byte range"), {
code: "EINVAL"
});
}
}
15 changes: 15 additions & 0 deletions vendor/agents/packages/shell/src/tests/agents/workspace.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { WorkspaceFileSystem } from "../../workspace";
import {
subscribe as dcSubscribe,
unsubscribe as dcUnsubscribe
Expand Down Expand Up @@ -31,6 +32,20 @@ export class TestWorkspaceAgent extends Agent {
}
});

async byteRangeContract() {
const fs = new WorkspaceFileSystem(this.workspace);
await this.workspace.writeFileBytes(
"/ranges",
new Uint8Array([0, 128, 255, 4])
);
await this.workspace.symlink("ranges", "/range-link");
return {
bytes: Array.from(await fs.readFileRange("/range-link", 1, 2)),
pastEnd: Array.from(await fs.readFileRange("/ranges", 8, 2)),
zero: Array.from(await fs.readFileRange("/ranges", 0, 0))
};
}

async stat(path: string): Promise<FileStat | null | { error: string }> {
try {
return await this.workspace.stat(path);
Expand Down
9 changes: 9 additions & 0 deletions vendor/agents/packages/shell/src/tests/workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2161,3 +2161,12 @@ describe("workspace — observability", () => {
expect(log).toHaveLength(0);
});
});

it("reads byte ranges through legacy WorkspaceFsLike adapters", async () => {
const agent = await freshAgent("range-fallback");
expect(await agent.byteRangeContract()).toEqual({
bytes: [128, 255],
pastEnd: [],
zero: []
});
});
16 changes: 16 additions & 0 deletions vendor/agents/packages/shell/src/workspace.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { validateReadRange } from "./fs/read-range";
import type { WorkspaceFsLike } from "./filesystem";
import type { FileSystem, FileSystemDirent, FsStat } from "./fs/interface";
import { FileSystemStateBackend } from "./memory";
Expand Down Expand Up @@ -42,6 +43,21 @@ export class WorkspaceFileSystem implements FileSystem {
return bytes;
}

async readFileRange(
path: string,
offset: number,
length: number
): Promise<Uint8Array> {
validateReadRange(offset, length);
const bytes = this.ws.readFileRange
? await this.ws.readFileRange(path, offset, length)
: (await this.ws.readFileBytes(path))?.slice(offset, offset + length);
if (bytes == null) throw enoent(path);
if (bytes.byteLength > length)
throw new Error("EIO: byte range exceeded requested length");
return bytes;
}

async writeFile(path: string, content: string): Promise<void> {
await this.ws.writeFile(path, content);
}
Expand Down
Loading