@mastra/files-sdk 0.3.0 → 0.3.1-alpha.0

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.cjs CHANGED
@@ -1,427 +1,457 @@
1
- 'use strict';
2
-
3
- var workspace = require('@mastra/core/workspace');
4
-
5
- // src/filesystem/index.ts
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _mastra_core_workspace = require("@mastra/core/workspace");
3
+ //#region src/filesystem/index.ts
4
+ /**
5
+ * Normalize a POSIX-style path to an object-storage key.
6
+ * Strips leading slashes, resolves `.`/`./` to empty string.
7
+ */
6
8
  function toKey(path) {
7
- let start = 0;
8
- while (start < path.length && path.charCodeAt(start) === 47) start++;
9
- let end = path.length;
10
- while (end > start && path.charCodeAt(end - 1) === 47) end--;
11
- const key = path.slice(start, end);
12
- if (key === "." || key === "./") return "";
13
- return key;
9
+ let start = 0;
10
+ while (start < path.length && path.charCodeAt(start) === 47) start++;
11
+ let end = path.length;
12
+ while (end > start && path.charCodeAt(end - 1) === 47) end--;
13
+ const key = path.slice(start, end);
14
+ if (key === "." || key === "./") return "";
15
+ return key;
14
16
  }
17
+ /**
18
+ * Extract the basename (last path segment) from a key.
19
+ */
15
20
  function basename(key) {
16
- const idx = key.lastIndexOf("/");
17
- return idx === -1 ? key : key.slice(idx + 1);
21
+ const idx = key.lastIndexOf("/");
22
+ return idx === -1 ? key : key.slice(idx + 1);
18
23
  }
24
+ /**
25
+ * Walk the FilesSDK error code chain.
26
+ *
27
+ * FilesSDK frequently wraps an inner `NotFound` / `Unauthorized` error in an
28
+ * outer `Provider` error (`error.cause` carries the original). Some adapters
29
+ * may wrap multiple levels deep, so we walk the cause chain and return any
30
+ * matching code found along the way.
31
+ */
19
32
  function hasFilesSDKCode(err, code, depth = 0) {
20
- if (depth > 5 || !err || typeof err !== "object") return false;
21
- const e = err;
22
- if (e.code === code) return true;
23
- if (e.cause) return hasFilesSDKCode(e.cause, code, depth + 1);
24
- return false;
33
+ if (depth > 5 || !err || typeof err !== "object") return false;
34
+ const e = err;
35
+ if (e.code === code) return true;
36
+ if (e.cause) return hasFilesSDKCode(e.cause, code, depth + 1);
37
+ return false;
25
38
  }
26
39
  function isNotFoundError(err) {
27
- return hasFilesSDKCode(err, "NotFound");
40
+ return hasFilesSDKCode(err, "NotFound");
28
41
  }
29
42
  function isUnauthorizedError(err) {
30
- return hasFilesSDKCode(err, "Unauthorized");
43
+ return hasFilesSDKCode(err, "Unauthorized");
31
44
  }
32
45
  function generateId() {
33
- return `files-sdk-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
46
+ return `files-sdk-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
34
47
  }
48
+ /**
49
+ * Convert FileContent (string | Buffer | Uint8Array) to a body acceptable by files-sdk.
50
+ */
35
51
  function toBody(content) {
36
- if (typeof content === "string") return content;
37
- if (Buffer.isBuffer(content)) return new Uint8Array(content);
38
- return content;
52
+ if (typeof content === "string") return content;
53
+ if (Buffer.isBuffer(content)) return new Uint8Array(content);
54
+ return content;
39
55
  }
