@mastra/gcs 0.3.1 → 0.3.2

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,496 +1,529 @@
1
- 'use strict';
2
-
3
- var storage = require('@google-cloud/storage');
4
- var workspace = require('@mastra/core/workspace');
5
-
6
- // src/filesystem/index.ts
7
- var MIME_TYPES = {
8
- // Text
9
- ".txt": "text/plain",
10
- ".md": "text/markdown",
11
- ".markdown": "text/markdown",
12
- ".html": "text/html",
13
- ".htm": "text/html",
14
- ".css": "text/css",
15
- ".csv": "text/csv",
16
- ".xml": "text/xml",
17
- // Code
18
- ".js": "text/javascript",
19
- ".mjs": "text/javascript",
20
- ".ts": "text/typescript",
21
- ".tsx": "text/typescript",
22
- ".jsx": "text/javascript",
23
- ".json": "application/json",
24
- ".yaml": "text/yaml",
25
- ".yml": "text/yaml",
26
- ".py": "text/x-python",
27
- ".rb": "text/x-ruby",
28
- ".sh": "text/x-shellscript",
29
- ".bash": "text/x-shellscript",
30
- // Images
31
- ".png": "image/png",
32
- ".jpg": "image/jpeg",
33
- ".jpeg": "image/jpeg",
34
- ".gif": "image/gif",
35
- ".svg": "image/svg+xml",
36
- ".webp": "image/webp",
37
- ".ico": "image/x-icon",
38
- // Documents
39
- ".pdf": "application/pdf",
40
- // Archives
41
- ".zip": "application/zip",
42
- ".gz": "application/gzip",
43
- ".tar": "application/x-tar"
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _google_cloud_storage = require("@google-cloud/storage");
3
+ let _mastra_core_workspace = require("@mastra/core/workspace");
4
+ //#region src/filesystem/index.ts
5
+ /**
6
+ * GCS Filesystem Provider
7
+ *
8
+ * A filesystem implementation backed by Google Cloud Storage.
9
+ */
10
+ /**
11
+ * Common MIME types by file extension.
12
+ */
13
+ const MIME_TYPES = {
14
+ ".txt": "text/plain",
15
+ ".md": "text/markdown",
16
+ ".markdown": "text/markdown",
17
+ ".html": "text/html",
18
+ ".htm": "text/html",
19
+ ".css": "text/css",
20
+ ".csv": "text/csv",
21
+ ".xml": "text/xml",
22
+ ".js": "text/javascript",
23
+ ".mjs": "text/javascript",
24
+ ".ts": "text/typescript",
25
+ ".tsx": "text/typescript",
26
+ ".jsx": "text/javascript",
27
+ ".json": "application/json",
28
+ ".yaml": "text/yaml",
29
+ ".yml": "text/yaml",
30
+ ".py": "text/x-python",
31
+ ".rb": "text/x-ruby",
32
+ ".sh": "text/x-shellscript",
33
+ ".bash": "text/x-shellscript",
34
+ ".png": "image/png",
35
+ ".jpg": "image/jpeg",
36
+ ".jpeg": "image/jpeg",
37
+ ".gif": "image/gif",
38
+ ".svg": "image/svg+xml",
39
+ ".webp": "image/webp",
40
+ ".ico": "image/x-icon",
41
+ ".pdf": "application/pdf",
42
+ ".zip": "application/zip",
43
+ ".gz": "application/gzip",
44
+ ".tar": "application/x-tar"
44
45
  };
46
+ /**
47
+ * Get MIME type from file path extension.
48
+ */
45
49
  function getMimeType(path) {
46
- const ext = path.toLowerCase().match(/\.[^.]+$/)?.[0];
47
- return ext ? MIME_TYPES[ext] ?? "application/octet-stream" : "application/octet-stream";
50
+ const ext = path.toLowerCase().match(/\.[^.]+$/)?.[0];
51
+ return ext ? MIME_TYPES[ext] ?? "application/octet-stream" : "application/octet-stream";
48
52
  }
53
+ /**
54
+ * GCS filesystem implementation.
55
+ *
56
+ * Stores files in a Google Cloud Storage bucket.
57
+ * Supports mounting into E2B sandboxes via gcsfuse.
58
+ *
59
+ * @example Using Application Default Credentials
60
+ * ```typescript
61
+ * import { GCSFilesystem } from '@mastra/gcs';
62
+ *
63
+ * // Uses ADC (gcloud auth application-default login)
64
+ * const fs = new GCSFilesystem({
65
+ * bucket: 'my-bucket',
66
+ * projectId: 'my-project',
67
+ * });
68
+ * ```
69
+ *
70
+ * @example Using Service Account Key
71
+ * ```typescript
72
+ * import { GCSFilesystem } from '@mastra/gcs';
73
+ *
74
+ * const fs = new GCSFilesystem({
75
+ * bucket: 'my-bucket',
76
+ * projectId: 'my-project',
77
+ * credentials: {
78
+ * type: 'service_account',
79
+ * project_id: 'my-project',
80
+ * private_key_id: '...',
81
+ * private_key: '-----BEGIN PRIVATE KEY-----\n...',
82
+ * client_email: '...@...iam.gserviceaccount.com',
83
+ * // ... rest of service account key
84
+ * },
85
+ * });
86
+ * ```
87
+ *
88
+ * @example Using Key File Path
89
+ * ```typescript
90
+ * import { GCSFilesystem } from '@mastra/gcs';
91
+ *
92
+ * const fs = new GCSFilesystem({
93
+ * bucket: 'my-bucket',
94
+ * projectId: 'my-project',
95
+ * credentials: '/path/to/service-account-key.json',
96
+ * });
97
+ * ```
98
+ */
99
+ /** Trim leading and trailing slashes without regex (avoids polynomial regex on user input). */
49
100
  function trimSlashes(s) {
50
- let start = 0;
51
- let end = s.length;
52
- while (start < end && s[start] === "/") start++;
53
- while (end > start && s[end - 1] === "/") end--;
54
- return s.slice(start, end);
101
+ let start = 0;
102
+ let end = s.length;
103
+ while (start < end && s[start] === "/") start++;
104
+ while (end > start && s[end - 1] === "/") end--;
105
+ return s.slice(start, end);
55
106
  }
56
- var GCSFilesystem = class extends workspace.MastraFilesystem {
57
- id;
58
- name = "GCSFilesystem";
59
- provider = "gcs";
60
- readOnly;
61
- status = "pending";
62
- // Display metadata for UI
63
- displayName;
64
- icon = "gcs";
65
- description;
66
- bucketName;
67
- projectId;
68
- credentials;
69
- prefix;
70
- endpoint;
71
- _storage = null;
72
- _bucket = null;
73
- constructor(options) {
74
- super({ ...options, name: "GCSFilesystem" });
75
- this.id = options.id ?? `gcs-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
76
- this.bucketName = options.bucket;
77
- this.projectId = options.projectId;
78
- this.credentials = options.credentials;
79
- this.prefix = options.prefix ? trimSlashes(options.prefix) + "/" : "";
80
- this.endpoint = options.endpoint;
81
- this.displayName = options.displayName ?? "Google Cloud Storage";
82
- this.icon = options.icon ?? "gcs";
83
- this.description = options.description;
84
- this.readOnly = options.readOnly;
85
- }
86
- /**
87
- * Get the underlying Google Cloud Storage instance for direct access to GCS APIs.
88
- *
89
- * Use this when you need to access GCS features not exposed through the
90
- * WorkspaceFilesystem interface (e.g., signed URLs, IAM, custom metadata, etc.).
91
- *
92
- * @example Access other buckets
93
- * ```typescript
94
- * const storage = fs.storage;
95
- * const [buckets] = await storage.getBuckets();
96
- * ```
97
- */
98
- get storage() {
99
- return this.getStorage();
100
- }
101
- /**
102
- * Get the underlying GCS Bucket instance for direct access to bucket operations.
103
- *
104
- * Use this when you need to access bucket features not exposed through the
105
- * WorkspaceFilesystem interface (e.g., signed URLs, lifecycle rules, etc.).
106
- *
107
- * @example Generate a signed URL
108
- * ```typescript
109
- * const bucket = fs.bucket;
110
- * const [url] = await bucket.file('my-file.txt').getSignedUrl({
111
- * action: 'read',
112
- * expires: Date.now() + 15 * 60 * 1000,
113
- * });
114
- * ```
115
- */
116
- get bucket() {
117
- return this.getBucket();
118
- }
119
- /**
120
- * Get mount configuration for E2B sandbox.
121
- * Returns GCS-compatible config that works with gcsfuse.
122
- */
123
- getMountConfig() {
124
- const config = {
125
- type: "gcs",
126
- bucket: this.bucketName
127
- };
128
- if (this.credentials && typeof this.credentials === "object") {
129
- config.serviceAccountKey = JSON.stringify(this.credentials);
130
- }
131
- if (this.prefix) {
132
- config.prefix = this.prefix.replace(/\/$/, "");
133
- }
134
- return config;
135
- }
136
- /**
137
- * Get filesystem info for status reporting.
138
- */
139
- getInfo() {
140
- return {
141
- id: this.id,
142
- name: this.name,
143
- provider: this.provider,
144
- status: this.status,
145
- error: this.error,
146
- readOnly: this.readOnly,
147
- icon: this.icon,
148
- metadata: {
149
- bucket: this.bucketName,
150
- ...this.endpoint && { endpoint: this.endpoint },
151
- ...this.prefix && { prefix: this.prefix }
152
- }
153
- };
154
- }
155
- /**
156
- * Get instructions describing this GCS filesystem.
157
- * Used by agents to understand storage semantics.
158
- */
159
- getInstructions() {
160
- const access = this.readOnly ? "Read-only" : "Persistent";
161
- return `Google Cloud Storage in bucket "${this.bucketName}". ${access} storage - files are retained across sessions.`;
162
- }
163
- getStorage() {
164
- if (this._storage) return this._storage;
165
- const options = {};
166
- if (this.projectId) {
167
- options.projectId = this.projectId;
168
- }
169
- if (this.credentials) {
170
- if (typeof this.credentials === "string") {
171
- options.keyFilename = this.credentials;
172
- } else {
173
- options.credentials = this.credentials;
174
- }
175
- }
176
- if (this.endpoint) {
177
- options.apiEndpoint = this.endpoint;
178
- }
179
- this._storage = new storage.Storage(options);
180
- return this._storage;
181
- }
182
- getBucket() {
183
- if (this._bucket) return this._bucket;
184
- const storage = this.getStorage();
185
- this._bucket = storage.bucket(this.bucketName);
186
- return this._bucket;
187
- }
188
- /**
189
- * Ensure the filesystem is initialized and return the bucket.
190
- * Uses base class ensureReady() for status management, then returns bucket.
191
- */
192
- async getReadyBucket() {
193
- await this.ensureReady();
194
- return this.getBucket();
195
- }
196
- toKey(path) {
197
- const cleanPath = path.replace(/^\/+/, "").replace(/^\.(?:\/|$)/, "");
198
- return this.prefix + cleanPath;
199
- }
200
- // ---------------------------------------------------------------------------
201
- // File Operations
202
- // ---------------------------------------------------------------------------
203
- async readFile(path, options) {
204
- const bucket = await this.getReadyBucket();
205
- const file = bucket.file(this.toKey(path));
206
- try {
207
- const [content] = await file.download();
208
- if (options?.encoding) {
209
- return content.toString(options.encoding);
210
- }
211
- return content;
212
- } catch (error) {
213
- if (error && typeof error === "object" && "code" in error && error.code === 404) {
214
- throw new workspace.FileNotFoundError(path);
215
- }
216
- throw error;
217
- }
218
- }
219
- async writeFile(path, content, options) {
220
- const bucket = await this.getReadyBucket();
221
- const file = bucket.file(this.toKey(path));
222
- if (options?.overwrite === false && await this.exists(path)) {
223
- throw new workspace.FileExistsError(path);
224
- }
225
- const body = typeof content === "string" ? Buffer.from(content, "utf-8") : Buffer.from(content);
226
- const contentType = getMimeType(path);
227
- await file.save(body, {
228
- contentType,
229
- resumable: false
230
- });
231
- }
232
- async appendFile(path, content) {
233
- let existing = "";
234
- try {
235
- existing = await this.readFile(path, { encoding: "utf-8" });
236
- } catch (error) {
237
- if (error instanceof workspace.FileNotFoundError) ; else {
238
- throw error;
239
- }
240
- }
241
- const appendContent = typeof content === "string" ? content : Buffer.from(content).toString("utf-8");
242
- await this.writeFile(path, existing + appendContent);
243
- }
244
- async deleteFile(path, options) {
245
- const isDir = await this.isDirectory(path);
246
- if (isDir) {
247
- await this.rmdir(path, { recursive: true, force: options?.force });
248
- return;
249
- }
250
- const bucket = await this.getReadyBucket();
251
- const file = bucket.file(this.toKey(path));
252
- try {
253
- await file.delete();
254
- } catch (error) {
255
- if (!options?.force) {
256
- if (error && typeof error === "object" && "code" in error && error.code === 404) {
257
- throw new workspace.FileNotFoundError(path);
258
- }
259
- throw error;
260
- }
261
- }
262
- }
263
- async copyFile(src, dest, options) {
264
- const bucket = await this.getReadyBucket();
265
- const srcFile = bucket.file(this.toKey(src));
266
- const destFile = bucket.file(this.toKey(dest));
267
- if (options?.overwrite === false && await this.exists(dest)) {
268
- throw new workspace.FileExistsError(dest);
269
- }
270
- try {
271
- await srcFile.copy(destFile);
272
- } catch (error) {
273
- if (error && typeof error === "object" && "code" in error && error.code === 404) {
274
- throw new workspace.FileNotFoundError(src);
275
- }
276
- throw error;
277
- }
278
- }
279
- async moveFile(src, dest, options) {
280
- await this.copyFile(src, dest, options);
281
- await this.deleteFile(src, { force: true });
282
- }
283
- // ---------------------------------------------------------------------------
284
- // Directory Operations
285
- // ---------------------------------------------------------------------------
286
- async mkdir(_path, _options) {
287
- }
288
- async rmdir(path, options) {
289
- if (!options?.recursive) {
290
- const bucket2 = await this.getReadyBucket();
291
- const prefix2 = this.toKey(path).replace(/\/$/, "") + "/";
292
- const [files] = await bucket2.getFiles({ prefix: prefix2, maxResults: 1 });
293
- if (files.length > 0) {
294
- throw new Error(`Directory not empty: ${path}`);
295
- }
296
- return;
297
- }
298
- const bucket = await this.getReadyBucket();
299
- const prefix = this.toKey(path).replace(/\/$/, "") + "/";
300
- await bucket.deleteFiles({ prefix });
301
- }
302
- async readdir(path, options) {
303
- const bucket = await this.getReadyBucket();
304
- const prefix = this.toKey(path).replace(/\/$/, "");
305
- const searchPrefix = prefix ? prefix + "/" : "";
306
- const entries = [];
307
- const seenDirs = /* @__PURE__ */ new Set();
308
- const [files] = await bucket.getFiles({
309
- prefix: searchPrefix,
310
- autoPaginate: true
311
- });
312
- for (const file of files) {
313
- const key = file.name;
314
- if (!key || key === searchPrefix) continue;
315
- const relativePath = key.slice(searchPrefix.length);
316
- if (!relativePath) continue;
317
- if (relativePath.endsWith("/")) {
318
- const dirName = relativePath.slice(0, -1);
319
- if (!seenDirs.has(dirName)) {
320
- seenDirs.add(dirName);
321
- entries.push({ name: dirName, type: "directory" });
322
- }
323
- continue;
324
- }
325
- const name = options?.recursive ? relativePath : relativePath.split("/")[0];
326
- if (!name) continue;
327
- if (!options?.recursive && relativePath.includes("/")) {
328
- if (!seenDirs.has(name)) {
329
- seenDirs.add(name);
330
- entries.push({ name, type: "directory" });
331
- }
332
- continue;
333
- }
334
- if (options?.extension) {
335
- const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
336
- if (!extensions.some((ext) => name.endsWith(ext))) {
337
- continue;
338
- }
339
- }
340
- entries.push({
341
- name,
342
- type: "file",
343
- size: file.metadata.size != null ? Number(file.metadata.size) : void 0
344
- });
345
- }
346
- return entries;
347
- }
348
- // ---------------------------------------------------------------------------
349
- // Path Operations
350
- // ---------------------------------------------------------------------------
351
- async exists(path) {
352
- const key = this.toKey(path);
353
- if (!key) return true;
354
- const bucket = await this.getReadyBucket();
355
- const file = bucket.file(key);
356
- const [exists] = await file.exists();
357
- if (exists) return true;
358
- const [files] = await bucket.getFiles({
359
- prefix: key.replace(/\/$/, "") + "/",
360
- maxResults: 1
361
- });
362
- return files.length > 0;
363
- }
364
- async stat(path) {
365
- const key = this.toKey(path);
366
- if (!key) {
367
- return {
368
- name: "",
369
- path,
370
- type: "directory",
371
- size: 0,
372
- createdAt: /* @__PURE__ */ new Date(),
373
- modifiedAt: /* @__PURE__ */ new Date()
374
- };
375
- }
376
- const bucket = await this.getReadyBucket();
377
- const file = bucket.file(key);
378
- const [exists] = await file.exists();
379
- if (exists) {
380
- const [metadata] = await file.getMetadata();
381
- const name = path.split("/").pop() ?? "";
382
- return {
383
- name,
384
- path,
385
- type: "file",
386
- size: Number(metadata.size) || 0,
387
- // read_file tool gates the native media-part path on `stat.mimeType`.
388
- mimeType: typeof metadata.contentType === "string" ? metadata.contentType : getMimeType(path),
389
- createdAt: metadata.timeCreated ? new Date(metadata.timeCreated) : /* @__PURE__ */ new Date(),
390
- modifiedAt: metadata.updated ? new Date(metadata.updated) : /* @__PURE__ */ new Date()
391
- };
392
- }
393
- const isDir = await this.isDirectory(path);
394
- if (isDir) {
395
- const name = path.split("/").filter(Boolean).pop() ?? "";
396
- return {
397
- name,
398
- path,
399
- type: "directory",
400
- size: 0,
401
- createdAt: /* @__PURE__ */ new Date(),
402
- modifiedAt: /* @__PURE__ */ new Date()
403
- };
404
- }
405
- throw new workspace.FileNotFoundError(path);
406
- }
407
- async isFile(path) {
408
- const key = this.toKey(path);
409
- if (!key) return false;
410
- const bucket = await this.getReadyBucket();
411
- const file = bucket.file(key);
412
- const [exists] = await file.exists();
413
- return exists;
414
- }
415
- async isDirectory(path) {
416
- const key = this.toKey(path);
417
- if (!key) return true;
418
- const bucket = await this.getReadyBucket();
419
- const [files] = await bucket.getFiles({
420
- prefix: key.replace(/\/$/, "") + "/",
421
- maxResults: 1
422
- });
423
- return files.length > 0;
424
- }
425
- // ---------------------------------------------------------------------------
426
- // Lifecycle (overrides base class protected methods)
427
- // ---------------------------------------------------------------------------
428
- /**
429
- * Initialize the GCS client.
430
- * Status management is handled by the base class.
431
- */
432
- async init() {
433
- const bucket = this.getBucket();
434
- try {
435
- const [exists] = await bucket.exists();
436
- if (!exists) {
437
- const err = new Error(`Bucket "${this.bucketName}" does not exist`);
438
- err.status = 404;
439
- throw err;
440
- }
441
- } catch (error) {
442
- if (error.status) {
443
- throw error;
444
- }
445
- const code = error.code;
446
- if (typeof code === "number") {
447
- const message = error instanceof Error ? error.message : String(error);
448
- const err = new Error(
449
- message
450
- // code === 403
451
- // ? `Access denied to bucket "${this.bucketName}" - check credentials and permissions`
452
- // : message,
453
- );
454
- err.status = code;
455
- throw err;
456
- }
457
- throw error;
458
- }
459
- }
460
- /**
461
- * Clean up the GCS client.
462
- * Status management is handled by the base class.
463
- */
464
- async destroy() {
465
- this._storage = null;
466
- this._bucket = null;
467
- }
107
+ var GCSFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
108
+ id;
109
+ name = "GCSFilesystem";
110
+ provider = "gcs";
111
+ readOnly;
112
+ status = "pending";
113
+ displayName;
114
+ icon = "gcs";
115
+ description;
116
+ bucketName;
117
+ projectId;
118
+ credentials;
119
+ prefix;
120
+ endpoint;
121
+ _storage = null;
122
+ _bucket = null;
123
+ constructor(options) {
124
+ super({
125
+ ...options,
126
+ name: "GCSFilesystem"
127
+ });
128
+ this.id = options.id ?? `gcs-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
129
+ this.bucketName = options.bucket;
130
+ this.projectId = options.projectId;
131
+ this.credentials = options.credentials;
132
+ this.prefix = options.prefix ? trimSlashes(options.prefix) + "/" : "";
133
+ this.endpoint = options.endpoint;
134
+ this.displayName = options.displayName ?? "Google Cloud Storage";
135
+ this.icon = options.icon ?? "gcs";
136
+ this.description = options.description;
137
+ this.readOnly = options.readOnly;
138
+ }
139
+ /**
140
+ * Get the underlying Google Cloud Storage instance for direct access to GCS APIs.
141
+ *
142
+ * Use this when you need to access GCS features not exposed through the
143
+ * WorkspaceFilesystem interface (e.g., signed URLs, IAM, custom metadata, etc.).
144
+ *
145
+ * @example Access other buckets
146
+ * ```typescript
147
+ * const storage = fs.storage;
148
+ * const [buckets] = await storage.getBuckets();
149
+ * ```
150
+ */
151
+ get storage() {
152
+ return this.getStorage();
153
+ }
154
+ /**
155
+ * Get the underlying GCS Bucket instance for direct access to bucket operations.
156
+ *
157
+ * Use this when you need to access bucket features not exposed through the
158
+ * WorkspaceFilesystem interface (e.g., signed URLs, lifecycle rules, etc.).
159
+ *
160
+ * @example Generate a signed URL
161
+ * ```typescript
162
+ * const bucket = fs.bucket;
163
+ * const [url] = await bucket.file('my-file.txt').getSignedUrl({
164
+ * action: 'read',
165
+ * expires: Date.now() + 15 * 60 * 1000,
166
+ * });
167
+ * ```
168
+ */
169
+ get bucket() {
170
+ return this.getBucket();
171
+ }
172
+ /**
173
+ * Get mount configuration for E2B sandbox.
174
+ * Returns GCS-compatible config that works with gcsfuse.
175
+ */
176
+ getMountConfig() {
177
+ const config = {
178
+ type: "gcs",
179
+ bucket: this.bucketName
180
+ };
181
+ if (this.credentials && typeof this.credentials === "object") config.serviceAccountKey = JSON.stringify(this.credentials);
182
+ if (this.prefix) config.prefix = this.prefix.replace(/\/$/, "");
183
+ return config;
184
+ }
185
+ /**
186
+ * Get filesystem info for status reporting.
187
+ */
188
+ getInfo() {
189
+ return {
190
+ id: this.id,
191
+ name: this.name,
192
+ provider: this.provider,
193
+ status: this.status,
194
+ error: this.error,
195
+ readOnly: this.readOnly,
196
+ icon: this.icon,
197
+ metadata: {
198
+ bucket: this.bucketName,
199
+ ...this.endpoint && { endpoint: this.endpoint },
200
+ ...this.prefix && { prefix: this.prefix }
201
+ }
202
+ };
203
+ }
204
+ /**
205
+ * Get instructions describing this GCS filesystem.
206
+ * Used by agents to understand storage semantics.
207
+ */
208
+ getInstructions() {
209
+ const access = this.readOnly ? "Read-only" : "Persistent";
210
+ return `Google Cloud Storage in bucket "${this.bucketName}". ${access} storage - files are retained across sessions.`;
211
+ }
212
+ getStorage() {
213
+ if (this._storage) return this._storage;
214
+ const options = {};
215
+ if (this.projectId) options.projectId = this.projectId;
216
+ if (this.credentials) if (typeof this.credentials === "string") options.keyFilename = this.credentials;
217
+ else options.credentials = this.credentials;
218
+ if (this.endpoint) options.apiEndpoint = this.endpoint;
219
+ this._storage = new _google_cloud_storage.Storage(options);
220
+ return this._storage;
221
+ }
222
+ getBucket() {
223
+ if (this._bucket) return this._bucket;
224
+ const storage = this.getStorage();
225
+ this._bucket = storage.bucket(this.bucketName);
226
+ return this._bucket;
227
+ }
228
+ /**
229
+ * Ensure the filesystem is initialized and return the bucket.
230
+ * Uses base class ensureReady() for status management, then returns bucket.
231
+ */
232
+ async getReadyBucket() {
233
+ await this.ensureReady();
234
+ return this.getBucket();
235
+ }
236
+ toKey(path) {
237
+ const cleanPath = path.replace(/^\/+/, "").replace(/^\.(?:\/|$)/, "");
238
+ return this.prefix + cleanPath;
239
+ }
240
+ async readFile(path, options) {
241
+ if (path.endsWith("/")) throw new _mastra_core_workspace.FileNotFoundError(path);
242
+ const file = (await this.getReadyBucket()).file(this.toKey(path));
243
+ try {
244
+ const [content] = await file.download();
245
+ if (options?.encoding) return content.toString(options.encoding);
246
+ return content;
247
+ } catch (error) {
248
+ if (error && typeof error === "object" && "code" in error && error.code === 404) throw new _mastra_core_workspace.FileNotFoundError(path);
249
+ throw error;
250
+ }
251
+ }
252
+ async writeFile(path, content, options) {
253
+ const file = (await this.getReadyBucket()).file(this.toKey(path));
254
+ if (options?.overwrite === false && await this.exists(path)) throw new _mastra_core_workspace.FileExistsError(path);
255
+ const body = typeof content === "string" ? Buffer.from(content, "utf-8") : Buffer.from(content);
256
+ const contentType = getMimeType(path);
257
+ await file.save(body, {
258
+ contentType,
259
+ resumable: false
260
+ });
261
+ }
262
+ async appendFile(path, content) {
263
+ let existing = "";
264
+ try {
265
+ existing = await this.readFile(path, { encoding: "utf-8" });
266
+ } catch (error) {
267
+ if (error instanceof _mastra_core_workspace.FileNotFoundError) {} else throw error;
268
+ }
269
+ const appendContent = typeof content === "string" ? content : Buffer.from(content).toString("utf-8");
270
+ await this.writeFile(path, existing + appendContent);
271
+ }
272
+ async deleteFile(path, options) {
273
+ if (await this.isDirectory(path)) {
274
+ await this.rmdir(path, {
275
+ recursive: true,
276
+ force: options?.force
277
+ });
278
+ return;
279
+ }
280
+ const file = (await this.getReadyBucket()).file(this.toKey(path));
281
+ try {
282
+ await file.delete();
283
+ } catch (error) {
284
+ if (!options?.force) {
285
+ if (error && typeof error === "object" && "code" in error && error.code === 404) throw new _mastra_core_workspace.FileNotFoundError(path);
286
+ throw error;
287
+ }
288
+ }
289
+ }
290
+ async copyFile(src, dest, options) {
291
+ if (src.endsWith("/")) throw new _mastra_core_workspace.FileNotFoundError(src);
292
+ const bucket = await this.getReadyBucket();
293
+ const srcFile = bucket.file(this.toKey(src));
294
+ const destFile = bucket.file(this.toKey(dest));
295
+ if (options?.overwrite === false && await this.exists(dest)) throw new _mastra_core_workspace.FileExistsError(dest);
296
+ try {
297
+ await srcFile.copy(destFile);
298
+ } catch (error) {
299
+ if (error && typeof error === "object" && "code" in error && error.code === 404) throw new _mastra_core_workspace.FileNotFoundError(src);
300
+ throw error;
301
+ }
302
+ }
303
+ async moveFile(src, dest, options) {
304
+ await this.copyFile(src, dest, options);
305
+ await this.deleteFile(src, { force: true });
306
+ }
307
+ async mkdir(path, _options) {
308
+ const key = trimSlashes(this.toKey(path));
309
+ if (!key || key + "/" === this.prefix) return;
310
+ const bucket = await this.getReadyBucket();
311
+ let fileExists = false;
312
+ try {
313
+ [fileExists] = await bucket.file(key).exists();
314
+ } catch {
315
+ fileExists = false;
316
+ }
317
+ if (fileExists) throw new _mastra_core_workspace.FileExistsError(path);
318
+ await bucket.file(key + "/").save(Buffer.alloc(0), { resumable: false });
319
+ }
320
+ async rmdir(path, options) {
321
+ if (!options?.recursive) {
322
+ const bucket = await this.getReadyBucket();
323
+ const key = trimSlashes(this.toKey(path));
324
+ const prefix = key + "/";
325
+ const [files] = await bucket.getFiles({
326
+ prefix,
327
+ maxResults: 2
328
+ });
329
+ if (files.some((file) => file.name !== prefix)) throw new Error(`Directory not empty: ${path}`);
330
+ if (key && prefix !== this.prefix) await bucket.file(prefix).delete({ ignoreNotFound: true });
331
+ return;
332
+ }
333
+ const bucket = await this.getReadyBucket();
334
+ const prefix = this.toKey(path).replace(/\/$/, "") + "/";
335
+ await bucket.deleteFiles({ prefix });
336
+ }
337
+ async readdir(path, options) {
338
+ const bucket = await this.getReadyBucket();
339
+ const prefix = this.toKey(path).replace(/\/$/, "");
340
+ const searchPrefix = prefix ? prefix + "/" : "";
341
+ const entries = [];
342
+ const seenDirs = /* @__PURE__ */ new Set();
343
+ const [files] = await bucket.getFiles({
344
+ prefix: searchPrefix,
345
+ autoPaginate: true
346
+ });
347
+ for (const file of files) {
348
+ const key = file.name;
349
+ if (!key || key === searchPrefix) continue;
350
+ const relativePath = key.slice(searchPrefix.length);
351
+ if (!relativePath) continue;
352
+ if (relativePath.endsWith("/")) {
353
+ if (options?.recursive && options.extension) continue;
354
+ const dirName = options?.recursive ? relativePath.slice(0, -1) : relativePath.split("/")[0];
355
+ if (!dirName) continue;
356
+ if (!seenDirs.has(dirName)) {
357
+ seenDirs.add(dirName);
358
+ entries.push({
359
+ name: dirName,
360
+ type: "directory"
361
+ });
362
+ }
363
+ continue;
364
+ }
365
+ const name = options?.recursive ? relativePath : relativePath.split("/")[0];
366
+ if (!name) continue;
367
+ if (!options?.recursive && relativePath.includes("/")) {
368
+ if (!seenDirs.has(name)) {
369
+ seenDirs.add(name);
370
+ entries.push({
371
+ name,
372
+ type: "directory"
373
+ });
374
+ }
375
+ continue;
376
+ }
377
+ if (options?.extension) {
378
+ if (!(Array.isArray(options.extension) ? options.extension : [options.extension]).some((ext) => name.endsWith(ext))) continue;
379
+ }
380
+ entries.push({
381
+ name,
382
+ type: "file",
383
+ size: file.metadata.size != null ? Number(file.metadata.size) : void 0
384
+ });
385
+ }
386
+ return entries;
387
+ }
388
+ async exists(path) {
389
+ const key = this.toKey(path);
390
+ if (!key) return true;
391
+ const bucket = await this.getReadyBucket();
392
+ const [exists] = await bucket.file(key).exists();
393
+ if (exists) return true;
394
+ const [files] = await bucket.getFiles({
395
+ prefix: key.replace(/\/$/, "") + "/",
396
+ maxResults: 1
397
+ });
398
+ return files.length > 0;
399
+ }
400
+ async stat(path) {
401
+ const key = trimSlashes(this.toKey(path));
402
+ const directoryOnly = path.endsWith("/");
403
+ if (!key) return {
404
+ name: "",
405
+ path,
406
+ type: "directory",
407
+ size: 0,
408
+ createdAt: /* @__PURE__ */ new Date(),
409
+ modifiedAt: /* @__PURE__ */ new Date()
410
+ };
411
+ const file = (await this.getReadyBucket()).file(key);
412
+ const name = key.split("/").pop() ?? "";
413
+ const [exists] = directoryOnly ? [false] : await file.exists();
414
+ if (exists) {
415
+ const [metadata] = await file.getMetadata();
416
+ return {
417
+ name,
418
+ path,
419
+ type: "file",
420
+ size: Number(metadata.size) || 0,
421
+ mimeType: typeof metadata.contentType === "string" ? metadata.contentType : getMimeType(path),
422
+ createdAt: metadata.timeCreated ? new Date(metadata.timeCreated) : /* @__PURE__ */ new Date(),
423
+ modifiedAt: metadata.updated ? new Date(metadata.updated) : /* @__PURE__ */ new Date()
424
+ };
425
+ }
426
+ if (await this.isDirectory(path)) return {
427
+ name,
428
+ path,
429
+ type: "directory",
430
+ size: 0,
431
+ createdAt: /* @__PURE__ */ new Date(),
432
+ modifiedAt: /* @__PURE__ */ new Date()
433
+ };
434
+ throw new _mastra_core_workspace.FileNotFoundError(path);
435
+ }
436
+ async isFile(path) {
437
+ if (path.endsWith("/")) return false;
438
+ const key = trimSlashes(this.toKey(path));
439
+ if (!key) return false;
440
+ const [exists] = await (await this.getReadyBucket()).file(key).exists();
441
+ return exists;
442
+ }
443
+ async isDirectory(path) {
444
+ const key = this.toKey(path);
445
+ if (!key) return true;
446
+ const [files] = await (await this.getReadyBucket()).getFiles({
447
+ prefix: key.replace(/\/$/, "") + "/",
448
+ maxResults: 1
449
+ });
450
+ return files.length > 0;
451
+ }
452
+ /**
453
+ * Initialize the GCS client.
454
+ * Status management is handled by the base class.
455
+ */
456
+ async init() {
457
+ const bucket = this.getBucket();
458
+ try {
459
+ const [exists] = await bucket.exists();
460
+ if (!exists) {
461
+ const err = /* @__PURE__ */ new Error(`Bucket "${this.bucketName}" does not exist`);
462
+ err.status = 404;
463
+ throw err;
464
+ }
465
+ } catch (error) {
466
+ if (error.status) throw error;
467
+ const code = error.code;
468
+ if (typeof code === "number") {
469
+ const message = error instanceof Error ? error.message : String(error);
470
+ const err = new Error(message);
471
+ err.status = code;
472
+ throw err;
473
+ }
474
+ throw error;
475
+ }
476
+ }
477
+ /**
478
+ * Clean up the GCS client.
479
+ * Status management is handled by the base class.
480
+ */
481
+ async destroy() {
482
+ this._storage = null;
483
+ this._bucket = null;
484
+ }
468
485
  };
469
-
470
- // src/provider.ts
471
- var gcsFilesystemProvider = {
472
- id: "gcs",
473
- name: "Google Cloud Storage",
474
- description: "Google Cloud Storage bucket",
475
- configSchema: {
476
- type: "object",
477
- required: ["bucket"],
478
- properties: {
479
- bucket: { type: "string", description: "GCS bucket name" },
480
- projectId: { type: "string", description: "GCS project ID" },
481
- credentials: {
482
- description: "Service account key JSON object or path to key file",
483
- oneOf: [{ type: "object" }, { type: "string" }]
484
- },
485
- prefix: { type: "string", description: "Key prefix (acts like a subdirectory)" },
486
- readOnly: { type: "boolean", description: "Mount as read-only", default: false },
487
- endpoint: { type: "string", description: "Custom API endpoint URL (for local emulators)" }
488
- }
489
- },
490
- createFilesystem: (config) => new GCSFilesystem(config)
486
+ //#endregion
487
+ //#region src/provider.ts
488
+ const gcsFilesystemProvider = {
489
+ id: "gcs",
490
+ name: "Google Cloud Storage",
491
+ description: "Google Cloud Storage bucket",
492
+ configSchema: {
493
+ type: "object",
494
+ required: ["bucket"],
495
+ properties: {
496
+ bucket: {
497
+ type: "string",
498
+ description: "GCS bucket name"
499
+ },
500
+ projectId: {
501
+ type: "string",
502
+ description: "GCS project ID"
503
+ },
504
+ credentials: {
505
+ description: "Service account key JSON object or path to key file",
506
+ oneOf: [{ type: "object" }, { type: "string" }]
507
+ },
508
+ prefix: {
509
+ type: "string",
510
+ description: "Key prefix (acts like a subdirectory)"
511
+ },
512
+ readOnly: {
513
+ type: "boolean",
514
+ description: "Mount as read-only",
515
+ default: false
516
+ },
517
+ endpoint: {
518
+ type: "string",
519
+ description: "Custom API endpoint URL (for local emulators)"
520
+ }
521
+ }
522
+ },
523
+ createFilesystem: (config) => new GCSFilesystem(config)
491
524
  };
492
-
525
+ //#endregion
493
526
  exports.GCSFilesystem = GCSFilesystem;
494
527
  exports.gcsFilesystemProvider = gcsFilesystemProvider;
495
- //# sourceMappingURL=index.cjs.map
528
+
496
529
  //# sourceMappingURL=index.cjs.map