Package Exports
- @eyueldk/aisdk-toolkit-filesystem
- @eyueldk/aisdk-toolkit-filesystem/adapters
- @eyueldk/aisdk-toolkit-filesystem/adapters/cloudflare-sandbox
- @eyueldk/aisdk-toolkit-filesystem/adapters/composite
- @eyueldk/aisdk-toolkit-filesystem/adapters/daytona
- @eyueldk/aisdk-toolkit-filesystem/adapters/docker
- @eyueldk/aisdk-toolkit-filesystem/adapters/local
- @eyueldk/aisdk-toolkit-filesystem/adapters/memory
- @eyueldk/aisdk-toolkit-filesystem/package.json
Readme
@eyueldk/aisdk-toolkit-filesystem
Pluggable filesystem tools for the Vercel AI SDK. Swap storage via FileSystemAdapter implementations.
Features
createFileSystemToolkit({ adapter, permissions?, editMode? })→{ tools, prompt, state }- Default
editMode: "applyPatch"—applyPatch(OpenCode-style patches) plusreadFile,glob,grep editMode: "tools"— granularwriteFile,editFile,remove,moveinstead ofapplyPatch- Optional path permissions (first matching glob wins)
- Adapters: memory, local disk, Docker container, Daytona sandbox, Cloudflare Sandbox, composite (multiple mounts)
Install
pnpm add @eyueldk/aisdk-toolkit-filesystemRequires Node 20+.
Quick start
import { generateText, stepCountIs } from "ai";
import { createFileSystemToolkit } from "@eyueldk/aisdk-toolkit-filesystem";
import { MemoryFileSystem } from "@eyueldk/aisdk-toolkit-filesystem/adapters/memory";
const adapter = await MemoryFileSystem.create({
initialFiles: { "README.md": "# hi" },
});
const { tools, prompt } = createFileSystemToolkit({
adapter,
permissions: [
{ mode: "allow", operations: ["read", "write"], paths: ["**"] },
],
});
await generateText({
model: yourLanguageModel,
tools,
stopWhen: stepCountIs(20),
system: `You can use filesystem tools.\n\n${prompt()}`,
prompt: "Read README.md and summarize it in one sentence.",
});Adapters
Import adapters from subpaths so bundlers (e.g. SSR) load only the runtime you need:
| Subpath | Adapter |
|---|---|
@eyueldk/aisdk-toolkit-filesystem/adapters/memory |
MemoryFileSystem |
@eyueldk/aisdk-toolkit-filesystem/adapters/local |
LocalFileSystem |
@eyueldk/aisdk-toolkit-filesystem/adapters/docker |
DockerFileSystem |
@eyueldk/aisdk-toolkit-filesystem/adapters/daytona |
DaytonaFileSystem |
@eyueldk/aisdk-toolkit-filesystem/adapters/cloudflare-sandbox |
CloudflareSandboxFileSystem |
@eyueldk/aisdk-toolkit-filesystem/adapters/composite |
CompositeFileSystem |
@eyueldk/aisdk-toolkit-filesystem/adapters |
FileSystemAdapter types only |
The main entry (@eyueldk/aisdk-toolkit-filesystem) exports the toolkit and FileSystemAdapter — not concrete adapters.
| Adapter | Factory | Notes |
|---|---|---|
| MemoryFileSystem | await MemoryFileSystem.create({ initialFiles? }) |
Volatile; tests and sandboxes |
| LocalFileSystem | await LocalFileSystem.create({ root }) |
Host paths under root; symlinks cannot escape root |
| DockerFileSystem | await DockerFileSystem.create({ container, root?, docker? }) |
Running container; list via find |
| CompositeFileSystem | CompositeFileSystem.create({ mounts }) |
Virtual union of adapters; mount keys must not overlap/nest |
| DaytonaFileSystem | await DaytonaFileSystem.create({ sandbox, root? }) or { sandboxId?, daytona? } |
Default root: workspace |
| CloudflareSandboxFileSystem | await CloudflareSandboxFileSystem.create({ sandbox, root? }) |
Pass ISandbox from getSandbox; default root: /workspace |
import { LocalFileSystem } from "@eyueldk/aisdk-toolkit-filesystem/adapters/local";
const disk = await LocalFileSystem.create({ root: "/path/to/workspace" });Daytona (requires DAYTONA_API_KEY; optional DAYTONA_API_URL for self-hosted):
import { DaytonaFileSystem } from "@eyueldk/aisdk-toolkit-filesystem/adapters/daytona";
import { Daytona } from "@daytonaio/sdk";
const sandbox = await new Daytona().create();
const adapter = await DaytonaFileSystem.create({ sandbox, root: "workspace" });Cloudflare Sandbox (Workers + @cloudflare/sandbox):
import { getSandbox } from "@cloudflare/sandbox";
import { CloudflareSandboxFileSystem } from "@eyueldk/aisdk-toolkit-filesystem/adapters/cloudflare-sandbox";
const sandbox = getSandbox(env.Sandbox, "session-id");
const adapter = await CloudflareSandboxFileSystem.create({ sandbox, root: "/workspace" });Adapter paths are POSIX and normalized with resolvePath. .. is allowed when the resolved path stays inside root.
Composite mounts
Combine adapters under virtual paths (mount keys must not nest):
import { createFileSystemToolkit } from "@eyueldk/aisdk-toolkit-filesystem";
import { CompositeFileSystem } from "@eyueldk/aisdk-toolkit-filesystem/adapters/composite";
import { LocalFileSystem } from "@eyueldk/aisdk-toolkit-filesystem/adapters/local";
import { MemoryFileSystem } from "@eyueldk/aisdk-toolkit-filesystem/adapters/memory";
const sandbox = await MemoryFileSystem.create();
const host = await LocalFileSystem.create({ root: "/project" });
const adapter = CompositeFileSystem.create({
mounts: { "/sandbox": sandbox, "/host": host },
});
const { tools, prompt } = createFileSystemToolkit({
adapter,
permissions: [
{ mode: "allow", operations: ["read", "write"], paths: ["**"] },
],
});Paths like sandbox/src/app.ts route to the sandbox adapter; host/README.md routes to the host adapter. Use glob on / to see mount names.
Tool outputs
Each tool returns a structured JSON object with result-only fields (inputs like path or pattern are not echoed back).
| Tool | Result fields |
|---|---|
readFile |
{ content } |
writeFile |
{ created } — requires overwrite: true to replace an existing file |
editFile |
{ changed, diff } — diff is a unified diff (via diff) |
applyPatch |
{ applied: [{ action, path, moveTo? }] } — OpenCode patch envelope (*** Begin Patch … *** End Patch) |
remove |
{ removed } — pass recursive: true to delete directories (editMode: "tools") |
move |
{ moved } (editMode: "tools") |
glob |
{ entries: [{ type, path }] } — optional path, include, stream |
grep |
{ matches: [{ path, line, text }] } |
Permissions
Omitted permissions defaults to deny-all (read and write on **). Add allow rules for paths the agent may access.
import { createFileSystemToolkit } from "@eyueldk/aisdk-toolkit-filesystem";
createFileSystemToolkit({
adapter,
permissions: [
{ mode: "deny", operations: ["write"], paths: ["etc/**"] },
{ mode: "allow", operations: ["read", "write"], paths: ["src/**"] },
],
});Rules: { mode: "allow" | "deny", operations: ["read" | "write"], paths: string[] }. First match wins; unmatched paths are allowed when you supply explicit rules. read / write apply to file content only — glob is always available for path discovery.
prompt() is synchronous — it returns permissions as JSON and patch/edit guidance. With default editMode, it includes the OpenCode applyPatch format.
editMode
| Value | Edit tools | System prompt |
|---|---|---|
applyPatch (default) |
applyPatch |
OpenCode patch language |
tools |
writeFile, editFile, remove, move |
Granular edit tools |
createFileSystemToolkit({
adapter,
editMode: "tools",
permissions: [{ mode: "allow", operations: ["read", "write"], paths: ["**"] }],
});Migration
2.6.0 → 2.6.1
DaytonaFileSystemusesdownloadFileinstead ofdownloadFileStreamon serverless/browser runtimes (e.g. Cloudflare Workers), matching@daytonaio/sdkgating.
2.5.0 → 2.5.1
applyPatchparser tolerates markdown code fences, trailing blank lines, and whitespace on envelope lines; clearer errors when*** End Patchis missing.
2.5 → 2.6
CloudflareSandboxFileSystemrestored at/adapters/cloudflare-sandbox(@cloudflare/sandboxISandbox).
2.4 → 2.5
- Default
editMode: "applyPatch"—applyPatchreplaceswriteFile/editFile/remove/moveunless you passeditMode: "tools". prompt()documents the OpenCode patch format wheneditModeisapplyPatch.- Low-level helpers
parsePatchandapplyPatchOperationsare exported; diffs useapplyDifffrom@openai/agents.
2.3 → 2.4
listtool removed — useglobwithinclude: ["file", "dir"]and patterns such as*or**.globreturns{ entries: [{ type, path }] }(not{ paths }); optionalpath,include,stream.removeFilerenamedremove; passrecursive: trueto delete directories.- New tools:
move. - Adapter methods:
remove,mkdir,move;FileSystemAdapter.lsunchanged (adaptermkdiris not exposed as a tool).
2.1 → 2.2
prompt()/filesystemPrompt()are synchronous again (no filesystem snapshot in the prompt).- Adapter listing type renamed
FileStat→FileInfo(andFileInfoType).
1.6.2 → 2.0
- Toolkit
hintstring replaced byprompt()— includes configured permissions (JSON). Standalone export:filesystemPrompt()(replacesFILE_SYSTEM_HINT). - Omitted
permissionsdefaults to deny-all for file content (read/write).globis always available for path discovery.
1.6.1 → 1.6.2
- Adapter subpaths renamed from
/adapter/*to/adapters/*(e.g.@eyueldk/aisdk-toolkit-filesystem/adapters/local).
1.6.0 → 1.6.1
- Adapter subpaths grouped under
/adapters/*.
1.5.1 → 1.6.0
- Adapters are no longer exported from the main entry. Import from
/adapters/*subpaths so SSR/bundlers avoid pulling unused backends (dockerode,memfs,@daytonaio/sdk, etc.).
1.5.0 → 1.5.1
DockerFileSystemwithroot: "/"(default) no longer rejects absolute paths such as/workspace/....
1.4 → 1.5
- All tools return structured JSON objects (with
outputSchema) instead of plain strings. - Outputs include result-only fields — tool inputs (
path,pattern, etc.) and redundant counts are not echoed back. writeFilerequiresoverwrite: trueto replace an existing file; returns{ created }.editFilereturns{ changed, diff }(unified diff viadiff).
1.3 → 1.4
CompositeFileSystem.create({ mounts })— combine adapters at virtual paths (e.g.{ "/sandbox": sandboxAdapter }); pass the result tocreateFileSystemToolkit({ adapter }).
1.2 → 1.3
- Tool names renamed:
read→readFile,write→writeFile,edit→editFile.
Configuration
| API | Description |
|---|---|
adapter.ls(path, { recursive?, stream? }) |
Default array; stream: true for large trees |
createFileSystemTools |
Tools only (no prompt / state) — same default deny-all when permissions is omitted |
Troubleshooting
- Docker: needs a running container and POSIX
findin the image. Useroot: "/"for full-container access, orroot: "/workspace"(etc.) to scope the adapter to a subdirectory. - Daytona: sandbox create may succeed while file ops fail if the toolbox proxy is unreachable (self-hosted OSS: resolve
proxy.localhostto loopback).
License
MIT — eyueldk/aisdk-toolkit