@mastra/agentfs 0.2.0 → 0.2.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,516 +1,475 @@
1
- import { mkdirSync } from 'fs';
2
- import os from 'os';
3
- import nodePath from 'path';
4
- import { MastraFilesystem, WorkspaceReadOnlyError, FileExistsError, FileNotFoundError, IsDirectoryError, DirectoryNotEmptyError, NotDirectoryError, DirectoryNotFoundError, PermissionError } from '@mastra/core/workspace';
5
- import { AgentFS } from 'agentfs-sdk';
6
-
7
- // src/filesystem/index.ts
1
+ import { mkdirSync } from "fs";
2
+ import os from "os";
3
+ import nodePath from "path";
4
+ import { DirectoryNotEmptyError, DirectoryNotFoundError, FileExistsError, FileNotFoundError, IsDirectoryError, MastraFilesystem, NotDirectoryError, PermissionError, WorkspaceReadOnlyError } from "@mastra/core/workspace";
5
+ import { AgentFS } from "agentfs-sdk";
6
+ //#region src/filesystem/index.ts
7
+ /**
8
+ * AgentFS Filesystem Provider
9
+ *
10
+ * Implements WorkspaceFilesystem backed by AgentFS (Turso/SQLite).
11
+ * Follows the same pattern as S3Filesystem.
12
+ */
13
+ /**
14
+ * Expand a leading `~` to the user's home directory.
15
+ * Node.js path APIs don't handle tilde — only the shell does.
16
+ */
8
17
  function expandTilde(p) {
9
- if (p === "~") return os.homedir();
10
- if (p.startsWith("~/") || p.startsWith("~\\")) {
11
- return nodePath.join(os.homedir(), p.slice(2));
12
- }
13
- return p;
18
+ if (p === "~") return os.homedir();
19
+ if (p.startsWith("~/") || p.startsWith("~\\")) return nodePath.join(os.homedir(), p.slice(2));
20
+ return p;
14
21
  }
22
+ /**
23
+ * Resolve a database path to an absolute path.
24
+ * Expands tilde and resolves relative paths against cwd (at construction time).
25
+ */
15
26
  function resolveDbPath(p) {
16
- return nodePath.resolve(expandTilde(p));
27
+ return nodePath.resolve(expandTilde(p));
17
28
  }
18
29
  function normalizePath(input) {
19
- if (input === "" || input === ".") return "/";
20
- let path = input.startsWith("/") ? input : "/" + input;
21
- let result = "";
22
- let prevSlash = false;
23
- for (let i = 0; i < path.length; i++) {
24
- const ch = path[i];
25
- if (ch === "/") {
26
- if (!prevSlash) {
27
- result += ch;
28
- }
29
- prevSlash = true;
30
- } else {
31
- result += ch;
32
- prevSlash = false;
33
- }
34
- }
35
- if (result.length > 1 && result.endsWith("/")) {
36
- result = result.slice(0, -1);
37
- }
38
- return result;
30
+ if (input === "" || input === ".") return "/";
31
+ let path = input.startsWith("/") ? input : "/" + input;
32
+ let result = "";
33
+ let prevSlash = false;
34
+ for (let i = 0; i < path.length; i++) {
35
+ const ch = path[i];
36
+ if (ch === "/") {
37
+ if (!prevSlash) result += ch;
38
+ prevSlash = true;
39
+ } else {
40
+ result += ch;
41
+ prevSlash = false;
42
+ }
43
+ }
44
+ if (result.length > 1 && result.endsWith("/")) result = result.slice(0, -1);
45
+ return result;
39
46
  }
40
47
  function getParentPath(path) {
41
- const normalized = normalizePath(path);
42
- if (normalized === "/") return "/";
43
- const lastSlash = normalized.lastIndexOf("/");
44
- return lastSlash <= 0 ? "/" : normalized.slice(0, lastSlash);
48
+ const normalized = normalizePath(path);
49
+ if (normalized === "/") return "/";
50
+ const lastSlash = normalized.lastIndexOf("/");
51
+ return lastSlash <= 0 ? "/" : normalized.slice(0, lastSlash);
45
52
  }
46
53
  function joinPath(base, name) {
47
- if (base === "/") return normalizePath("/" + name);
48
- return normalizePath(base + "/" + name);
54
+ if (base === "/") return normalizePath("/" + name);
55
+ return normalizePath(base + "/" + name);
49
56
  }
50
57
  function getBaseName(path) {
51
- const normalized = normalizePath(path);
52
- if (normalized === "/") return "";
53
- const lastSlash = normalized.lastIndexOf("/");
54
- return normalized.slice(lastSlash + 1);
58
+ const normalized = normalizePath(path);
59
+ if (normalized === "/") return "";
60
+ const lastSlash = normalized.lastIndexOf("/");
61
+ return normalized.slice(lastSlash + 1);
55
62
  }
