@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/CHANGELOG.md +34 -6
- package/dist/filesystem/index.d.ts +1 -1
- package/dist/filesystem/index.d.ts.map +1 -1
- package/dist/index.cjs +519 -486
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +517 -483
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
package/dist/index.cjs
CHANGED
|
@@ -1,496 +1,529 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
*
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
528
|
+
|
|
496
529
|
//# sourceMappingURL=index.cjs.map
|