@mastra/google-drive 0.2.0 → 0.2.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +6 -4
- package/README.md +7 -16
- package/dist/filesystem/index.d.ts.map +1 -1
- package/dist/index.cjs +550 -512
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +548 -509
- package/dist/index.js.map +1 -1
- package/package.json +15 -15
- package/CHANGELOG.md +0 -118
package/dist/index.cjs
CHANGED
|
@@ -1,519 +1,557 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let crypto = require("crypto");
|
|
3
|
+
let _mastra_core_workspace = require("@mastra/core/workspace");
|
|
4
|
+
//#region src/filesystem/index.ts
|
|
5
|
+
const DRIVE_API = "https://www.googleapis.com/drive/v3";
|
|
6
|
+
const DRIVE_UPLOAD_API = "https://www.googleapis.com/upload/drive/v3";
|
|
7
|
+
const OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
8
|
+
const FOLDER_MIME_TYPE = "application/vnd.google-apps.folder";
|
|
9
|
+
const DEFAULT_SCOPES = ["https://www.googleapis.com/auth/drive"];
|
|
10
|
+
/**
|
|
11
|
+
* Resolve an instructions override against default instructions.
|
|
12
|
+
*
|
|
13
|
+
* - `undefined` → return default
|
|
14
|
+
* - `string` → return the string as-is
|
|
15
|
+
* - `function` → call with { defaultInstructions, requestContext }
|
|
16
|
+
*/
|
|
12
17
|
function resolveInstructions(override, getDefault, requestContext) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
if (typeof override === "string") return override;
|
|
19
|
+
const defaultInstructions = getDefault();
|
|
20
|
+
if (override === void 0) return defaultInstructions;
|
|
21
|
+
return override({
|
|
22
|
+
defaultInstructions,
|
|
23
|
+
requestContext
|
|
24
|
+
});
|
|
17
25
|
}
|
|
18
|
-
var GoogleDriveFilesystem = class extends
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
26
|
+
var GoogleDriveFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
|
|
27
|
+
id;
|
|
28
|
+
name = "GoogleDriveFilesystem";
|
|
29
|
+
provider = "google-drive";
|
|
30
|
+
readOnly;
|
|
31
|
+
icon = "drive";
|
|
32
|
+
displayName = "Google Drive";
|
|
33
|
+
status = "pending";
|
|
34
|
+
accessToken;
|
|
35
|
+
tokenExpiresAt = 0;
|
|
36
|
+
tokenRefreshPromise;
|
|
37
|
+
folderId;
|
|
38
|
+
getAccessToken;
|
|
39
|
+
serviceAccount;
|
|
40
|
+
instructionsOverride;
|
|
41
|
+
constructor(options) {
|
|
42
|
+
super({
|
|
43
|
+
name: "GoogleDriveFilesystem",
|
|
44
|
+
...options
|
|
45
|
+
});
|
|
46
|
+
this.id = options.id ?? `google-drive:${options.folderId}`;
|
|
47
|
+
this.folderId = options.folderId;
|
|
48
|
+
this.accessToken = options.accessToken;
|
|
49
|
+
this.getAccessToken = options.getAccessToken;
|
|
50
|
+
this.serviceAccount = options.serviceAccount;
|
|
51
|
+
this.readOnly = options.readOnly;
|
|
52
|
+
this.instructionsOverride = options.instructions;
|
|
53
|
+
}
|
|
54
|
+
async init() {
|
|
55
|
+
const driveFile = await this.request(`${DRIVE_API}/files/${encodeURIComponent(this.folderId)}`, { searchParams: {
|
|
56
|
+
fields: "id,name,mimeType,trashed",
|
|
57
|
+
supportsAllDrives: "true"
|
|
58
|
+
} });
|
|
59
|
+
if (driveFile.trashed) throw new Error(`Google Drive folder ${this.folderId} is trashed and cannot be used as a filesystem root.`);
|
|
60
|
+
if (driveFile.mimeType !== FOLDER_MIME_TYPE) throw new Error(`Google Drive root ${this.folderId} must be a folder, but received mimeType ${driveFile.mimeType ?? "unknown"}.`);
|
|
61
|
+
}
|
|
62
|
+
async destroy() {}
|
|
63
|
+
async isReady() {
|
|
64
|
+
return this.status === "ready";
|
|
65
|
+
}
|
|
66
|
+
getInfo() {
|
|
67
|
+
return {
|
|
68
|
+
id: this.id,
|
|
69
|
+
name: this.name,
|
|
70
|
+
provider: this.provider,
|
|
71
|
+
status: this.status,
|
|
72
|
+
error: this.error,
|
|
73
|
+
readOnly: this.readOnly,
|
|
74
|
+
icon: this.icon,
|
|
75
|
+
metadata: { folderId: this.folderId }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
getInstructions(opts) {
|
|
79
|
+
const defaultInstructions = [
|
|
80
|
+
"Google Drive filesystem mounted to a single folder.",
|
|
81
|
+
"Use POSIX-style paths relative to that folder, for example /notes/todo.txt.",
|
|
82
|
+
"Directories are Google Drive folders. File names must be unique within each folder for path-based operations.",
|
|
83
|
+
this.readOnly ? "This Google Drive filesystem is read-only." : "You can read, create, update, move, copy, and delete files in this folder."
|
|
84
|
+
].join("\n");
|
|
85
|
+
return resolveInstructions(this.instructionsOverride, () => defaultInstructions, opts?.requestContext);
|
|
86
|
+
}
|
|
87
|
+
async readFile(path, options) {
|
|
88
|
+
await this.ensureReady();
|
|
89
|
+
const file = await this.getFile(path);
|
|
90
|
+
if (file.mimeType === FOLDER_MIME_TYPE) throw new _mastra_core_workspace.IsDirectoryError(path);
|
|
91
|
+
const response = await this.fetch(`${DRIVE_API}/files/${encodeURIComponent(file.id)}`, {
|
|
92
|
+
method: "GET",
|
|
93
|
+
searchParams: { alt: "media" }
|
|
94
|
+
});
|
|
95
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
96
|
+
return options?.encoding ? buffer.toString(options.encoding) : buffer;
|
|
97
|
+
}
|
|
98
|
+
async writeFile(path, content, options) {
|
|
99
|
+
await this.ensureReady();
|
|
100
|
+
this.assertWritable("writeFile");
|
|
101
|
+
const existing = await this.findFile(path);
|
|
102
|
+
if (existing) {
|
|
103
|
+
if (existing.mimeType === FOLDER_MIME_TYPE) throw new _mastra_core_workspace.IsDirectoryError(path);
|
|
104
|
+
if (options?.overwrite === false) throw new _mastra_core_workspace.FileExistsError(path);
|
|
105
|
+
if (options?.expectedMtime) {
|
|
106
|
+
if (!existing.modifiedTime) throw new _mastra_core_workspace.StaleFileError(path, options.expectedMtime, /* @__PURE__ */ new Date(0));
|
|
107
|
+
const actual = new Date(existing.modifiedTime);
|
|
108
|
+
if (actual.getTime() !== options.expectedMtime.getTime()) throw new _mastra_core_workspace.StaleFileError(path, options.expectedMtime, actual);
|
|
109
|
+
}
|
|
110
|
+
await this.upload(existing.id, content, options?.mimeType, "PATCH");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const { parentId, name } = await this.resolveParent(path, options?.recursive ?? true);
|
|
114
|
+
await this.upload(void 0, content, options?.mimeType, "POST", {
|
|
115
|
+
name,
|
|
116
|
+
parents: [parentId]
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async appendFile(path, content) {
|
|
120
|
+
await this.ensureReady();
|
|
121
|
+
this.assertWritable("appendFile");
|
|
122
|
+
const existing = await this.findFile(path);
|
|
123
|
+
if (existing) {
|
|
124
|
+
if (existing.mimeType === FOLDER_MIME_TYPE) throw new _mastra_core_workspace.IsDirectoryError(path);
|
|
125
|
+
const current = await this.readFile(path);
|
|
126
|
+
const expectedMtime = existing.modifiedTime ? new Date(existing.modifiedTime) : void 0;
|
|
127
|
+
await this.writeFile(path, Buffer.concat([this.toBuffer(current), this.toBuffer(content)]), { expectedMtime });
|
|
128
|
+
} else await this.writeFile(path, content, { recursive: true });
|
|
129
|
+
}
|
|
130
|
+
async deleteFile(path, options) {
|
|
131
|
+
await this.ensureReady();
|
|
132
|
+
this.assertWritable("deleteFile");
|
|
133
|
+
const file = await this.findFile(path);
|
|
134
|
+
if (!file) {
|
|
135
|
+
if (options?.force) return;
|
|
136
|
+
throw new _mastra_core_workspace.FileNotFoundError(path);
|
|
137
|
+
}
|
|
138
|
+
if (file.mimeType === FOLDER_MIME_TYPE) throw new _mastra_core_workspace.IsDirectoryError(path);
|
|
139
|
+
await this.request(`${DRIVE_API}/files/${encodeURIComponent(file.id)}`, { method: "DELETE" });
|
|
140
|
+
}
|
|
141
|
+
async copyFile(src, dest, options) {
|
|
142
|
+
await this.ensureReady();
|
|
143
|
+
this.assertWritable("copyFile");
|
|
144
|
+
const source = await this.getFile(src);
|
|
145
|
+
if (source.mimeType === FOLDER_MIME_TYPE) throw new _mastra_core_workspace.IsDirectoryError(src);
|
|
146
|
+
const existing = await this.findFile(dest);
|
|
147
|
+
if (existing) {
|
|
148
|
+
if (existing.id === source.id) throw new _mastra_core_workspace.FileExistsError(dest);
|
|
149
|
+
if (existing.mimeType === FOLDER_MIME_TYPE || options?.overwrite === false) throw new _mastra_core_workspace.FileExistsError(dest);
|
|
150
|
+
await this.deleteAny(existing, dest, true);
|
|
151
|
+
}
|
|
152
|
+
const { parentId, name } = await this.resolveParent(dest, options?.recursive ?? true);
|
|
153
|
+
await this.request(`${DRIVE_API}/files/${encodeURIComponent(source.id)}/copy`, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
name,
|
|
157
|
+
parents: [parentId]
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async moveFile(src, dest, options) {
|
|
162
|
+
await this.ensureReady();
|
|
163
|
+
this.assertWritable("moveFile");
|
|
164
|
+
const source = await this.getFile(src);
|
|
165
|
+
if (options?.overwrite === false && await this.exists(dest)) throw new _mastra_core_workspace.FileExistsError(dest);
|
|
166
|
+
const existing = await this.findFile(dest);
|
|
167
|
+
if (existing && existing.id !== source.id) {
|
|
168
|
+
if (existing.mimeType === FOLDER_MIME_TYPE || options?.overwrite === false) throw new _mastra_core_workspace.FileExistsError(dest);
|
|
169
|
+
await this.deleteAny(existing, dest, true);
|
|
170
|
+
}
|
|
171
|
+
const { parentId, name } = await this.resolveParent(dest, options?.recursive ?? true);
|
|
172
|
+
const searchParams = {
|
|
173
|
+
addParents: parentId,
|
|
174
|
+
fields: "id",
|
|
175
|
+
supportsAllDrives: "true"
|
|
176
|
+
};
|
|
177
|
+
const oldParents = source.parents?.join(",");
|
|
178
|
+
if (oldParents) searchParams.removeParents = oldParents;
|
|
179
|
+
await this.request(`${DRIVE_API}/files/${encodeURIComponent(source.id)}`, {
|
|
180
|
+
method: "PATCH",
|
|
181
|
+
searchParams,
|
|
182
|
+
body: JSON.stringify({ name })
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async mkdir(path, options) {
|
|
186
|
+
await this.ensureReady();
|
|
187
|
+
this.assertWritable("mkdir");
|
|
188
|
+
if (this.normalize(path) === "/") return;
|
|
189
|
+
const existing = await this.findFile(path);
|
|
190
|
+
if (existing) {
|
|
191
|
+
if (existing.mimeType !== FOLDER_MIME_TYPE) throw new _mastra_core_workspace.FileExistsError(path);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const { parentId, name } = await this.resolveParent(path, options?.recursive ?? true);
|
|
195
|
+
await this.createFolder(parentId, name);
|
|
196
|
+
}
|
|
197
|
+
async rmdir(path, options) {
|
|
198
|
+
await this.ensureReady();
|
|
199
|
+
this.assertWritable("rmdir");
|
|
200
|
+
const dir = await this.findFile(path);
|
|
201
|
+
if (!dir) {
|
|
202
|
+
if (options?.force) return;
|
|
203
|
+
throw new _mastra_core_workspace.DirectoryNotFoundError(path);
|
|
204
|
+
}
|
|
205
|
+
if (dir.mimeType !== FOLDER_MIME_TYPE) throw new _mastra_core_workspace.NotDirectoryError(path);
|
|
206
|
+
if (!options?.recursive) {
|
|
207
|
+
if ((await this.listChildren(dir.id)).length) throw new _mastra_core_workspace.DirectoryNotEmptyError(path);
|
|
208
|
+
}
|
|
209
|
+
await this.request(`${DRIVE_API}/files/${encodeURIComponent(dir.id)}`, { method: "DELETE" });
|
|
210
|
+
}
|
|
211
|
+
async readdir(path, options) {
|
|
212
|
+
await this.ensureReady();
|
|
213
|
+
const dir = await this.getFile(path);
|
|
214
|
+
if (dir.mimeType !== FOLDER_MIME_TYPE) throw new _mastra_core_workspace.NotDirectoryError(path);
|
|
215
|
+
const entries = await this.readdirRecursive(dir.id, options, 0);
|
|
216
|
+
const extensions = Array.isArray(options?.extension) ? options.extension : options?.extension ? [options.extension] : void 0;
|
|
217
|
+
return extensions ? entries.filter((entry) => entry.type === "directory" || extensions.some((ext) => entry.name.endsWith(ext))) : entries;
|
|
218
|
+
}
|
|
219
|
+
async exists(path) {
|
|
220
|
+
await this.ensureReady();
|
|
221
|
+
return Boolean(await this.findFile(path));
|
|
222
|
+
}
|
|
223
|
+
async stat(path) {
|
|
224
|
+
await this.ensureReady();
|
|
225
|
+
const file = await this.getFile(path);
|
|
226
|
+
const isDirectory = file.mimeType === FOLDER_MIME_TYPE;
|
|
227
|
+
return {
|
|
228
|
+
name: file.name,
|
|
229
|
+
path: this.normalize(path),
|
|
230
|
+
type: isDirectory ? "directory" : "file",
|
|
231
|
+
size: Number(file.size ?? 0),
|
|
232
|
+
createdAt: file.createdTime ? new Date(file.createdTime) : /* @__PURE__ */ new Date(0),
|
|
233
|
+
modifiedAt: file.modifiedTime ? new Date(file.modifiedTime) : /* @__PURE__ */ new Date(0),
|
|
234
|
+
mimeType: isDirectory ? void 0 : file.mimeType
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async realpath(path) {
|
|
238
|
+
return this.normalize(path);
|
|
239
|
+
}
|
|
240
|
+
assertWritable(operation) {
|
|
241
|
+
if (this.readOnly) throw new _mastra_core_workspace.WorkspaceReadOnlyError(operation);
|
|
242
|
+
}
|
|
243
|
+
toBuffer(content) {
|
|
244
|
+
if (Buffer.isBuffer(content)) return content;
|
|
245
|
+
if (content instanceof Uint8Array) return Buffer.from(content);
|
|
246
|
+
return Buffer.from(content, "utf-8");
|
|
247
|
+
}
|
|
248
|
+
normalize(path) {
|
|
249
|
+
const parts = path.split("/").filter(Boolean);
|
|
250
|
+
const stack = [];
|
|
251
|
+
for (const part of parts) {
|
|
252
|
+
if (part === ".") continue;
|
|
253
|
+
if (part === "..") stack.pop();
|
|
254
|
+
else stack.push(part);
|
|
255
|
+
}
|
|
256
|
+
return `/${stack.join("/")}`;
|
|
257
|
+
}
|
|
258
|
+
async getFile(path) {
|
|
259
|
+
const file = await this.findFile(path);
|
|
260
|
+
if (!file) throw new _mastra_core_workspace.FileNotFoundError(path);
|
|
261
|
+
return file;
|
|
262
|
+
}
|
|
263
|
+
async findFile(path) {
|
|
264
|
+
const normalized = this.normalize(path);
|
|
265
|
+
if (normalized === "/") return this.rootFile();
|
|
266
|
+
const names = normalized.split("/").filter(Boolean);
|
|
267
|
+
let parentId = this.folderId;
|
|
268
|
+
let file;
|
|
269
|
+
for (const name of names) {
|
|
270
|
+
file = await this.findChild(parentId, name);
|
|
271
|
+
if (!file) return void 0;
|
|
272
|
+
parentId = file.id;
|
|
273
|
+
}
|
|
274
|
+
return file;
|
|
275
|
+
}
|
|
276
|
+
async rootFile() {
|
|
277
|
+
return this.request(`${DRIVE_API}/files/${encodeURIComponent(this.folderId)}`, { searchParams: {
|
|
278
|
+
fields: "id,name,mimeType,size,createdTime,modifiedTime,parents",
|
|
279
|
+
supportsAllDrives: "true"
|
|
280
|
+
} });
|
|
281
|
+
}
|
|
282
|
+
async resolveParent(path, recursive) {
|
|
283
|
+
const parts = this.normalize(path).split("/").filter(Boolean);
|
|
284
|
+
const name = parts.pop();
|
|
285
|
+
if (!name) throw new _mastra_core_workspace.IsDirectoryError(path);
|
|
286
|
+
const parentPath = `/${parts.join("/")}`;
|
|
287
|
+
const parent = recursive ? await this.resolveDir(parentPath, true) : await this.findFile(parentPath);
|
|
288
|
+
if (!parent) throw new _mastra_core_workspace.DirectoryNotFoundError(parentPath);
|
|
289
|
+
if (parent.mimeType !== FOLDER_MIME_TYPE) throw new _mastra_core_workspace.NotDirectoryError(parentPath);
|
|
290
|
+
return {
|
|
291
|
+
parentId: parent.id,
|
|
292
|
+
name
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
async resolveDir(path, recursive) {
|
|
296
|
+
const normalized = this.normalize(path);
|
|
297
|
+
if (normalized === "/") return this.rootFile();
|
|
298
|
+
const names = normalized.split("/").filter(Boolean);
|
|
299
|
+
let parentId = this.folderId;
|
|
300
|
+
let current;
|
|
301
|
+
for (const name of names) {
|
|
302
|
+
current = await this.findChild(parentId, name);
|
|
303
|
+
if (current) {
|
|
304
|
+
if (current.mimeType !== FOLDER_MIME_TYPE) throw new _mastra_core_workspace.NotDirectoryError(name);
|
|
305
|
+
parentId = current.id;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (!recursive) throw new _mastra_core_workspace.DirectoryNotFoundError(normalized);
|
|
309
|
+
current = await this.createFolder(parentId, name);
|
|
310
|
+
parentId = current.id;
|
|
311
|
+
}
|
|
312
|
+
return current;
|
|
313
|
+
}
|
|
314
|
+
async createFolder(parentId, name) {
|
|
315
|
+
return this.request(`${DRIVE_API}/files`, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
searchParams: {
|
|
318
|
+
fields: "id,name,mimeType,size,createdTime,modifiedTime,parents",
|
|
319
|
+
supportsAllDrives: "true"
|
|
320
|
+
},
|
|
321
|
+
body: JSON.stringify({
|
|
322
|
+
name,
|
|
323
|
+
mimeType: FOLDER_MIME_TYPE,
|
|
324
|
+
parents: [parentId]
|
|
325
|
+
})
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
async findChild(parentId, name) {
|
|
329
|
+
return (await this.listChildren(parentId, `name = '${this.escapeQuery(name)}'`))[0];
|
|
330
|
+
}
|
|
331
|
+
async listChildren(parentId, extraQuery) {
|
|
332
|
+
const query = [
|
|
333
|
+
`'${this.escapeQuery(parentId)}' in parents`,
|
|
334
|
+
"trashed = false",
|
|
335
|
+
extraQuery
|
|
336
|
+
].filter(Boolean).join(" and ");
|
|
337
|
+
const files = [];
|
|
338
|
+
let pageToken;
|
|
339
|
+
do {
|
|
340
|
+
const result = await this.request(`${DRIVE_API}/files`, { searchParams: {
|
|
341
|
+
q: query,
|
|
342
|
+
fields: "nextPageToken,files(id,name,mimeType,size,createdTime,modifiedTime,parents)",
|
|
343
|
+
pageSize: "1000",
|
|
344
|
+
supportsAllDrives: "true",
|
|
345
|
+
includeItemsFromAllDrives: "true",
|
|
346
|
+
...pageToken ? { pageToken } : {}
|
|
347
|
+
} });
|
|
348
|
+
files.push(...result.files ?? []);
|
|
349
|
+
pageToken = result.nextPageToken;
|
|
350
|
+
} while (pageToken);
|
|
351
|
+
return files;
|
|
352
|
+
}
|
|
353
|
+
async readdirRecursive(parentId, options, depth) {
|
|
354
|
+
const children = await this.listChildren(parentId);
|
|
355
|
+
const entries = [];
|
|
356
|
+
for (const child of children) {
|
|
357
|
+
const isDirectory = child.mimeType === FOLDER_MIME_TYPE;
|
|
358
|
+
entries.push({
|
|
359
|
+
name: child.name,
|
|
360
|
+
type: isDirectory ? "directory" : "file",
|
|
361
|
+
size: Number(child.size ?? 0)
|
|
362
|
+
});
|
|
363
|
+
if (isDirectory && options?.recursive && (options.maxDepth === void 0 || depth < options.maxDepth)) {
|
|
364
|
+
const nested = await this.readdirRecursive(child.id, options, depth + 1);
|
|
365
|
+
entries.push(...nested.map((entry) => ({
|
|
366
|
+
...entry,
|
|
367
|
+
name: `${child.name}/${entry.name}`
|
|
368
|
+
})));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return entries;
|
|
372
|
+
}
|
|
373
|
+
async deleteAny(file, path, recursive) {
|
|
374
|
+
if (file.mimeType === FOLDER_MIME_TYPE) await this.rmdir(path, {
|
|
375
|
+
recursive,
|
|
376
|
+
force: true
|
|
377
|
+
});
|
|
378
|
+
else await this.deleteFile(path, { force: true });
|
|
379
|
+
}
|
|
380
|
+
async upload(fileId, content, mimeType = "application/octet-stream", method, metadata) {
|
|
381
|
+
const boundary = `mastra-${Date.now()}`;
|
|
382
|
+
const body = Buffer.concat([
|
|
383
|
+
Buffer.from(`--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${JSON.stringify(metadata ?? {})}\r\n`),
|
|
384
|
+
Buffer.from(`--${boundary}\r\nContent-Type: ${mimeType}\r\n\r\n`),
|
|
385
|
+
this.toBuffer(content),
|
|
386
|
+
Buffer.from(`\r\n--${boundary}--`)
|
|
387
|
+
]);
|
|
388
|
+
const url = fileId ? `${DRIVE_UPLOAD_API}/files/${encodeURIComponent(fileId)}` : `${DRIVE_UPLOAD_API}/files`;
|
|
389
|
+
await this.request(url, {
|
|
390
|
+
method,
|
|
391
|
+
searchParams: {
|
|
392
|
+
uploadType: "multipart",
|
|
393
|
+
fields: "id",
|
|
394
|
+
supportsAllDrives: "true"
|
|
395
|
+
},
|
|
396
|
+
headers: { "Content-Type": `multipart/related; boundary=${boundary}` },
|
|
397
|
+
body
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
escapeQuery(value) {
|
|
401
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
402
|
+
}
|
|
403
|
+
async request(url, init = {}) {
|
|
404
|
+
const response = await this.fetch(url, init);
|
|
405
|
+
if (response.status === 204) return void 0;
|
|
406
|
+
return await response.json();
|
|
407
|
+
}
|
|
408
|
+
async fetch(url, init = {}) {
|
|
409
|
+
const token = await this.getToken();
|
|
410
|
+
const target = new URL(url);
|
|
411
|
+
for (const [key, value] of Object.entries(init.searchParams ?? {})) target.searchParams.set(key, value);
|
|
412
|
+
const headers = { Authorization: `Bearer ${token}` };
|
|
413
|
+
if (init.body && typeof init.body === "string" && !init.headers) headers["Content-Type"] = "application/json";
|
|
414
|
+
const response = await globalThis.fetch(target, {
|
|
415
|
+
...init,
|
|
416
|
+
headers: {
|
|
417
|
+
...headers,
|
|
418
|
+
...init.headers
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
if (!response.ok) {
|
|
422
|
+
const message = await response.text().catch(() => response.statusText);
|
|
423
|
+
throw new Error(`Google Drive API request failed (${response.status}): ${message}`);
|
|
424
|
+
}
|
|
425
|
+
return response;
|
|
426
|
+
}
|
|
427
|
+
async getToken() {
|
|
428
|
+
if (this.accessToken && Date.now() < this.tokenExpiresAt - 6e4) return this.accessToken;
|
|
429
|
+
if (this.getAccessToken) return this.getAccessToken();
|
|
430
|
+
if (this.serviceAccount) {
|
|
431
|
+
if (!this.tokenRefreshPromise) this.tokenRefreshPromise = this.getServiceAccountToken().finally(() => {
|
|
432
|
+
this.tokenRefreshPromise = void 0;
|
|
433
|
+
});
|
|
434
|
+
return this.tokenRefreshPromise;
|
|
435
|
+
}
|
|
436
|
+
if (this.accessToken) return this.accessToken;
|
|
437
|
+
throw new Error("GoogleDriveFilesystem requires accessToken, getAccessToken, or serviceAccount authentication.");
|
|
438
|
+
}
|
|
439
|
+
async getServiceAccountToken() {
|
|
440
|
+
const account = this.serviceAccount;
|
|
441
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
442
|
+
const header = {
|
|
443
|
+
alg: "RS256",
|
|
444
|
+
typ: "JWT",
|
|
445
|
+
...account.privateKeyId ? { kid: account.privateKeyId } : {}
|
|
446
|
+
};
|
|
447
|
+
const claim = {
|
|
448
|
+
iss: account.clientEmail,
|
|
449
|
+
scope: (account.scopes ?? DEFAULT_SCOPES).join(" "),
|
|
450
|
+
aud: OAUTH_TOKEN_URL,
|
|
451
|
+
exp: now + 3600,
|
|
452
|
+
iat: now,
|
|
453
|
+
...account.subject ? { sub: account.subject } : {}
|
|
454
|
+
};
|
|
455
|
+
const unsigned = `${this.base64Url(JSON.stringify(header))}.${this.base64Url(JSON.stringify(claim))}`;
|
|
456
|
+
const privateKey = this.normalizePrivateKey(account.privateKey);
|
|
457
|
+
let signature;
|
|
458
|
+
try {
|
|
459
|
+
signature = (0, crypto.createSign)("RSA-SHA256").update(unsigned).sign(privateKey, "base64url");
|
|
460
|
+
} catch (err) {
|
|
461
|
+
const hasBegin = privateKey.includes("-----BEGIN");
|
|
462
|
+
const hasEnd = privateKey.includes("-----END");
|
|
463
|
+
throw new Error(`Google service account private key signing failed (${err.message}). Key has BEGIN marker: ${hasBegin}, END marker: ${hasEnd}. Ensure your .env value contains the raw PEM with \\n for newlines, without extra surrounding quotes or commas.`);
|
|
464
|
+
}
|
|
465
|
+
const response = await globalThis.fetch(OAUTH_TOKEN_URL, {
|
|
466
|
+
method: "POST",
|
|
467
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
468
|
+
body: new URLSearchParams({
|
|
469
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
470
|
+
assertion: `${unsigned}.${signature}`
|
|
471
|
+
})
|
|
472
|
+
});
|
|
473
|
+
if (!response.ok) throw new Error(`Google service account token request failed (${response.status}): ${await response.text()}`);
|
|
474
|
+
const json = await response.json();
|
|
475
|
+
this.accessToken = json.access_token;
|
|
476
|
+
this.tokenExpiresAt = Date.now() + json.expires_in * 1e3;
|
|
477
|
+
return json.access_token;
|
|
478
|
+
}
|
|
479
|
+
base64Url(value) {
|
|
480
|
+
return Buffer.from(value).toString("base64url");
|
|
481
|
+
}
|
|
482
|
+
normalizePrivateKey(key) {
|
|
483
|
+
let out = key.trim();
|
|
484
|
+
for (let i = 0; i < 5; i++) {
|
|
485
|
+
const before = out;
|
|
486
|
+
if (out.endsWith(",")) out = out.slice(0, -1).trim();
|
|
487
|
+
if (out.startsWith("\"") && out.endsWith("\"") || out.startsWith("'") && out.endsWith("'")) out = out.slice(1, -1);
|
|
488
|
+
if (out.startsWith("\\\"") && out.endsWith("\\\"") || out.startsWith("\\'") && out.endsWith("\\'")) out = out.slice(2, -2);
|
|
489
|
+
if (out === before) break;
|
|
490
|
+
}
|
|
491
|
+
out = out.replace(/\\n/g, "\n");
|
|
492
|
+
out = out.replace(/\\"/g, "\"").replace(/\\'/g, "'");
|
|
493
|
+
out = out.replace(/\r\n?/g, "\n");
|
|
494
|
+
if (!out.endsWith("\n")) out += "\n";
|
|
495
|
+
return out;
|
|
496
|
+
}
|
|
478
497
|
};
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region src/provider.ts
|
|
500
|
+
const googleDriveFilesystemProvider = {
|
|
501
|
+
id: "google-drive",
|
|
502
|
+
name: "Google Drive",
|
|
503
|
+
description: "Google Drive folder mounted as a filesystem",
|
|
504
|
+
configSchema: {
|
|
505
|
+
type: "object",
|
|
506
|
+
required: ["folderId"],
|
|
507
|
+
properties: {
|
|
508
|
+
folderId: {
|
|
509
|
+
type: "string",
|
|
510
|
+
description: "Google Drive folder ID to mount as the workspace root"
|
|
511
|
+
},
|
|
512
|
+
accessToken: {
|
|
513
|
+
type: "string",
|
|
514
|
+
description: "OAuth access token with the https://www.googleapis.com/auth/drive scope"
|
|
515
|
+
},
|
|
516
|
+
serviceAccount: {
|
|
517
|
+
type: "object",
|
|
518
|
+
required: ["clientEmail", "privateKey"],
|
|
519
|
+
properties: {
|
|
520
|
+
clientEmail: {
|
|
521
|
+
type: "string",
|
|
522
|
+
description: "Google service account email"
|
|
523
|
+
},
|
|
524
|
+
privateKey: {
|
|
525
|
+
type: "string",
|
|
526
|
+
description: "PEM-encoded private key"
|
|
527
|
+
},
|
|
528
|
+
privateKeyId: {
|
|
529
|
+
type: "string",
|
|
530
|
+
description: "Optional private key ID"
|
|
531
|
+
},
|
|
532
|
+
scopes: {
|
|
533
|
+
type: "array",
|
|
534
|
+
items: { type: "string" },
|
|
535
|
+
description: "Optional OAuth scopes override"
|
|
536
|
+
},
|
|
537
|
+
subject: {
|
|
538
|
+
type: "string",
|
|
539
|
+
description: "Optional delegated user email"
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
description: "Service account credentials for server-to-server auth"
|
|
543
|
+
},
|
|
544
|
+
readOnly: {
|
|
545
|
+
type: "boolean",
|
|
546
|
+
description: "Mount as read-only",
|
|
547
|
+
default: false
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
createFilesystem: (config) => new GoogleDriveFilesystem(config)
|
|
514
552
|
};
|
|
515
|
-
|
|
553
|
+
//#endregion
|
|
516
554
|
exports.GoogleDriveFilesystem = GoogleDriveFilesystem;
|
|
517
555
|
exports.googleDriveFilesystemProvider = googleDriveFilesystemProvider;
|
|
518
|
-
|
|
556
|
+
|
|
519
557
|
//# sourceMappingURL=index.cjs.map
|