56
63
  function isErrnoError(error) {
57
- return typeof error === "object" && error !== null && "code" in error;
64
+ return typeof error === "object" && error !== null && "code" in error;
58
65
  }
59
66
  function mapError(error, path, context = "file") {
60
- if (!isErrnoError(error)) {
61
- return error instanceof Error ? error : new Error(String(error));
62
- }
63
- switch (error.code) {
64
- case "ENOENT":
65
- return context === "directory" ? new DirectoryNotFoundError(path) : new FileNotFoundError(path);
66
- case "EEXIST":
67
- return new FileExistsError(path);
68
- case "EISDIR":
69
- return new IsDirectoryError(path);
70
- case "ENOTDIR":
71
- return new NotDirectoryError(path);
72
- case "ENOTEMPTY":
73
- return new DirectoryNotEmptyError(path);
74
- case "EPERM":
75
- case "EACCES":
76
- return new PermissionError(path, "access");
77
- default:
78
- return error instanceof Error ? error : new Error(String(error));
79
- }
67
+ if (!isErrnoError(error)) return error instanceof Error ? error : new Error(String(error));
68
+ switch (error.code) {
69
+ case "ENOENT": return context === "directory" ? new DirectoryNotFoundError(path) : new FileNotFoundError(path);
70
+ case "EEXIST": return new FileExistsError(path);
71
+ case "EISDIR": return new IsDirectoryError(path);
72
+ case "ENOTDIR": return new NotDirectoryError(path);
73
+ case "ENOTEMPTY": return new DirectoryNotEmptyError(path);
74
+ case "EPERM":
75
+ case "EACCES": return new PermissionError(path, "access");
76
+ default: return error instanceof Error ? error : new Error(String(error));
77
+ }
80
78
  }
81
79
  function hasCode(error, code) {
82
- return isErrnoError(error) && error.code === code;
80
+ return isErrnoError(error) && error.code === code;
83
81
  }