40
- var MIME_TYPES = {
41
- ".txt": "text/plain",
42
- ".md": "text/markdown",
43
- ".markdown": "text/markdown",
44
- ".html": "text/html",
45
- ".htm": "text/html",
46
- ".css": "text/css",
47
- ".csv": "text/csv",
48
- ".xml": "text/xml",
49
- ".js": "text/javascript",
50
- ".mjs": "text/javascript",
51
- ".ts": "text/typescript",
52
- ".tsx": "text/typescript",
53
- ".jsx": "text/javascript",
54
- ".json": "application/json",
55
- ".yaml": "text/yaml",
56
- ".yml": "text/yaml",
57
- ".py": "text/x-python",
58
- ".rb": "text/x-ruby",
59
- ".sh": "text/x-shellscript",
60
- ".bash": "text/x-shellscript",
61
- ".png": "image/png",
62
- ".jpg": "image/jpeg",
63
- ".jpeg": "image/jpeg",
64
- ".gif": "image/gif",
65
- ".svg": "image/svg+xml",
66
- ".webp": "image/webp",
67
- ".ico": "image/x-icon",
68
- ".pdf": "application/pdf",
69
- ".zip": "application/zip",
70
- ".gz": "application/gzip",
71
- ".tar": "application/x-tar"
56
+ /**
57
+ * Infer MIME type from a file path extension.
58
+ */
59
+ const MIME_TYPES = {
60
+ ".txt": "text/plain",
61
+ ".md": "text/markdown",
62
+ ".markdown": "text/markdown",
63
+ ".html": "text/html",
64
+ ".htm": "text/html",
65
+ ".css": "text/css",
66
+ ".csv": "text/csv",
67
+ ".xml": "text/xml",
68
+ ".js": "text/javascript",
69
+ ".mjs": "text/javascript",
70
+ ".ts": "text/typescript",
71
+ ".tsx": "text/typescript",
72
+ ".jsx": "text/javascript",
73
+ ".json": "application/json",
74
+ ".yaml": "text/yaml",
75
+ ".yml": "text/yaml",
76
+ ".py": "text/x-python",
77
+ ".rb": "text/x-ruby",
78
+ ".sh": "text/x-shellscript",
79
+ ".bash": "text/x-shellscript",
80
+ ".png": "image/png",
81
+ ".jpg": "image/jpeg",
82
+ ".jpeg": "image/jpeg",
83
+ ".gif": "image/gif",
84
+ ".svg": "image/svg+xml",
85
+ ".webp": "image/webp",
86
+ ".ico": "image/x-icon",
87
+ ".pdf": "application/pdf",
88
+ ".zip": "application/zip",
89
+ ".gz": "application/gzip",
90
+ ".tar": "application/x-tar"
72
91
  };
73
92
  function getMimeType(path) {
74
- const dot = path.lastIndexOf(".");
75
- if (dot === -1) return "application/octet-stream";
76
- return MIME_TYPES[path.slice(dot).toLowerCase()] ?? "application/octet-stream";
93
+ const dot = path.lastIndexOf(".");
94
+ if (dot === -1) return "application/octet-stream";
95
+ return MIME_TYPES[path.slice(dot).toLowerCase()] ?? "application/octet-stream";
77
96
  }
78
- var FilesSDKFilesystem = class extends workspace.MastraFilesystem {
79
- id;
80
- name = "FilesSDKFilesystem";
81
- provider = "files-sdk";
82
- status = "pending";
83
- readOnly;
84
- icon;
85
- displayName;
86
- description;
87
- _files;
88
- constructor(options) {
89
- super({ name: "FilesSDKFilesystem", ...options });
90
- this._files = options.files;
91
- this.id = options.id ?? generateId();
92
- this.readOnly = options.readOnly;
93
- this.icon = options.icon;
94
- this.displayName = options.displayName;
95
- this.description = options.description;
96
- }
97
- /** The underlying FilesSDK instance, for escape-hatch access. */
98
- get files() {
99
- return this._files;
100
- }
101
- // ---------------------------------------------------------------------------
102
- // Lifecycle
103
- // ---------------------------------------------------------------------------
104
- async init() {
105
- try {
106
- await this._files.list({ limit: 1 });
107
- } catch (err) {
108
- if (isUnauthorizedError(err)) {
109
- throw new Error("Access denied \u2014 check credentials and storage permissions");
110
- }
111
- throw err;
112
- }
113
- }
114
- // destroy() — default no-op is fine; FilesSDK has no explicit teardown.
115
- getInfo() {
116
- return {
117
- id: this.id,
118
- name: this.name,
119
- provider: this.provider,
120
- status: this.status,
121
- error: this.error,
122
- readOnly: this.readOnly,
123
- icon: this.icon,
124
- metadata: {
125
- adapter: this._files.adapter?.name ?? "unknown"
126
- }
127
- };
128
- }
129
- getInstructions() {
130
- const adapterName = this._files.adapter?.name ?? "unknown";
131
- const parts = [`Unified storage via FilesSDK (${adapterName} adapter).`];
132
- if (this.readOnly) parts.push("Mounted read-only.");
133
- parts.push("Persistent storage \u2014 files are retained across sessions.");
134
- return parts.join(" ");
135
- }
136
- // ---------------------------------------------------------------------------
137
- // File operations
138
- // ---------------------------------------------------------------------------
139
- async readFile(path, options) {
140
- await this.ensureReady();
141
- const key = toKey(path);
142
- try {
143
- const file = await this._files.download(key);
144
- const buf = Buffer.from(await file.arrayBuffer());
145
- if (options?.encoding) return buf.toString(options.encoding);
146
- return buf;
147
- } catch (err) {
148
- if (isNotFoundError(err)) throw new workspace.FileNotFoundError(path);
149
- throw err;
150
- }
151
- }
152
- async writeFile(path, content, options) {
153
- await this.ensureReady();
154
- this.assertWritable("writeFile");
155
- const key = toKey(path);
156
- if (options?.overwrite === false && await this.isFile(key)) {
157
- throw new workspace.FileExistsError(path);
158
- }
159
- const body = toBody(content);
160
- await this._files.upload(key, body, {
161
- contentType: options?.mimeType ?? getMimeType(path)
162
- });
163
- }
164
- /**
165
- * Append content to a file.
166
- *
167
- * **Not atomic.** Object storage has no native append, so this is implemented
168
- * as a read-modify-write: the existing object is downloaded, the new content
169
- * is concatenated, and the result is uploaded as a new object. Concurrent
170
- * appends to the same key may overwrite each other. This limitation is
171
- * inherent to object storage, not specific to FilesSDK, and matches the
172
- * behavior of the sibling S3, GCS, and Azure workspace providers.
173
- */
174
- async appendFile(path, content) {
175
- await this.ensureReady();
176
- this.assertWritable("appendFile");
177
- const key = toKey(path);
178
- let existing = Buffer.alloc(0);
179
- try {
180
- const file = await this._files.download(key);
181
- existing = Buffer.from(await file.arrayBuffer());
182
- } catch (err) {
183
- if (!isNotFoundError(err)) throw err;
184
- }
185
- const append = typeof content === "string" ? Buffer.from(content) : toBody(content);
186
- const merged = Buffer.concat([existing, Buffer.isBuffer(append) ? append : Buffer.from(append)]);
187
- await this._files.upload(key, new Uint8Array(merged), {
188
- contentType: getMimeType(path)
189
- });
190
- }
191
- async deleteFile(path, options) {
192
- await this.ensureReady();
193
- this.assertWritable("deleteFile");
194
- const key = toKey(path);
195
- if (await this.isDirectory(key)) {
196
- await this.rmdir(path, { recursive: true, force: options?.force });
197
- return;
198
- }
199
- if (!options?.force && !await this.isFile(key)) {
200
- throw new workspace.FileNotFoundError(path);
201
- }
202
- try {
203
- await this._files.delete(key);
204
- } catch (err) {
205
- if (options?.force) return;
206
- if (isNotFoundError(err)) throw new workspace.FileNotFoundError(path);
207
- throw err;
208
- }
209
- }
210
- async copyFile(src, dest, options) {
211
- await this.ensureReady();
212
- this.assertWritable("copyFile");
213
- const fromKey = toKey(src);
214
- const toKey_ = toKey(dest);
215
- if (options?.overwrite === false && await this.isFile(toKey_)) {
216
- throw new workspace.FileExistsError(dest);
217
- }
218
- try {
219
- await this._files.copy(fromKey, toKey_);
220
- } catch (err) {
221
- if (isNotFoundError(err)) throw new workspace.FileNotFoundError(src);
222
- throw err;
223
- }
224
- }
225
- async moveFile(src, dest, options) {
226
- await this.copyFile(src, dest, options);
227
- await this.deleteFile(src, { force: true });
228
- }
229
- // ---------------------------------------------------------------------------
230
- // Directory operations
231
- // ---------------------------------------------------------------------------
232
- async mkdir(_path, _options) {
233
- await this.ensureReady();
234
- this.assertWritable("mkdir");
235
- }
236
- async rmdir(path, options) {
237
- await this.ensureReady();
238
- this.assertWritable("rmdir");
239
- const key = toKey(path);
240
- const prefix = key ? `${key}/` : "";
241
- const allKeys = [];
242
- let cursor;
243
- do {
244
- const result = await this._files.list({ prefix, cursor, limit: 1e3 });
245
- for (const item of result.items) {
246
- allKeys.push(item.key);
247
- }
248
- cursor = result.cursor;
249
- } while (cursor);
250
- if (allKeys.length === 0) return;
251
- if (!options?.recursive) {
252
- throw new workspace.DirectoryNotEmptyError(path);
253
- }
254
- await this._files.delete(allKeys);
255
- }
256
- async readdir(path, options) {
257
- await this.ensureReady();
258
- const key = toKey(path);
259
- const prefix = key ? `${key}/` : "";
260
- const entries = [];
261
- const seenDirs = /* @__PURE__ */ new Set();
262
- let cursor;
263
- const maxDepth = options?.maxDepth;
264
- const recursive = options?.recursive ?? false;
265
- const extensions = options?.extension ? Array.isArray(options.extension) ? options.extension : [options.extension] : void 0;
266
- do {
267
- const result = await this._files.list({ prefix, cursor, limit: 1e3 });
268
- for (const item of result.items) {
269
- const relativePath = prefix ? item.key.slice(prefix.length) : item.key;
270
- if (!relativePath) continue;
271
- const segments = relativePath.split("/");
272
- if (segments.length === 1) {
273
- const name = segments[0];
274
- if (extensions) {
275
- const ext = name.lastIndexOf(".") !== -1 ? name.slice(name.lastIndexOf(".")) : "";
276
- if (!extensions.includes(ext)) continue;
277
- }
278
- entries.push({
279
- name,
280
- type: "file",
281
- size: item.size
282
- });
283
- } else if (!recursive) {
284
- const dirName = segments[0];
285
- if (!seenDirs.has(dirName)) {
286
- seenDirs.add(dirName);
287
- entries.push({
288
- name: dirName,
289
- type: "directory"
290
- });
291
- }
292
- } else {
293
- for (let i = 1; i < segments.length; i++) {
294
- const dirPath = segments.slice(0, i).join("/");
295
- const dirDepth = i;
296
- if (maxDepth !== void 0 && dirDepth > maxDepth) continue;
297
- if (!seenDirs.has(dirPath)) {
298
- seenDirs.add(dirPath);
299
- entries.push({
300
- name: dirPath,
301
- type: "directory"
302
- });
303
- }
304
- }
305
- if (maxDepth !== void 0 && segments.length > maxDepth) continue;
306
- const name = relativePath;
307
- if (extensions) {
308
- const ext = name.lastIndexOf(".") !== -1 ? name.slice(name.lastIndexOf(".")) : "";
309
- if (!extensions.includes(ext)) continue;
310
- }
311
- entries.push({
312
- name,
313
- type: "file",
314
- size: item.size
315
- });
316
- }
317
- }
318
- cursor = result.cursor;
319
- } while (cursor);
320
- return entries;
321
- }
322
- // ---------------------------------------------------------------------------
323
- // Path operations
324
- // ---------------------------------------------------------------------------
325
- async exists(path) {
326
- await this.ensureReady();
327
- const key = toKey(path);
328
- if (!key) return true;
329
- if (await this.isDirectory(key)) return true;
330
- return this.isFile(key);
331
- }
332
- async stat(path) {
333
- await this.ensureReady();
334
- const key = toKey(path);
335
- if (!key) {
336
- const now = /* @__PURE__ */ new Date();
337
- return {
338
- name: "/",
339
- path: "/",
340
- type: "directory",
341
- size: 0,
342
- createdAt: now,
343
- modifiedAt: now
344
- };
345
- }
346
- try {
347
- const file = await this._files.head(key);
348
- return this.storedFileToStat(file, path);
349
- } catch (err) {
350
- if (!isNotFoundError(err)) throw err;
351
- }
352
- if (await this.isDirectory(key)) {
353
- const now = /* @__PURE__ */ new Date();
354
- return {
355
- name: basename(key),
356
- path,
357
- type: "directory",
358
- size: 0,
359
- createdAt: now,
360
- modifiedAt: now
361
- };
362
- }
363
- throw new workspace.FileNotFoundError(path);
364
- }
365
- // ---------------------------------------------------------------------------
366
- // Private helpers
367
- // ---------------------------------------------------------------------------
368
- assertWritable(operation) {
369
- if (this.readOnly) {
370
- throw new workspace.WorkspaceReadOnlyError(operation);
371
- }
372
- }
373
- /** Check if a key prefix has any children (i.e. acts like a directory). */
374
- async isDirectory(key) {
375
- if (!key) return true;
376
- const prefix = `${key}/`;
377
- const result = await this._files.list({ prefix, limit: 1 });
378
- return result.items.length > 0;
379
- }
380
- /**
381
- * Check whether `key` refers to a real stored file (not an empty leftover
382
- * directory). Uses prefix listing constrained to the exact key, which only
383
- * matches actually-stored objects across all adapters.
384
- */
385
- async isFile(key) {
386
- if (!key) return false;
387
- const result = await this._files.list({ prefix: key, limit: 10 });
388
- return result.items.some((item) => item.key === key);
389
- }
390
- /** Convert a FilesSDK StoredFile to a Mastra FileStat. */
391
- storedFileToStat(file, path) {
392
- return {
393
- name: basename(file.key ?? path),
394
- path,
395
- type: "file",
396
- size: file.size ?? 0,
397
- createdAt: file.lastModified ? new Date(file.lastModified) : /* @__PURE__ */ new Date(),
398
- modifiedAt: file.lastModified ? new Date(file.lastModified) : /* @__PURE__ */ new Date(),
399
- mimeType: file.type || void 0
400
- };
401
- }
97
+ /**
98
+ * Workspace filesystem adapter backed by [FilesSDK](https://files-sdk.dev).
99
+ *
100
+ * Accepts a pre-configured `Files` instance so users choose their own adapter
101
+ * (S3, R2, GCS, Azure, local fs, etc.) and this class bridges it to the
102
+ * Mastra `WorkspaceFilesystem` interface.
103
+ *
104
+ * Object-storage semantics are bridged to the POSIX-like interface:
105
+ * - `mkdir` is a no-op (directories don't exist in object storage)
106
+ * - `readdir` uses `list()` with prefix filtering to synthesize directory entries
107
+ * - `rmdir` lists all keys under a prefix and batch-deletes them
108
+ */
109
+ var FilesSDKFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
110
+ id;
111
+ name = "FilesSDKFilesystem";
112
+ provider = "files-sdk";
113
+ status = "pending";
114
+ readOnly;
115
+ icon;
116
+ displayName;
117
+ description;
118
+ _files;
119
+ constructor(options) {
120
+ super({
121
+ name: "FilesSDKFilesystem",
122
+ ...options
123
+ });
124
+ this._files = options.files;
125
+ this.id = options.id ?? generateId();
126
+ this.readOnly = options.readOnly;
127
+ this.icon = options.icon;
128
+ this.displayName = options.displayName;
129
+ this.description = options.description;
130
+ }
131
+ /** The underlying FilesSDK instance, for escape-hatch access. */
132
+ get files() {
133
+ return this._files;
134
+ }
135
+ async init() {
136
+ try {
137
+ await this._files.list({ limit: 1 });
138
+ } catch (err) {
139
+ if (isUnauthorizedError(err)) throw new Error("Access denied — check credentials and storage permissions");
140
+ throw err;
141
+ }
142
+ }
143
+ getInfo() {
144
+ return {
145
+ id: this.id,
146
+ name: this.name,
147
+ provider: this.provider,
148
+ status: this.status,
149
+ error: this.error,
150
+ readOnly: this.readOnly,
151
+ icon: this.icon,
152
+ metadata: { adapter: this._files.adapter?.name ?? "unknown" }
153
+ };
154
+ }
155
+ getInstructions() {
156
+ const parts = [`Unified storage via FilesSDK (${this._files.adapter?.name ?? "unknown"} adapter).`];
157
+ if (this.readOnly) parts.push("Mounted read-only.");
158
+ parts.push("Persistent storage — files are retained across sessions.");
159
+ return parts.join(" ");
160
+ }
161
+ async readFile(path, options) {
162
+ await this.ensureReady();
163
+ const key = toKey(path);
164
+ try {
165
+ const file = await this._files.download(key);
166
+ const buf = Buffer.from(await file.arrayBuffer());
167
+ if (options?.encoding) return buf.toString(options.encoding);
168
+ return buf;
169
+ } catch (err) {
170
+ if (isNotFoundError(err)) throw new _mastra_core_workspace.FileNotFoundError(path);
171
+ throw err;
172
+ }
173
+ }
174
+ async writeFile(path, content, options) {
175
+ await this.ensureReady();
176
+ this.assertWritable("writeFile");
177
+ const key = toKey(path);
178
+ if (options?.overwrite === false && await this.isFile(key)) throw new _mastra_core_workspace.FileExistsError(path);
179
+ const body = toBody(content);
180
+ await this._files.upload(key, body, { contentType: options?.mimeType ?? getMimeType(path) });
181
+ }
182
+ /**
183
+ * Append content to a file.
184
+ *
185
+ * **Not atomic.** Object storage has no native append, so this is implemented
186
+ * as a read-modify-write: the existing object is downloaded, the new content
187
+ * is concatenated, and the result is uploaded as a new object. Concurrent
188
+ * appends to the same key may overwrite each other. This limitation is
189
+ * inherent to object storage, not specific to FilesSDK, and matches the
190
+ * behavior of the sibling S3, GCS, and Azure workspace providers.
191
+ */
192
+ async appendFile(path, content) {
193
+ await this.ensureReady();
194
+ this.assertWritable("appendFile");
195
+ const key = toKey(path);
196
+ let existing = Buffer.alloc(0);
197
+ try {
198
+ const file = await this._files.download(key);
199
+ existing = Buffer.from(await file.arrayBuffer());
200
+ } catch (err) {
201
+ if (!isNotFoundError(err)) throw err;
202
+ }
203
+ const append = typeof content === "string" ? Buffer.from(content) : toBody(content);
204
+ const merged = Buffer.concat([existing, Buffer.isBuffer(append) ? append : Buffer.from(append)]);
205
+ await this._files.upload(key, new Uint8Array(merged), { contentType: getMimeType(path) });
206
+ }
207
+ async deleteFile(path, options) {
208
+ await this.ensureReady();
209
+ this.assertWritable("deleteFile");
210
+ const key = toKey(path);
211
+ if (await this.isDirectory(key)) {
212
+ await this.rmdir(path, {
213
+ recursive: true,
214
+ force: options?.force
215
+ });
216
+ return;
217
+ }
218
+ if (!options?.force && !await this.isFile(key)) throw new _mastra_core_workspace.FileNotFoundError(path);
219
+ try {
220
+ await this._files.delete(key);
221
+ } catch (err) {
222
+ if (options?.force) return;
223
+ if (isNotFoundError(err)) throw new _mastra_core_workspace.FileNotFoundError(path);
224
+ throw err;
225
+ }
226
+ }
227
+ async copyFile(src, dest, options) {
228
+ await this.ensureReady();
229
+ this.assertWritable("copyFile");
230
+ const fromKey = toKey(src);
231
+ const toKey_ = toKey(dest);
232
+ if (options?.overwrite === false && await this.isFile(toKey_)) throw new _mastra_core_workspace.FileExistsError(dest);
233
+ try {
234
+ await this._files.copy(fromKey, toKey_);
235
+ } catch (err) {
236
+ if (isNotFoundError(err)) throw new _mastra_core_workspace.FileNotFoundError(src);
237
+ throw err;
238
+ }
239
+ }
240
+ async moveFile(src, dest, options) {
241
+ await this.copyFile(src, dest, options);
242
+ await this.deleteFile(src, { force: true });
243
+ }
244
+ async mkdir(_path, _options) {
245
+ await this.ensureReady();
246
+ this.assertWritable("mkdir");
247
+ }
248
+ async rmdir(path, options) {
249
+ await this.ensureReady();
250
+ this.assertWritable("rmdir");
251
+ const key = toKey(path);
252
+ const prefix = key ? `${key}/` : "";
253
+ const allKeys = [];
254
+ let cursor;
255
+ do {
256
+ const result = await this._files.list({
257
+ prefix,
258
+ cursor,
259
+ limit: 1e3
260
+ });
261
+ for (const item of result.items) allKeys.push(item.key);
262
+ cursor = result.cursor;
263
+ } while (cursor);
264
+ if (allKeys.length === 0) return;
265
+ if (!options?.recursive) throw new _mastra_core_workspace.DirectoryNotEmptyError(path);
266
+ await this._files.delete(allKeys);
267
+ }
268
+ async readdir(path, options) {
269
+ await this.ensureReady();
270
+ const key = toKey(path);
271
+ const prefix = key ? `${key}/` : "";
272
+ const entries = [];
273
+ const seenDirs = /* @__PURE__ */ new Set();
274
+ let cursor;
275
+ const maxDepth = options?.maxDepth;
276
+ const recursive = options?.recursive ?? false;
277
+ const extensions = options?.extension ? Array.isArray(options.extension) ? options.extension : [options.extension] : void 0;
278
+ do {
279
+ const result = await this._files.list({
280
+ prefix,
281
+ cursor,
282
+ limit: 1e3
283
+ });
284
+ for (const item of result.items) {
285
+ const relativePath = prefix ? item.key.slice(prefix.length) : item.key;
286
+ if (!relativePath) continue;
287
+ const segments = relativePath.split("/");
288
+ if (segments.length === 1) {
289
+ const name = segments[0];
290
+ if (extensions) {
291
+ const ext = name.lastIndexOf(".") !== -1 ? name.slice(name.lastIndexOf(".")) : "";
292
+ if (!extensions.includes(ext)) continue;
293
+ }
294
+ entries.push({
295
+ name,
296
+ type: "file",
297
+ size: item.size
298
+ });
299
+ } else if (!recursive) {
300
+ const dirName = segments[0];
301
+ if (!seenDirs.has(dirName)) {
302
+ seenDirs.add(dirName);
303
+ entries.push({
304
+ name: dirName,
305
+ type: "directory"
306
+ });
307
+ }
308
+ } else {
309
+ for (let i = 1; i < segments.length; i++) {
310
+ const dirPath = segments.slice(0, i).join("/");
311
+ if (maxDepth !== void 0 && i > maxDepth) continue;
312
+ if (!seenDirs.has(dirPath)) {
313
+ seenDirs.add(dirPath);
314
+ entries.push({
315
+ name: dirPath,
316
+ type: "directory"
317
+ });
318
+ }
319
+ }
320
+ if (maxDepth !== void 0 && segments.length > maxDepth) continue;
321
+ const name = relativePath;
322
+ if (extensions) {
323
+ const ext = name.lastIndexOf(".") !== -1 ? name.slice(name.lastIndexOf(".")) : "";
324
+ if (!extensions.includes(ext)) continue;
325
+ }
326
+ entries.push({
327
+ name,
328
+ type: "file",
329
+ size: item.size
330
+ });
331
+ }
332
+ }
333
+ cursor = result.cursor;
334
+ } while (cursor);
335
+ return entries;
336
+ }
337
+ async exists(path) {
338
+ await this.ensureReady();
339
+ const key = toKey(path);
340
+ if (!key) return true;
341
+ if (await this.isDirectory(key)) return true;
342
+ return this.isFile(key);
343
+ }
344
+ async stat(path) {
345
+ await this.ensureReady();
346
+ const key = toKey(path);
347
+ if (!key) {
348
+ const now = /* @__PURE__ */ new Date();
349
+ return {
350
+ name: "/",
351
+ path: "/",
352
+ type: "directory",
353
+ size: 0,
354
+ createdAt: now,
355
+ modifiedAt: now
356
+ };
357
+ }
358
+ try {
359
+ const file = await this._files.head(key);
360
+ return this.storedFileToStat(file, path);
361
+ } catch (err) {
362
+ if (!isNotFoundError(err)) throw err;
363
+ }
364
+ if (await this.isDirectory(key)) {
365
+ const now = /* @__PURE__ */ new Date();
366
+ return {
367
+ name: basename(key),
368
+ path,
369
+ type: "directory",
370
+ size: 0,
371
+ createdAt: now,
372
+ modifiedAt: now
373
+ };
374
+ }
375
+ throw new _mastra_core_workspace.FileNotFoundError(path);
376
+ }
377
+ assertWritable(operation) {
378
+ if (this.readOnly) throw new _mastra_core_workspace.WorkspaceReadOnlyError(operation);
379
+ }
380
+ /** Check if a key prefix has any children (i.e. acts like a directory). */
381
+ async isDirectory(key) {
382
+ if (!key) return true;
383
+ const prefix = `${key}/`;
384
+ return (await this._files.list({
385
+ prefix,
386
+ limit: 1
387
+ })).items.length > 0;
388
+ }
389
+ /**
390
+ * Check whether `key` refers to a real stored file (not an empty leftover
391
+ * directory). Uses prefix listing constrained to the exact key, which only
392
+ * matches actually-stored objects across all adapters.
393
+ */
394
+ async isFile(key) {
395
+ if (!key) return false;
396
+ return (await this._files.list({
397
+ prefix: key,
398
+ limit: 10
399
+ })).items.some((item) => item.key === key);
400
+ }
401
+ /** Convert a FilesSDK StoredFile to a Mastra FileStat. */
402
+ storedFileToStat(file, path) {
403
+ return {
404
+ name: basename(file.key ?? path),
405
+ path,
406
+ type: "file",
407
+ size: file.size ?? 0,
408
+ createdAt: file.lastModified ? new Date(file.lastModified) : /* @__PURE__ */ new Date(),
409
+ modifiedAt: file.lastModified ? new Date(file.lastModified) : /* @__PURE__ */ new Date(),
410
+ mimeType: file.type || void 0
411
+ };
412
+ }
402
413
  };
403
-
404
- // src/provider.ts
405
- var filesSDKFilesystemProvider = {
406
- id: "files-sdk",
407
- name: "FilesSDK (Unified Storage)",
408
- description: "Unified storage via FilesSDK \u2014 supports S3, R2, GCS, Azure, Vercel Blob, local filesystem, and more",
409
- configSchema: {
410
- type: "object",
411
- required: ["files"],
412
- properties: {
413
- files: { type: "object", description: "Pre-configured FilesSDK Files instance" },
414
- id: { type: "string", description: "Unique filesystem ID" },
415
- displayName: { type: "string", description: "Human-friendly display name" },
416
- icon: { type: "string", description: "Icon identifier for UI" },
417
- description: { type: "string", description: "Description for UI" },
418
- readOnly: { type: "boolean", description: "Mount as read-only", default: false }
419
- }
420
- },
421
- createFilesystem: (config) => new FilesSDKFilesystem(config)
414
+ //#endregion
415
+ //#region src/provider.ts
416
+ const filesSDKFilesystemProvider = {
417
+ id: "files-sdk",
418
+ name: "FilesSDK (Unified Storage)",
419
+ description: "Unified storage via FilesSDK — supports S3, R2, GCS, Azure, Vercel Blob, local filesystem, and more",
420
+ configSchema: {
421
+ type: "object",
422
+ required: ["files"],
423
+ properties: {
424
+ files: {
425
+ type: "object",
426
+ description: "Pre-configured FilesSDK Files instance"
427
+ },
428
+ id: {
429
+ type: "string",
430
+ description: "Unique filesystem ID"
431
+ },
432
+ displayName: {
433
+ type: "string",
434
+ description: "Human-friendly display name"
435
+ },
436
+ icon: {
437
+ type: "string",
438
+ description: "Icon identifier for UI"
439
+ },
440
+ description: {
441
+ type: "string",
442
+ description: "Description for UI"
443
+ },
444
+ readOnly: {
445
+ type: "boolean",
446
+ description: "Mount as read-only",
447
+ default: false
448
+ }
449
+ }
450
+ },
451
+ createFilesystem: (config) => new FilesSDKFilesystem(config)
422
452
  };
423
-
453
+ //#endregion
424
454
  exports.FilesSDKFilesystem = FilesSDKFilesystem;
425
455
  exports.filesSDKFilesystemProvider = filesSDKFilesystemProvider;
426
- //# sourceMappingURL=index.cjs.map
456
+
427
457
  //# sourceMappingURL=index.cjs.map