82
+ /**
83
+ * AgentFS filesystem implementation.
84
+ *
85
+ * Stores files in a Turso/SQLite database via the AgentFS SDK.
86
+ *
87
+ * @example Using agentId
88
+ * ```typescript
89
+ * import { AgentFSFilesystem } from '@mastra/agentfs';
90
+ *
91
+ * const fs = new AgentFSFilesystem({ agentId: 'my-agent' });
92
+ * ```
93
+ *
94
+ * @example Using a pre-opened instance
95
+ * ```typescript
96
+ * import { AgentFS } from 'agentfs-sdk';
97
+ * import { AgentFSFilesystem } from '@mastra/agentfs';
98
+ *
99
+ * const agent = await AgentFS.open({ id: 'my-agent' });
100
+ * const fs = new AgentFSFilesystem({ agent });
101
+ * ```
102
+ */
84
103
  var AgentFSFilesystem = class extends MastraFilesystem {
85
- id;
86
- name = "AgentFSFilesystem";
87
- provider = "agentfs";
88
- readOnly;
89
- status = "pending";
90
- // Display metadata
91
- displayName;
92
- icon;
93
- description;
94
- _agent = null;
95
- _ownsAgent;
96
- _agentId;
97
- _path;
98
- constructor(options) {
99
- if (!options.agentId && !options.path && !options.agent) {
100
- throw new Error("AgentFSFilesystem requires at least one of 'agentId', 'path', or 'agent'.");
101
- }
102
- super({ ...options, name: "AgentFSFilesystem" });
103
- this.id = options.id ?? `agentfs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
104
- this._agentId = options.agentId;
105
- this._path = options.path ? resolveDbPath(options.path) : void 0;
106
- this.readOnly = options.readOnly;
107
- if (options.agent) {
108
- this._agent = options.agent;
109
- this._ownsAgent = false;
110
- } else {
111
- this._ownsAgent = true;
112
- }
113
- this.icon = options.icon ?? "database";
114
- this.displayName = options.displayName ?? "AgentFS";
115
- this.description = options.description;
116
- }
117
- /** The underlying AgentFS instance, or null if not yet initialized. */
118
- get agent() {
119
- return this._agent;
120
- }
121
- // ---------------------------------------------------------------------------
122
- // Info
123
- // ---------------------------------------------------------------------------
124
- getInfo() {
125
- return {
126
- id: this.id,
127
- name: this.name,
128
- provider: this.provider,
129
- status: this.status,
130
- error: this.error,
131
- readOnly: this.readOnly,
132
- icon: this.icon,
133
- metadata: {
134
- ...this._agentId && { agentId: this._agentId },
135
- ...this._path && { dbPath: this._path }
136
- }
137
- };
138
- }
139
- getInstructions() {
140
- const label = this._agentId ? `agent "${this._agentId}"` : "database";
141
- const access = this.readOnly ? "Read-only" : "Persistent";
142
- return `AgentFS storage for ${label}. ${access} SQLite-backed filesystem \u2014 files are retained across sessions.`;
143
- }
144
- // ---------------------------------------------------------------------------
145
- // Lifecycle
146
- // ---------------------------------------------------------------------------
147
- async init() {
148
- if (this._agent) return;
149
- const openOptions = {};
150
- if (this._agentId) openOptions.id = this._agentId;
151
- if (this._path) {
152
- openOptions.path = this._path;
153
- mkdirSync(nodePath.dirname(this._path), { recursive: true });
154
- }
155
- this._agent = await AgentFS.open(openOptions);
156
- }
157
- async destroy() {
158
- if (this._agent && this._ownsAgent) {
159
- await this._agent.close();
160
- }
161
- this._agent = null;
162
- }
163
- // ---------------------------------------------------------------------------
164
- // Internal helpers
165
- // ---------------------------------------------------------------------------
166
- async getAgent() {
167
- await this.ensureReady();
168
- return this._agent;
169
- }
170
- assertWritable(operation) {
171
- if (this.readOnly) {
172
- throw new WorkspaceReadOnlyError(operation);
173
- }
174
- }
175
- // ---------------------------------------------------------------------------
176
- // File Operations
177
- // ---------------------------------------------------------------------------
178
- async readFile(path, options) {
179
- const agent = await this.getAgent();
180
- const normalized = normalizePath(path);
181
- try {
182
- if (options?.encoding) {
183
- return await agent.fs.readFile(normalized, options.encoding);
184
- }
185
- return await agent.fs.readFile(normalized);
186
- } catch (error) {
187
- throw mapError(error, normalized, "file");
188
- }
189
- }
190
- async writeFile(path, content, options) {
191
- this.assertWritable("writeFile");
192
- const agent = await this.getAgent();
193
- const normalized = normalizePath(path);
194
- if (options?.recursive !== false) {
195
- await this.mkdirRecursive(agent, getParentPath(normalized));
196
- }
197
- if (options?.overwrite === false) {
198
- try {
199
- await agent.fs.access(normalized);
200
- throw new FileExistsError(normalized);
201
- } catch (error) {
202
- if (error instanceof FileExistsError) throw error;
203
- if (!hasCode(error, "ENOENT")) throw mapError(error, normalized, "file");
204
- }
205
- }
206
- try {
207
- const data = typeof content === "string" ? content : Buffer.from(content);
208
- await agent.fs.writeFile(normalized, data);
209
- } catch (error) {
210
- throw mapError(error, normalized, "file");
211
- }
212
- }
213
- async appendFile(path, content) {
214
- this.assertWritable("appendFile");
215
- let existing = Buffer.alloc(0);
216
- try {
217
- const current = await this.readFile(path);
218
- existing = Buffer.isBuffer(current) ? Buffer.from(current) : Buffer.from(current);
219
- } catch (error) {
220
- if (error instanceof FileNotFoundError) ; else {
221
- throw error;
222
- }
223
- }
224
- const appendBuffer = typeof content === "string" ? Buffer.from(content, "utf-8") : Buffer.from(content);
225
- await this.writeFile(path, Buffer.concat([existing, appendBuffer]));
226
- }
227
- async deleteFile(path, options) {
228
- this.assertWritable("deleteFile");
229
- const agent = await this.getAgent();
230
- const normalized = normalizePath(path);
231
- try {
232
- const st = await agent.fs.stat(normalized);
233
- if (st.isDirectory()) {
234
- throw new IsDirectoryError(normalized);
235
- }
236
- } catch (error) {
237
- if (error instanceof IsDirectoryError) throw error;
238
- if (hasCode(error, "ENOENT")) {
239
- if (options?.force) return;
240
- throw new FileNotFoundError(normalized);
241
- }
242
- throw mapError(error, normalized, "file");
243
- }
244
- try {
245
- await agent.fs.unlink(normalized);
246
- } catch (error) {
247
- if (options?.force && hasCode(error, "ENOENT")) return;
248
- throw mapError(error, normalized, "file");
249
- }
250
- }
251
- async copyFile(src, dest, options) {
252
- this.assertWritable("copyFile");
253
- const agent = await this.getAgent();
254
- const normalizedSrc = normalizePath(src);
255
- const normalizedDest = normalizePath(dest);
256
- if (options?.overwrite === false) {
257
- try {
258
- await agent.fs.access(normalizedDest);
259
- throw new FileExistsError(normalizedDest);
260
- } catch (error) {
261
- if (error instanceof FileExistsError) throw error;
262
- if (!hasCode(error, "ENOENT")) throw mapError(error, normalizedDest, "file");
263
- }
264
- }
265
- try {
266
- const st = await agent.fs.stat(normalizedSrc);
267
- if (st.isDirectory()) {
268
- if (!options?.recursive) {
269
- throw new IsDirectoryError(normalizedSrc);
270
- }
271
- await this.copyDirRecursive(agent, normalizedSrc, normalizedDest, options);
272
- return;
273
- }
274
- await this.mkdirRecursive(agent, getParentPath(normalizedDest));
275
- await agent.fs.copyFile(normalizedSrc, normalizedDest);
276
- } catch (error) {
277
- if (error instanceof IsDirectoryError || error instanceof FileExistsError) throw error;
278
- throw mapError(error, normalizedSrc, "file");
279
- }
280
- }
281
- async moveFile(src, dest, options) {
282
- this.assertWritable("moveFile");
283
- const agent = await this.getAgent();
284
- const normalizedSrc = normalizePath(src);
285
- const normalizedDest = normalizePath(dest);
286
- if (options?.overwrite === false) {
287
- try {
288
- await agent.fs.access(normalizedDest);
289
- throw new FileExistsError(normalizedDest);
290
- } catch (error) {
291
- if (error instanceof FileExistsError) throw error;
292
- if (!hasCode(error, "ENOENT")) throw mapError(error, normalizedDest, "file");
293
- }
294
- }
295
- await this.mkdirRecursive(agent, getParentPath(normalizedDest));
296
- try {
297
- await agent.fs.rename(normalizedSrc, normalizedDest);
298
- } catch (error) {
299
- throw mapError(error, normalizedSrc, "file");
300
- }
301
- }
302
- // ---------------------------------------------------------------------------
303
- // Directory Operations
304
- // ---------------------------------------------------------------------------
305
- async mkdir(path, options) {
306
- this.assertWritable("mkdir");
307
- const agent = await this.getAgent();
308
- const normalized = normalizePath(path);
309
- if (options?.recursive !== false) {
310
- await this.mkdirRecursive(agent, normalized);
311
- return;
312
- }
313
- try {
314
- await agent.fs.mkdir(normalized);
315
- } catch (error) {
316
- throw mapError(error, normalized, "directory");
317
- }
318
- }
319
- async rmdir(path, options) {
320
- this.assertWritable("rmdir");
321
- const agent = await this.getAgent();
322
- const normalized = normalizePath(path);
323
- if (options?.recursive) {
324
- try {
325
- await agent.fs.rm(normalized, { recursive: true });
326
- } catch (error) {
327
- if (options?.force && hasCode(error, "ENOENT")) return;
328
- throw mapError(error, normalized, "directory");
329
- }
330
- return;
331
- }
332
- try {
333
- const entries = await agent.fs.readdir(normalized);
334
- if (entries.length > 0) {
335
- throw new DirectoryNotEmptyError(normalized);
336
- }
337
- await agent.fs.rmdir(normalized);
338
- } catch (error) {
339
- if (error instanceof DirectoryNotEmptyError) throw error;
340
- if (options?.force && hasCode(error, "ENOENT")) return;
341
- throw mapError(error, normalized, "directory");
342
- }
343
- }
344
- async readdir(path, options) {
345
- const agent = await this.getAgent();
346
- const normalized = normalizePath(path);
347
- try {
348
- const st = await agent.fs.stat(normalized);
349
- if (!st.isDirectory()) {
350
- throw new NotDirectoryError(normalized);
351
- }
352
- const dirEntries = await agent.fs.readdirPlus(normalized);
353
- let entries = dirEntries.map((entry) => ({
354
- name: entry.name,
355
- type: entry.stats.isDirectory() ? "directory" : "file",
356
- size: entry.stats.size
357
- }));
358
- if (options?.extension) {
359
- const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
360
- entries = entries.filter((entry) => {
361
- if (entry.type === "directory") return true;
362
- return extensions.some((ext) => entry.name.endsWith(ext));
363
- });
364
- }
365
- if (options?.recursive) {
366
- const maxDepth = options.maxDepth ?? Infinity;
367
- const subdirs = entries.filter((e) => e.type === "directory");
368
- for (const dir of subdirs) {
369
- if (maxDepth > 1) {
370
- const subEntries = await this.readdir(joinPath(normalized, dir.name), {
371
- ...options,
372
- maxDepth: maxDepth - 1
373
- });
374
- for (const sub of subEntries) {
375
- entries.push({
376
- ...sub,
377
- name: dir.name + "/" + sub.name
378
- });
379
- }
380
- }
381
- }
382
- }
383
- return entries;
384
- } catch (error) {
385
- if (error instanceof NotDirectoryError || error instanceof DirectoryNotFoundError) {
386
- throw error;
387
- }
388
- throw mapError(error, normalized, "directory");
389
- }
390
- }
391
- // ---------------------------------------------------------------------------
392
- // Path Operations
393
- // ---------------------------------------------------------------------------
394
- async exists(path) {
395
- const agent = await this.getAgent();
396
- const normalized = normalizePath(path);
397
- try {
398
- await agent.fs.access(normalized);
399
- return true;
400
- } catch {
401
- return false;
402
- }
403
- }
404
- async stat(path) {
405
- const agent = await this.getAgent();
406
- const normalized = normalizePath(path);
407
- try {
408
- const st = await agent.fs.stat(normalized);
409
- return {
410
- name: getBaseName(normalized) || "",
411
- path: normalized,
412
- type: st.isDirectory() ? "directory" : "file",
413
- size: st.size,
414
- createdAt: new Date(st.ctime * 1e3),
415
- modifiedAt: new Date(st.mtime * 1e3)
416
- };
417
- } catch (error) {
418
- throw mapError(error, normalized, "file");
419
- }
420
- }
421
- // ---------------------------------------------------------------------------
422
- // Type checks
423
- // ---------------------------------------------------------------------------
424
- /**
425
- * Check if the path points to a file.
426
- * Returns `false` if the path doesn't exist.
427
- */
428
- async isFile(path) {
429
- try {
430
- const st = await this.stat(path);
431
- return st.type === "file";
432
- } catch (error) {
433
- if (error instanceof FileNotFoundError) return false;
434
- throw error;
435
- }
436
- }
437
- /**
438
- * Check if the path points to a directory.
439
- * Returns `false` if the path doesn't exist.
440
- */
441
- async isDirectory(path) {
442
- try {
443
- const st = await this.stat(path);
444
- return st.type === "directory";
445
- } catch (error) {
446
- if (error instanceof FileNotFoundError) return false;
447
- throw error;
448
- }
449
- }
450
- // ---------------------------------------------------------------------------
451
- // Private helpers
452
- // ---------------------------------------------------------------------------
453
- /**
454
- * Recursively create directories, ignoring EEXIST errors.
455
- */
456
- async mkdirRecursive(agent, path) {
457
- if (path === "/") return;
458
- const segments = path.split("/").filter(Boolean);
459
- let current = "";
460
- for (const segment of segments) {
461
- current += "/" + segment;
462
- try {
463
- await agent.fs.mkdir(current);
464
- } catch (error) {
465
- if (!hasCode(error, "EEXIST")) {
466
- throw mapError(error, current, "directory");
467
- }
468
- }
469
- }
470
- }
471
- /**
472
- * Recursively copy a directory tree.
473
- */
474
- async copyDirRecursive(agent, src, dest, options) {
475
- await this.mkdirRecursive(agent, dest);
476
- const entries = await agent.fs.readdirPlus(src);
477
- for (const entry of entries) {
478
- const srcChild = joinPath(src, entry.name);
479
- const destChild = joinPath(dest, entry.name);
480
- if (entry.stats.isDirectory()) {
481
- await this.copyDirRecursive(agent, srcChild, destChild, options);
482
- } else {
483
- if (options?.overwrite === false) {
484
- try {
485
- await agent.fs.access(destChild);
486
- throw new FileExistsError(destChild);
487
- } catch (error) {
488
- if (error instanceof FileExistsError) throw error;
489
- if (!hasCode(error, "ENOENT")) throw mapError(error, destChild, "file");
490
- }
491
- }
492
- await agent.fs.copyFile(srcChild, destChild);
493
- }
494
- }
495
- }
104
+ id;
105
+ name = "AgentFSFilesystem";
106
+ provider = "agentfs";
107
+ readOnly;
108
+ status = "pending";
109
+ displayName;
110
+ icon;
111
+ description;
112
+ _agent = null;
113
+ _ownsAgent;
114
+ _agentId;
115
+ _path;
116
+ constructor(options) {
117
+ if (!options.agentId && !options.path && !options.agent) throw new Error("AgentFSFilesystem requires at least one of 'agentId', 'path', or 'agent'.");
118
+ super({
119
+ ...options,
120
+ name: "AgentFSFilesystem"
121
+ });
122
+ this.id = options.id ?? `agentfs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
123
+ this._agentId = options.agentId;
124
+ this._path = options.path ? resolveDbPath(options.path) : void 0;
125
+ this.readOnly = options.readOnly;
126
+ if (options.agent) {
127
+ this._agent = options.agent;
128
+ this._ownsAgent = false;
129
+ } else this._ownsAgent = true;
130
+ this.icon = options.icon ?? "database";
131
+ this.displayName = options.displayName ?? "AgentFS";
132
+ this.description = options.description;
133
+ }
134
+ /** The underlying AgentFS instance, or null if not yet initialized. */
135
+ get agent() {
136
+ return this._agent;
137
+ }
138
+ getInfo() {
139
+ return {
140
+ id: this.id,
141
+ name: this.name,
142
+ provider: this.provider,
143
+ status: this.status,
144
+ error: this.error,
145
+ readOnly: this.readOnly,
146
+ icon: this.icon,
147
+ metadata: {
148
+ ...this._agentId && { agentId: this._agentId },
149
+ ...this._path && { dbPath: this._path }
150
+ }
151
+ };
152
+ }
153
+ getInstructions() {
154
+ return `AgentFS storage for ${this._agentId ? `agent "${this._agentId}"` : "database"}. ${this.readOnly ? "Read-only" : "Persistent"} SQLite-backed filesystem — files are retained across sessions.`;
155
+ }
156
+ async init() {
157
+ if (this._agent) return;
158
+ const openOptions = {};
159
+ if (this._agentId) openOptions.id = this._agentId;
160
+ if (this._path) {
161
+ openOptions.path = this._path;
162
+ mkdirSync(nodePath.dirname(this._path), { recursive: true });
163
+ }
164
+ this._agent = await AgentFS.open(openOptions);
165
+ }
166
+ async destroy() {
167
+ if (this._agent && this._ownsAgent) await this._agent.close();
168
+ this._agent = null;
169
+ }
170
+ async getAgent() {
171
+ await this.ensureReady();
172
+ return this._agent;
173
+ }
174
+ assertWritable(operation) {
175
+ if (this.readOnly) throw new WorkspaceReadOnlyError(operation);
176
+ }
177
+ async readFile(path, options) {
178
+ const agent = await this.getAgent();
179
+ const normalized = normalizePath(path);
180
+ try {
181
+ if (options?.encoding) return await agent.fs.readFile(normalized, options.encoding);
182
+ return await agent.fs.readFile(normalized);
183
+ } catch (error) {
184
+ throw mapError(error, normalized, "file");
185
+ }
186
+ }
187
+ async writeFile(path, content, options) {
188
+ this.assertWritable("writeFile");
189
+ const agent = await this.getAgent();
190
+ const normalized = normalizePath(path);
191
+ if (options?.recursive !== false) await this.mkdirRecursive(agent, getParentPath(normalized));
192
+ if (options?.overwrite === false) try {
193
+ await agent.fs.access(normalized);
194
+ throw new FileExistsError(normalized);
195
+ } catch (error) {
196
+ if (error instanceof FileExistsError) throw error;
197
+ if (!hasCode(error, "ENOENT")) throw mapError(error, normalized, "file");
198
+ }
199
+ try {
200
+ const data = typeof content === "string" ? content : Buffer.from(content);
201
+ await agent.fs.writeFile(normalized, data);
202
+ } catch (error) {
203
+ throw mapError(error, normalized, "file");
204
+ }
205
+ }
206
+ async appendFile(path, content) {
207
+ this.assertWritable("appendFile");
208
+ let existing = Buffer.alloc(0);
209
+ try {
210
+ const current = await this.readFile(path);
211
+ existing = Buffer.isBuffer(current) ? Buffer.from(current) : Buffer.from(current);
212
+ } catch (error) {
213
+ if (error instanceof FileNotFoundError) {} else throw error;
214
+ }
215
+ const appendBuffer = typeof content === "string" ? Buffer.from(content, "utf-8") : Buffer.from(content);
216
+ await this.writeFile(path, Buffer.concat([existing, appendBuffer]));
217
+ }
218
+ async deleteFile(path, options) {
219
+ this.assertWritable("deleteFile");
220
+ const agent = await this.getAgent();
221
+ const normalized = normalizePath(path);
222
+ try {
223
+ if ((await agent.fs.stat(normalized)).isDirectory()) throw new IsDirectoryError(normalized);
224
+ } catch (error) {
225
+ if (error instanceof IsDirectoryError) throw error;
226
+ if (hasCode(error, "ENOENT")) {
227
+ if (options?.force) return;
228
+ throw new FileNotFoundError(normalized);
229
+ }
230
+ throw mapError(error, normalized, "file");
231
+ }
232
+ try {
233
+ await agent.fs.unlink(normalized);
234
+ } catch (error) {
235
+ if (options?.force && hasCode(error, "ENOENT")) return;
236
+ throw mapError(error, normalized, "file");
237
+ }
238
+ }
239
+ async copyFile(src, dest, options) {
240
+ this.assertWritable("copyFile");
241
+ const agent = await this.getAgent();
242
+ const normalizedSrc = normalizePath(src);
243
+ const normalizedDest = normalizePath(dest);
244
+ if (options?.overwrite === false) try {
245
+ await agent.fs.access(normalizedDest);
246
+ throw new FileExistsError(normalizedDest);
247
+ } catch (error) {
248
+ if (error instanceof FileExistsError) throw error;
249
+ if (!hasCode(error, "ENOENT")) throw mapError(error, normalizedDest, "file");
250
+ }
251
+ try {
252
+ if ((await agent.fs.stat(normalizedSrc)).isDirectory()) {
253
+ if (!options?.recursive) throw new IsDirectoryError(normalizedSrc);
254
+ await this.copyDirRecursive(agent, normalizedSrc, normalizedDest, options);
255
+ return;
256
+ }
257
+ await this.mkdirRecursive(agent, getParentPath(normalizedDest));
258
+ await agent.fs.copyFile(normalizedSrc, normalizedDest);
259
+ } catch (error) {
260
+ if (error instanceof IsDirectoryError || error instanceof FileExistsError) throw error;
261
+ throw mapError(error, normalizedSrc, "file");
262
+ }
263
+ }
264
+ async moveFile(src, dest, options) {
265
+ this.assertWritable("moveFile");
266
+ const agent = await this.getAgent();
267
+ const normalizedSrc = normalizePath(src);
268
+ const normalizedDest = normalizePath(dest);
269
+ if (options?.overwrite === false) try {
270
+ await agent.fs.access(normalizedDest);
271
+ throw new FileExistsError(normalizedDest);
272
+ } catch (error) {
273
+ if (error instanceof FileExistsError) throw error;
274
+ if (!hasCode(error, "ENOENT")) throw mapError(error, normalizedDest, "file");
275
+ }
276
+ await this.mkdirRecursive(agent, getParentPath(normalizedDest));
277
+ try {
278
+ await agent.fs.rename(normalizedSrc, normalizedDest);
279
+ } catch (error) {
280
+ throw mapError(error, normalizedSrc, "file");
281
+ }
282
+ }
283
+ async mkdir(path, options) {
284
+ this.assertWritable("mkdir");
285
+ const agent = await this.getAgent();
286
+ const normalized = normalizePath(path);
287
+ if (options?.recursive !== false) {
288
+ await this.mkdirRecursive(agent, normalized);
289
+ return;
290
+ }
291
+ try {
292
+ await agent.fs.mkdir(normalized);
293
+ } catch (error) {
294
+ throw mapError(error, normalized, "directory");
295
+ }
296
+ }
297
+ async rmdir(path, options) {
298
+ this.assertWritable("rmdir");
299
+ const agent = await this.getAgent();
300
+ const normalized = normalizePath(path);
301
+ if (options?.recursive) {
302
+ try {
303
+ await agent.fs.rm(normalized, { recursive: true });
304
+ } catch (error) {
305
+ if (options?.force && hasCode(error, "ENOENT")) return;
306
+ throw mapError(error, normalized, "directory");
307
+ }
308
+ return;
309
+ }
310
+ try {
311
+ if ((await agent.fs.readdir(normalized)).length > 0) throw new DirectoryNotEmptyError(normalized);
312
+ await agent.fs.rmdir(normalized);
313
+ } catch (error) {
314
+ if (error instanceof DirectoryNotEmptyError) throw error;
315
+ if (options?.force && hasCode(error, "ENOENT")) return;
316
+ throw mapError(error, normalized, "directory");
317
+ }
318
+ }
319
+ async readdir(path, options) {
320
+ const agent = await this.getAgent();
321
+ const normalized = normalizePath(path);
322
+ try {
323
+ if (!(await agent.fs.stat(normalized)).isDirectory()) throw new NotDirectoryError(normalized);
324
+ let entries = (await agent.fs.readdirPlus(normalized)).map((entry) => ({
325
+ name: entry.name,
326
+ type: entry.stats.isDirectory() ? "directory" : "file",
327
+ size: entry.stats.size
328
+ }));
329
+ if (options?.extension) {
330
+ const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
331
+ entries = entries.filter((entry) => {
332
+ if (entry.type === "directory") return true;
333
+ return extensions.some((ext) => entry.name.endsWith(ext));
334
+ });
335
+ }
336
+ if (options?.recursive) {
337
+ const maxDepth = options.maxDepth ?? Infinity;
338
+ const subdirs = entries.filter((e) => e.type === "directory");
339
+ for (const dir of subdirs) if (maxDepth > 1) {
340
+ const subEntries = await this.readdir(joinPath(normalized, dir.name), {
341
+ ...options,
342
+ maxDepth: maxDepth - 1
343
+ });
344
+ for (const sub of subEntries) entries.push({
345
+ ...sub,
346
+ name: dir.name + "/" + sub.name
347
+ });
348
+ }
349
+ }
350
+ return entries;
351
+ } catch (error) {
352
+ if (error instanceof NotDirectoryError || error instanceof DirectoryNotFoundError) throw error;
353
+ throw mapError(error, normalized, "directory");
354
+ }
355
+ }
356
+ async exists(path) {
357
+ const agent = await this.getAgent();
358
+ const normalized = normalizePath(path);
359
+ try {
360
+ await agent.fs.access(normalized);
361
+ return true;
362
+ } catch {
363
+ return false;
364
+ }
365
+ }
366
+ async stat(path) {
367
+ const agent = await this.getAgent();
368
+ const normalized = normalizePath(path);
369
+ try {
370
+ const st = await agent.fs.stat(normalized);
371
+ return {
372
+ name: getBaseName(normalized) || "",
373
+ path: normalized,
374
+ type: st.isDirectory() ? "directory" : "file",
375
+ size: st.size,
376
+ createdAt: /* @__PURE__ */ new Date(st.ctime * 1e3),
377
+ modifiedAt: /* @__PURE__ */ new Date(st.mtime * 1e3)
378
+ };
379
+ } catch (error) {
380
+ throw mapError(error, normalized, "file");
381
+ }
382
+ }
383
+ /**
384
+ * Check if the path points to a file.
385
+ * Returns `false` if the path doesn't exist.
386
+ */
387
+ async isFile(path) {
388
+ try {
389
+ return (await this.stat(path)).type === "file";
390
+ } catch (error) {
391
+ if (error instanceof FileNotFoundError) return false;
392
+ throw error;
393
+ }
394
+ }
395
+ /**
396
+ * Check if the path points to a directory.
397
+ * Returns `false` if the path doesn't exist.
398
+ */
399
+ async isDirectory(path) {
400
+ try {
401
+ return (await this.stat(path)).type === "directory";
402
+ } catch (error) {
403
+ if (error instanceof FileNotFoundError) return false;
404
+ throw error;
405
+ }
406
+ }
407
+ /**
408
+ * Recursively create directories, ignoring EEXIST errors.
409
+ */
410
+ async mkdirRecursive(agent, path) {
411
+ if (path === "/") return;
412
+ const segments = path.split("/").filter(Boolean);
413
+ let current = "";
414
+ for (const segment of segments) {
415
+ current += "/" + segment;
416
+ try {
417
+ await agent.fs.mkdir(current);
418
+ } catch (error) {
419
+ if (!hasCode(error, "EEXIST")) throw mapError(error, current, "directory");
420
+ }
421
+ }
422
+ }
423
+ /**
424
+ * Recursively copy a directory tree.
425
+ */
426
+ async copyDirRecursive(agent, src, dest, options) {
427
+ await this.mkdirRecursive(agent, dest);
428
+ const entries = await agent.fs.readdirPlus(src);
429
+ for (const entry of entries) {
430
+ const srcChild = joinPath(src, entry.name);
431
+ const destChild = joinPath(dest, entry.name);
432
+ if (entry.stats.isDirectory()) await this.copyDirRecursive(agent, srcChild, destChild, options);
433
+ else {
434
+ if (options?.overwrite === false) try {
435
+ await agent.fs.access(destChild);
436
+ throw new FileExistsError(destChild);
437
+ } catch (error) {
438
+ if (error instanceof FileExistsError) throw error;
439
+ if (!hasCode(error, "ENOENT")) throw mapError(error, destChild, "file");
440
+ }
441
+ await agent.fs.copyFile(srcChild, destChild);
442
+ }
443
+ }
444
+ }
496
445
  };
497
-
498
- // src/provider.ts
499
- var agentfsFilesystemProvider = {
500
- id: "agentfs",
501
- name: "AgentFS",
502
- description: "Turso/SQLite-backed filesystem via AgentFS",
503
- configSchema: {
504
- type: "object",
505
- properties: {
506
- agentId: { type: "string", description: "Agent ID \u2014 creates database at .agentfs/<agentId>.db" },
507
- path: { type: "string", description: "Explicit database file path (alternative to agentId)" },
508
- readOnly: { type: "boolean", description: "Mount as read-only", default: false }
509
- }
510
- },
511
- createFilesystem: (config) => new AgentFSFilesystem(config)
446
+ //#endregion
447
+ //#region src/provider.ts
448
+ const agentfsFilesystemProvider = {
449
+ id: "agentfs",
450
+ name: "AgentFS",
451
+ description: "Turso/SQLite-backed filesystem via AgentFS",
452
+ configSchema: {
453
+ type: "object",
454
+ properties: {
455
+ agentId: {
456
+ type: "string",
457
+ description: "Agent ID — creates database at .agentfs/<agentId>.db"
458
+ },
459
+ path: {
460
+ type: "string",
461
+ description: "Explicit database file path (alternative to agentId)"
462
+ },
463
+ readOnly: {
464
+ type: "boolean",
465
+ description: "Mount as read-only",
466
+ default: false
467
+ }
468
+ }
469
+ },
470
+ createFilesystem: (config) => new AgentFSFilesystem(config)
512
471
  };
513
-
472
+ //#endregion
514
473
  export { AgentFSFilesystem, agentfsFilesystemProvider };
515
- //# sourceMappingURL=index.js.map
474
+
516
475
  //# sourceMappingURL=index.js.map