@mastra/gcs 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +15 -0
- package/README.md +58 -0
- package/dist/filesystem/index.d.ts +118 -0
- package/dist/filesystem/index.d.ts.map +1 -0
- package/dist/index.cjs +426 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +424 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Apache License 2.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kepler Software, Inc.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @mastra/gcs
|
|
2
|
+
|
|
3
|
+
Google Cloud Storage filesystem provider for Mastra workspaces.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mastra/gcs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Agent } from '@mastra/core/agent';
|
|
15
|
+
import { Workspace } from '@mastra/core/workspace';
|
|
16
|
+
import { GCSFilesystem } from '@mastra/gcs';
|
|
17
|
+
|
|
18
|
+
const workspace = new Workspace({
|
|
19
|
+
filesystem: new GCSFilesystem({
|
|
20
|
+
bucket: 'my-gcs-bucket',
|
|
21
|
+
// Uses Application Default Credentials by default
|
|
22
|
+
// Or provide a service account key:
|
|
23
|
+
projectId: 'my-project-id',
|
|
24
|
+
credentials: JSON.parse(process.env.GCS_SERVICE_ACCOUNT_KEY),
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const agent = new Agent({
|
|
29
|
+
name: 'my-agent',
|
|
30
|
+
model: 'anthropic/claude-opus-4-5',
|
|
31
|
+
workspace,
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### With E2B Sandbox
|
|
36
|
+
|
|
37
|
+
When used with `@mastra/e2b`, GCS filesystems can be mounted into E2B sandboxes via gcsfuse:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { Workspace } from '@mastra/core/workspace';
|
|
41
|
+
import { GCSFilesystem } from '@mastra/gcs';
|
|
42
|
+
import { E2BSandbox } from '@mastra/e2b';
|
|
43
|
+
|
|
44
|
+
const workspace = new Workspace({
|
|
45
|
+
mounts: {
|
|
46
|
+
'/my-bucket': new GCSFilesystem({
|
|
47
|
+
bucket: 'my-gcs-bucket',
|
|
48
|
+
projectId: 'my-project-id',
|
|
49
|
+
credentials: JSON.parse(process.env.GCS_SERVICE_ACCOUNT_KEY),
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
sandbox: new E2BSandbox(),
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Documentation
|
|
57
|
+
|
|
58
|
+
For more information, see the [Mastra Workspaces documentation](https://mastra.ai/docs/workspace/overview).
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GCS Filesystem Provider
|
|
3
|
+
*
|
|
4
|
+
* A filesystem implementation backed by Google Cloud Storage.
|
|
5
|
+
*/
|
|
6
|
+
import type { FileContent, FileStat, FileEntry, ReadOptions, WriteOptions, ListOptions, RemoveOptions, CopyOptions, FilesystemInfo, FilesystemMountConfig, FilesystemIcon, ProviderStatus } from '@mastra/core/workspace';
|
|
7
|
+
import { MastraFilesystem } from '@mastra/core/workspace';
|
|
8
|
+
/**
|
|
9
|
+
* GCS mount configuration.
|
|
10
|
+
* Returned by GCSFilesystem.getMountConfig() for FUSE mounting in sandboxes.
|
|
11
|
+
*/
|
|
12
|
+
export interface GCSMountConfig extends FilesystemMountConfig {
|
|
13
|
+
type: 'gcs';
|
|
14
|
+
/** GCS bucket name */
|
|
15
|
+
bucket: string;
|
|
16
|
+
/** Service account key JSON (optional - omit for public buckets or ADC) */
|
|
17
|
+
serviceAccountKey?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* GCS filesystem provider configuration.
|
|
21
|
+
*/
|
|
22
|
+
export interface GCSFilesystemOptions {
|
|
23
|
+
/** Unique identifier for this filesystem instance */
|
|
24
|
+
id?: string;
|
|
25
|
+
/** GCS bucket name */
|
|
26
|
+
bucket: string;
|
|
27
|
+
/** Human-friendly display name for the UI */
|
|
28
|
+
displayName?: string;
|
|
29
|
+
/** Icon identifier for the UI (defaults to 'gcs') */
|
|
30
|
+
icon?: FilesystemIcon;
|
|
31
|
+
/** Description shown in tooltips */
|
|
32
|
+
description?: string;
|
|
33
|
+
/**
|
|
34
|
+
* GCS project ID.
|
|
35
|
+
* Required when using service account credentials.
|
|
36
|
+
*/
|
|
37
|
+
projectId?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Service account key JSON object or path to key file.
|
|
40
|
+
* If not provided, uses Application Default Credentials (ADC).
|
|
41
|
+
*/
|
|
42
|
+
credentials?: object | string;
|
|
43
|
+
/** Optional prefix for all keys (acts like a subdirectory) */
|
|
44
|
+
prefix?: string;
|
|
45
|
+
/** Mount as read-only (blocks write operations, mounts read-only in sandboxes) */
|
|
46
|
+
readOnly?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Custom API endpoint URL.
|
|
49
|
+
* Used for local development with emulators like fake-gcs-server.
|
|
50
|
+
*/
|
|
51
|
+
endpoint?: string;
|
|
52
|
+
}
|
|
53
|
+
export declare class GCSFilesystem extends MastraFilesystem {
|
|
54
|
+
readonly id: string;
|
|
55
|
+
readonly name = "GCSFilesystem";
|
|
56
|
+
readonly provider = "gcs";
|
|
57
|
+
readonly readOnly?: boolean;
|
|
58
|
+
status: ProviderStatus;
|
|
59
|
+
readonly displayName?: string;
|
|
60
|
+
readonly icon: FilesystemIcon;
|
|
61
|
+
readonly description?: string;
|
|
62
|
+
private readonly bucketName;
|
|
63
|
+
private readonly projectId?;
|
|
64
|
+
private readonly credentials?;
|
|
65
|
+
private readonly prefix;
|
|
66
|
+
private readonly endpoint?;
|
|
67
|
+
private _storage;
|
|
68
|
+
private _bucket;
|
|
69
|
+
constructor(options: GCSFilesystemOptions);
|
|
70
|
+
/**
|
|
71
|
+
* Get mount configuration for E2B sandbox.
|
|
72
|
+
* Returns GCS-compatible config that works with gcsfuse.
|
|
73
|
+
*/
|
|
74
|
+
getMountConfig(): GCSMountConfig;
|
|
75
|
+
/**
|
|
76
|
+
* Get filesystem info for status reporting.
|
|
77
|
+
*/
|
|
78
|
+
getInfo(): FilesystemInfo;
|
|
79
|
+
/**
|
|
80
|
+
* Get instructions describing this GCS filesystem.
|
|
81
|
+
* Used by agents to understand storage semantics.
|
|
82
|
+
*/
|
|
83
|
+
getInstructions(): string;
|
|
84
|
+
private getStorage;
|
|
85
|
+
private getBucket;
|
|
86
|
+
/**
|
|
87
|
+
* Ensure the filesystem is initialized and return the bucket.
|
|
88
|
+
* Uses base class ensureReady() for status management, then returns bucket.
|
|
89
|
+
*/
|
|
90
|
+
private getReadyBucket;
|
|
91
|
+
private toKey;
|
|
92
|
+
readFile(path: string, options?: ReadOptions): Promise<string | Buffer>;
|
|
93
|
+
writeFile(path: string, content: FileContent, _options?: WriteOptions): Promise<void>;
|
|
94
|
+
appendFile(path: string, content: FileContent): Promise<void>;
|
|
95
|
+
deleteFile(path: string, options?: RemoveOptions): Promise<void>;
|
|
96
|
+
copyFile(src: string, dest: string, _options?: CopyOptions): Promise<void>;
|
|
97
|
+
moveFile(src: string, dest: string, options?: CopyOptions): Promise<void>;
|
|
98
|
+
mkdir(_path: string, _options?: {
|
|
99
|
+
recursive?: boolean;
|
|
100
|
+
}): Promise<void>;
|
|
101
|
+
rmdir(path: string, options?: RemoveOptions): Promise<void>;
|
|
102
|
+
readdir(path: string, options?: ListOptions): Promise<FileEntry[]>;
|
|
103
|
+
exists(path: string): Promise<boolean>;
|
|
104
|
+
stat(path: string): Promise<FileStat>;
|
|
105
|
+
isFile(path: string): Promise<boolean>;
|
|
106
|
+
isDirectory(path: string): Promise<boolean>;
|
|
107
|
+
/**
|
|
108
|
+
* Initialize the GCS client.
|
|
109
|
+
* Status management is handled by the base class.
|
|
110
|
+
*/
|
|
111
|
+
init(): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Clean up the GCS client.
|
|
114
|
+
* Status management is handled by the base class.
|
|
115
|
+
*/
|
|
116
|
+
destroy(): Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/filesystem/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
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"
|
|
44
|
+
};
|
|
45
|
+
function getMimeType(path) {
|
|
46
|
+
const ext = path.toLowerCase().match(/\.[^.]+$/)?.[0];
|
|
47
|
+
return ext ? MIME_TYPES[ext] ?? "application/octet-stream" : "application/octet-stream";
|
|
48
|
+
}
|
|
49
|
+
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);
|
|
55
|
+
}
|
|
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({ 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 mount configuration for E2B sandbox.
|
|
88
|
+
* Returns GCS-compatible config that works with gcsfuse.
|
|
89
|
+
*/
|
|
90
|
+
getMountConfig() {
|
|
91
|
+
const config = {
|
|
92
|
+
type: "gcs",
|
|
93
|
+
bucket: this.bucketName
|
|
94
|
+
};
|
|
95
|
+
if (this.credentials && typeof this.credentials === "object") {
|
|
96
|
+
config.serviceAccountKey = JSON.stringify(this.credentials);
|
|
97
|
+
}
|
|
98
|
+
return config;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get filesystem info for status reporting.
|
|
102
|
+
*/
|
|
103
|
+
getInfo() {
|
|
104
|
+
return {
|
|
105
|
+
id: this.id,
|
|
106
|
+
name: this.name,
|
|
107
|
+
provider: this.provider,
|
|
108
|
+
status: this.status,
|
|
109
|
+
icon: this.icon,
|
|
110
|
+
metadata: {
|
|
111
|
+
bucket: this.bucketName,
|
|
112
|
+
...this.endpoint && { endpoint: this.endpoint },
|
|
113
|
+
...this.prefix && { prefix: this.prefix }
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get instructions describing this GCS filesystem.
|
|
119
|
+
* Used by agents to understand storage semantics.
|
|
120
|
+
*/
|
|
121
|
+
getInstructions() {
|
|
122
|
+
const access = this.readOnly ? "Read-only" : "Persistent";
|
|
123
|
+
return `Google Cloud Storage in bucket "${this.bucketName}". ${access} storage - files are retained across sessions.`;
|
|
124
|
+
}
|
|
125
|
+
getStorage() {
|
|
126
|
+
if (this._storage) return this._storage;
|
|
127
|
+
const options = {};
|
|
128
|
+
if (this.projectId) {
|
|
129
|
+
options.projectId = this.projectId;
|
|
130
|
+
}
|
|
131
|
+
if (this.credentials) {
|
|
132
|
+
if (typeof this.credentials === "string") {
|
|
133
|
+
options.keyFilename = this.credentials;
|
|
134
|
+
} else {
|
|
135
|
+
options.credentials = this.credentials;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (this.endpoint) {
|
|
139
|
+
options.apiEndpoint = this.endpoint;
|
|
140
|
+
}
|
|
141
|
+
this._storage = new storage.Storage(options);
|
|
142
|
+
return this._storage;
|
|
143
|
+
}
|
|
144
|
+
getBucket() {
|
|
145
|
+
if (this._bucket) return this._bucket;
|
|
146
|
+
const storage = this.getStorage();
|
|
147
|
+
this._bucket = storage.bucket(this.bucketName);
|
|
148
|
+
return this._bucket;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Ensure the filesystem is initialized and return the bucket.
|
|
152
|
+
* Uses base class ensureReady() for status management, then returns bucket.
|
|
153
|
+
*/
|
|
154
|
+
async getReadyBucket() {
|
|
155
|
+
await this.ensureReady();
|
|
156
|
+
return this.getBucket();
|
|
157
|
+
}
|
|
158
|
+
toKey(path) {
|
|
159
|
+
const cleanPath = path.replace(/^\/+/, "");
|
|
160
|
+
return this.prefix + cleanPath;
|
|
161
|
+
}
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// File Operations
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
async readFile(path, options) {
|
|
166
|
+
const bucket = await this.getReadyBucket();
|
|
167
|
+
const file = bucket.file(this.toKey(path));
|
|
168
|
+
try {
|
|
169
|
+
const [content] = await file.download();
|
|
170
|
+
if (options?.encoding) {
|
|
171
|
+
return content.toString(options.encoding);
|
|
172
|
+
}
|
|
173
|
+
return content;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error && typeof error === "object" && "code" in error && error.code === 404) {
|
|
176
|
+
throw new workspace.FileNotFoundError(path);
|
|
177
|
+
}
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async writeFile(path, content, _options) {
|
|
182
|
+
const bucket = await this.getReadyBucket();
|
|
183
|
+
const file = bucket.file(this.toKey(path));
|
|
184
|
+
const body = typeof content === "string" ? Buffer.from(content, "utf-8") : Buffer.from(content);
|
|
185
|
+
const contentType = getMimeType(path);
|
|
186
|
+
await file.save(body, {
|
|
187
|
+
contentType,
|
|
188
|
+
resumable: false
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async appendFile(path, content) {
|
|
192
|
+
let existing = "";
|
|
193
|
+
try {
|
|
194
|
+
existing = await this.readFile(path, { encoding: "utf-8" });
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error instanceof workspace.FileNotFoundError) ; else {
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const appendContent = typeof content === "string" ? content : Buffer.from(content).toString("utf-8");
|
|
201
|
+
await this.writeFile(path, existing + appendContent);
|
|
202
|
+
}
|
|
203
|
+
async deleteFile(path, options) {
|
|
204
|
+
const isDir = await this.isDirectory(path);
|
|
205
|
+
if (isDir) {
|
|
206
|
+
await this.rmdir(path, { recursive: true, force: options?.force });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const bucket = await this.getReadyBucket();
|
|
210
|
+
const file = bucket.file(this.toKey(path));
|
|
211
|
+
try {
|
|
212
|
+
await file.delete();
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (!options?.force) {
|
|
215
|
+
if (error && typeof error === "object" && "code" in error && error.code === 404) {
|
|
216
|
+
throw new workspace.FileNotFoundError(path);
|
|
217
|
+
}
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async copyFile(src, dest, _options) {
|
|
223
|
+
const bucket = await this.getReadyBucket();
|
|
224
|
+
const srcFile = bucket.file(this.toKey(src));
|
|
225
|
+
const destFile = bucket.file(this.toKey(dest));
|
|
226
|
+
try {
|
|
227
|
+
await srcFile.copy(destFile);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (error && typeof error === "object" && "code" in error && error.code === 404) {
|
|
230
|
+
throw new workspace.FileNotFoundError(src);
|
|
231
|
+
}
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async moveFile(src, dest, options) {
|
|
236
|
+
await this.copyFile(src, dest, options);
|
|
237
|
+
await this.deleteFile(src, { force: true });
|
|
238
|
+
}
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Directory Operations
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
async mkdir(_path, _options) {
|
|
243
|
+
}
|
|
244
|
+
async rmdir(path, options) {
|
|
245
|
+
if (!options?.recursive) {
|
|
246
|
+
const bucket2 = await this.getReadyBucket();
|
|
247
|
+
const prefix2 = this.toKey(path).replace(/\/$/, "") + "/";
|
|
248
|
+
const [files] = await bucket2.getFiles({ prefix: prefix2, maxResults: 1 });
|
|
249
|
+
if (files.length > 0) {
|
|
250
|
+
throw new Error(`Directory not empty: ${path}`);
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const bucket = await this.getReadyBucket();
|
|
255
|
+
const prefix = this.toKey(path).replace(/\/$/, "") + "/";
|
|
256
|
+
await bucket.deleteFiles({ prefix });
|
|
257
|
+
}
|
|
258
|
+
async readdir(path, options) {
|
|
259
|
+
const bucket = await this.getReadyBucket();
|
|
260
|
+
const prefix = this.toKey(path).replace(/\/$/, "");
|
|
261
|
+
const searchPrefix = prefix ? prefix + "/" : "";
|
|
262
|
+
const entries = [];
|
|
263
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
264
|
+
const [files] = await bucket.getFiles({
|
|
265
|
+
prefix: searchPrefix,
|
|
266
|
+
autoPaginate: true
|
|
267
|
+
});
|
|
268
|
+
for (const file of files) {
|
|
269
|
+
const key = file.name;
|
|
270
|
+
if (!key || key === searchPrefix) continue;
|
|
271
|
+
const relativePath = key.slice(searchPrefix.length);
|
|
272
|
+
if (!relativePath) continue;
|
|
273
|
+
if (relativePath.endsWith("/")) {
|
|
274
|
+
const dirName = relativePath.slice(0, -1);
|
|
275
|
+
if (!seenDirs.has(dirName)) {
|
|
276
|
+
seenDirs.add(dirName);
|
|
277
|
+
entries.push({ name: dirName, type: "directory" });
|
|
278
|
+
}
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const name = options?.recursive ? relativePath : relativePath.split("/")[0];
|
|
282
|
+
if (!name) continue;
|
|
283
|
+
if (!options?.recursive && relativePath.includes("/")) {
|
|
284
|
+
if (!seenDirs.has(name)) {
|
|
285
|
+
seenDirs.add(name);
|
|
286
|
+
entries.push({ name, type: "directory" });
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (options?.extension) {
|
|
291
|
+
const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
|
|
292
|
+
if (!extensions.some((ext) => name.endsWith(ext))) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
entries.push({
|
|
297
|
+
name,
|
|
298
|
+
type: "file",
|
|
299
|
+
size: file.metadata.size != null ? Number(file.metadata.size) : void 0
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return entries;
|
|
303
|
+
}
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
// Path Operations
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
async exists(path) {
|
|
308
|
+
const key = this.toKey(path);
|
|
309
|
+
if (!key) return true;
|
|
310
|
+
const bucket = await this.getReadyBucket();
|
|
311
|
+
const file = bucket.file(key);
|
|
312
|
+
const [exists] = await file.exists();
|
|
313
|
+
if (exists) return true;
|
|
314
|
+
const [files] = await bucket.getFiles({
|
|
315
|
+
prefix: key.replace(/\/$/, "") + "/",
|
|
316
|
+
maxResults: 1
|
|
317
|
+
});
|
|
318
|
+
return files.length > 0;
|
|
319
|
+
}
|
|
320
|
+
async stat(path) {
|
|
321
|
+
const key = this.toKey(path);
|
|
322
|
+
if (!key) {
|
|
323
|
+
return {
|
|
324
|
+
name: "",
|
|
325
|
+
path,
|
|
326
|
+
type: "directory",
|
|
327
|
+
size: 0,
|
|
328
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
329
|
+
modifiedAt: /* @__PURE__ */ new Date()
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const bucket = await this.getReadyBucket();
|
|
333
|
+
const file = bucket.file(key);
|
|
334
|
+
const [exists] = await file.exists();
|
|
335
|
+
if (exists) {
|
|
336
|
+
const [metadata] = await file.getMetadata();
|
|
337
|
+
const name = path.split("/").pop() ?? "";
|
|
338
|
+
return {
|
|
339
|
+
name,
|
|
340
|
+
path,
|
|
341
|
+
type: "file",
|
|
342
|
+
size: Number(metadata.size) || 0,
|
|
343
|
+
createdAt: metadata.timeCreated ? new Date(metadata.timeCreated) : /* @__PURE__ */ new Date(),
|
|
344
|
+
modifiedAt: metadata.updated ? new Date(metadata.updated) : /* @__PURE__ */ new Date()
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
const isDir = await this.isDirectory(path);
|
|
348
|
+
if (isDir) {
|
|
349
|
+
const name = path.split("/").filter(Boolean).pop() ?? "";
|
|
350
|
+
return {
|
|
351
|
+
name,
|
|
352
|
+
path,
|
|
353
|
+
type: "directory",
|
|
354
|
+
size: 0,
|
|
355
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
356
|
+
modifiedAt: /* @__PURE__ */ new Date()
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
throw new workspace.FileNotFoundError(path);
|
|
360
|
+
}
|
|
361
|
+
async isFile(path) {
|
|
362
|
+
const key = this.toKey(path);
|
|
363
|
+
if (!key) return false;
|
|
364
|
+
const bucket = await this.getReadyBucket();
|
|
365
|
+
const file = bucket.file(key);
|
|
366
|
+
const [exists] = await file.exists();
|
|
367
|
+
return exists;
|
|
368
|
+
}
|
|
369
|
+
async isDirectory(path) {
|
|
370
|
+
const key = this.toKey(path);
|
|
371
|
+
if (!key) return true;
|
|
372
|
+
const bucket = await this.getReadyBucket();
|
|
373
|
+
const [files] = await bucket.getFiles({
|
|
374
|
+
prefix: key.replace(/\/$/, "") + "/",
|
|
375
|
+
maxResults: 1
|
|
376
|
+
});
|
|
377
|
+
return files.length > 0;
|
|
378
|
+
}
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
// Lifecycle (overrides base class protected methods)
|
|
381
|
+
// ---------------------------------------------------------------------------
|
|
382
|
+
/**
|
|
383
|
+
* Initialize the GCS client.
|
|
384
|
+
* Status management is handled by the base class.
|
|
385
|
+
*/
|
|
386
|
+
async init() {
|
|
387
|
+
const bucket = this.getBucket();
|
|
388
|
+
try {
|
|
389
|
+
const [exists] = await bucket.exists();
|
|
390
|
+
if (!exists) {
|
|
391
|
+
const err = new Error(`Bucket "${this.bucketName}" does not exist`);
|
|
392
|
+
err.status = 404;
|
|
393
|
+
throw err;
|
|
394
|
+
}
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (error.status) {
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
const code = error.code;
|
|
400
|
+
if (typeof code === "number") {
|
|
401
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
402
|
+
const err = new Error(
|
|
403
|
+
message
|
|
404
|
+
// code === 403
|
|
405
|
+
// ? `Access denied to bucket "${this.bucketName}" - check credentials and permissions`
|
|
406
|
+
// : message,
|
|
407
|
+
);
|
|
408
|
+
err.status = code;
|
|
409
|
+
throw err;
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Clean up the GCS client.
|
|
416
|
+
* Status management is handled by the base class.
|
|
417
|
+
*/
|
|
418
|
+
async destroy() {
|
|
419
|
+
this._storage = null;
|
|
420
|
+
this._bucket = null;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
exports.GCSFilesystem = GCSFilesystem;
|
|
425
|
+
//# sourceMappingURL=index.cjs.map
|
|
426
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/filesystem/index.ts"],"names":["MastraFilesystem","Storage","FileNotFoundError","bucket","prefix"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * GCS Filesystem Provider\n *\n * A filesystem implementation backed by Google Cloud Storage.\n */\n\nimport { Storage } from '@google-cloud/storage';\nimport type { Bucket } from '@google-cloud/storage';\n\nimport type {\n FileContent,\n FileStat,\n FileEntry,\n ReadOptions,\n WriteOptions,\n ListOptions,\n RemoveOptions,\n CopyOptions,\n FilesystemInfo,\n FilesystemMountConfig,\n FilesystemIcon,\n ProviderStatus,\n} from '@mastra/core/workspace';\nimport { MastraFilesystem, FileNotFoundError } from '@mastra/core/workspace';\n\n/**\n * GCS mount configuration.\n * Returned by GCSFilesystem.getMountConfig() for FUSE mounting in sandboxes.\n */\nexport interface GCSMountConfig extends FilesystemMountConfig {\n type: 'gcs';\n /** GCS bucket name */\n bucket: string;\n /** Service account key JSON (optional - omit for public buckets or ADC) */\n serviceAccountKey?: string;\n}\n\n/**\n * Common MIME types by file extension.\n */\nconst MIME_TYPES: Record<string, string> = {\n // Text\n '.txt': 'text/plain',\n '.md': 'text/markdown',\n '.markdown': 'text/markdown',\n '.html': 'text/html',\n '.htm': 'text/html',\n '.css': 'text/css',\n '.csv': 'text/csv',\n '.xml': 'text/xml',\n // Code\n '.js': 'text/javascript',\n '.mjs': 'text/javascript',\n '.ts': 'text/typescript',\n '.tsx': 'text/typescript',\n '.jsx': 'text/javascript',\n '.json': 'application/json',\n '.yaml': 'text/yaml',\n '.yml': 'text/yaml',\n '.py': 'text/x-python',\n '.rb': 'text/x-ruby',\n '.sh': 'text/x-shellscript',\n '.bash': 'text/x-shellscript',\n // Images\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n // Documents\n '.pdf': 'application/pdf',\n // Archives\n '.zip': 'application/zip',\n '.gz': 'application/gzip',\n '.tar': 'application/x-tar',\n};\n\n/**\n * Get MIME type from file path extension.\n */\nfunction getMimeType(path: string): string {\n const ext = path.toLowerCase().match(/\\.[^.]+$/)?.[0];\n return ext ? (MIME_TYPES[ext] ?? 'application/octet-stream') : 'application/octet-stream';\n}\n\n/**\n * GCS filesystem provider configuration.\n */\nexport interface GCSFilesystemOptions {\n /** Unique identifier for this filesystem instance */\n id?: string;\n /** GCS bucket name */\n bucket: string;\n /** Human-friendly display name for the UI */\n displayName?: string;\n /** Icon identifier for the UI (defaults to 'gcs') */\n icon?: FilesystemIcon;\n /** Description shown in tooltips */\n description?: string;\n /**\n * GCS project ID.\n * Required when using service account credentials.\n */\n projectId?: string;\n /**\n * Service account key JSON object or path to key file.\n * If not provided, uses Application Default Credentials (ADC).\n */\n credentials?: object | string;\n /** Optional prefix for all keys (acts like a subdirectory) */\n prefix?: string;\n /** Mount as read-only (blocks write operations, mounts read-only in sandboxes) */\n readOnly?: boolean;\n /**\n * Custom API endpoint URL.\n * Used for local development with emulators like fake-gcs-server.\n */\n endpoint?: string;\n}\n\n/**\n * GCS filesystem implementation.\n *\n * Stores files in a Google Cloud Storage bucket.\n * Supports mounting into E2B sandboxes via gcsfuse.\n *\n * @example Using Application Default Credentials\n * ```typescript\n * import { GCSFilesystem } from '@mastra/gcs';\n *\n * // Uses ADC (gcloud auth application-default login)\n * const fs = new GCSFilesystem({\n * bucket: 'my-bucket',\n * projectId: 'my-project',\n * });\n * ```\n *\n * @example Using Service Account Key\n * ```typescript\n * import { GCSFilesystem } from '@mastra/gcs';\n *\n * const fs = new GCSFilesystem({\n * bucket: 'my-bucket',\n * projectId: 'my-project',\n * credentials: {\n * type: 'service_account',\n * project_id: 'my-project',\n * private_key_id: '...',\n * private_key: '-----BEGIN PRIVATE KEY-----\\n...',\n * client_email: '...@...iam.gserviceaccount.com',\n * // ... rest of service account key\n * },\n * });\n * ```\n *\n * @example Using Key File Path\n * ```typescript\n * import { GCSFilesystem } from '@mastra/gcs';\n *\n * const fs = new GCSFilesystem({\n * bucket: 'my-bucket',\n * projectId: 'my-project',\n * credentials: '/path/to/service-account-key.json',\n * });\n * ```\n */\n\n/** Trim leading and trailing slashes without regex (avoids polynomial regex on user input). */\nfunction trimSlashes(s: string): string {\n let start = 0;\n let end = s.length;\n while (start < end && s[start] === '/') start++;\n while (end > start && s[end - 1] === '/') end--;\n return s.slice(start, end);\n}\n\nexport class GCSFilesystem extends MastraFilesystem {\n readonly id: string;\n readonly name = 'GCSFilesystem';\n readonly provider = 'gcs';\n readonly readOnly?: boolean;\n\n status: ProviderStatus = 'pending';\n\n // Display metadata for UI\n readonly displayName?: string;\n readonly icon: FilesystemIcon = 'gcs';\n readonly description?: string;\n\n private readonly bucketName: string;\n private readonly projectId?: string;\n private readonly credentials?: object | string;\n private readonly prefix: string;\n private readonly endpoint?: string;\n\n private _storage: Storage | null = null;\n private _bucket: Bucket | null = null;\n\n constructor(options: GCSFilesystemOptions) {\n super({ name: 'GCSFilesystem' });\n this.id = options.id ?? `gcs-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n this.bucketName = options.bucket;\n this.projectId = options.projectId;\n this.credentials = options.credentials;\n // Trim leading/trailing slashes from prefix using iterative approach (avoids polynomial regex)\n this.prefix = options.prefix ? trimSlashes(options.prefix) + '/' : '';\n this.endpoint = options.endpoint;\n\n // Display metadata\n this.displayName = options.displayName ?? 'Google Cloud Storage';\n this.icon = options.icon ?? 'gcs';\n this.description = options.description;\n this.readOnly = options.readOnly;\n }\n\n /**\n * Get mount configuration for E2B sandbox.\n * Returns GCS-compatible config that works with gcsfuse.\n */\n getMountConfig(): GCSMountConfig {\n const config: GCSMountConfig = {\n type: 'gcs',\n bucket: this.bucketName,\n };\n\n // Include service account key if credentials are an object\n if (this.credentials && typeof this.credentials === 'object') {\n config.serviceAccountKey = JSON.stringify(this.credentials);\n }\n\n return config;\n }\n\n /**\n * Get filesystem info for status reporting.\n */\n getInfo(): FilesystemInfo {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n icon: this.icon,\n metadata: {\n bucket: this.bucketName,\n ...(this.endpoint && { endpoint: this.endpoint }),\n ...(this.prefix && { prefix: this.prefix }),\n },\n };\n }\n\n /**\n * Get instructions describing this GCS filesystem.\n * Used by agents to understand storage semantics.\n */\n getInstructions(): string {\n const access = this.readOnly ? 'Read-only' : 'Persistent';\n return `Google Cloud Storage in bucket \"${this.bucketName}\". ${access} storage - files are retained across sessions.`;\n }\n\n private getStorage(): Storage {\n if (this._storage) return this._storage;\n\n const options: { projectId?: string; credentials?: object; keyFilename?: string; apiEndpoint?: string } = {};\n\n if (this.projectId) {\n options.projectId = this.projectId;\n }\n\n if (this.credentials) {\n if (typeof this.credentials === 'string') {\n // Path to key file\n options.keyFilename = this.credentials;\n } else {\n // Credentials object\n options.credentials = this.credentials;\n }\n }\n\n if (this.endpoint) {\n options.apiEndpoint = this.endpoint;\n }\n\n this._storage = new Storage(options);\n return this._storage;\n }\n\n private getBucket(): Bucket {\n if (this._bucket) return this._bucket;\n\n const storage = this.getStorage();\n this._bucket = storage.bucket(this.bucketName);\n return this._bucket;\n }\n\n /**\n * Ensure the filesystem is initialized and return the bucket.\n * Uses base class ensureReady() for status management, then returns bucket.\n */\n private async getReadyBucket(): Promise<Bucket> {\n await this.ensureReady();\n return this.getBucket();\n }\n\n private toKey(path: string): string {\n // Remove leading slash and add prefix\n const cleanPath = path.replace(/^\\/+/, '');\n return this.prefix + cleanPath;\n }\n\n // ---------------------------------------------------------------------------\n // File Operations\n // ---------------------------------------------------------------------------\n\n async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {\n const bucket = await this.getReadyBucket();\n const file = bucket.file(this.toKey(path));\n\n try {\n const [content] = await file.download();\n\n if (options?.encoding) {\n return content.toString(options.encoding);\n }\n return content;\n } catch (error: unknown) {\n if (error && typeof error === 'object' && 'code' in error && error.code === 404) {\n throw new FileNotFoundError(path);\n }\n throw error;\n }\n }\n\n async writeFile(path: string, content: FileContent, _options?: WriteOptions): Promise<void> {\n const bucket = await this.getReadyBucket();\n const file = bucket.file(this.toKey(path));\n\n const body = typeof content === 'string' ? Buffer.from(content, 'utf-8') : Buffer.from(content);\n const contentType = getMimeType(path);\n\n await file.save(body, {\n contentType,\n resumable: false,\n });\n }\n\n async appendFile(path: string, content: FileContent): Promise<void> {\n // GCS doesn't support append, so read + write\n let existing = '';\n try {\n existing = (await this.readFile(path, { encoding: 'utf-8' })) as string;\n } catch (error) {\n if (error instanceof FileNotFoundError) {\n // File doesn't exist, start fresh\n } else {\n throw error;\n }\n }\n\n const appendContent = typeof content === 'string' ? content : Buffer.from(content).toString('utf-8');\n await this.writeFile(path, existing + appendContent);\n }\n\n async deleteFile(path: string, options?: RemoveOptions): Promise<void> {\n // Check if this is a directory - if so, use rmdir instead\n const isDir = await this.isDirectory(path);\n if (isDir) {\n await this.rmdir(path, { recursive: true, force: options?.force });\n return;\n }\n\n const bucket = await this.getReadyBucket();\n const file = bucket.file(this.toKey(path));\n\n try {\n await file.delete();\n } catch (error: unknown) {\n if (!options?.force) {\n if (error && typeof error === 'object' && 'code' in error && error.code === 404) {\n throw new FileNotFoundError(path);\n }\n throw error;\n }\n }\n }\n\n async copyFile(src: string, dest: string, _options?: CopyOptions): Promise<void> {\n const bucket = await this.getReadyBucket();\n const srcFile = bucket.file(this.toKey(src));\n const destFile = bucket.file(this.toKey(dest));\n\n try {\n await srcFile.copy(destFile);\n } catch (error: unknown) {\n if (error && typeof error === 'object' && 'code' in error && error.code === 404) {\n throw new FileNotFoundError(src);\n }\n throw error;\n }\n }\n\n async moveFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n await this.copyFile(src, dest, options);\n await this.deleteFile(src, { force: true });\n }\n\n // ---------------------------------------------------------------------------\n // Directory Operations\n // ---------------------------------------------------------------------------\n\n async mkdir(_path: string, _options?: { recursive?: boolean }): Promise<void> {\n // GCS doesn't have real directories - they're just key prefixes\n // No-op, directories are created implicitly when files are written\n }\n\n async rmdir(path: string, options?: RemoveOptions): Promise<void> {\n if (!options?.recursive) {\n // Quick emptiness check — only fetch one object instead of full readdir\n const bucket = await this.getReadyBucket();\n const prefix = this.toKey(path).replace(/\\/$/, '') + '/';\n const [files] = await bucket.getFiles({ prefix, maxResults: 1 });\n if (files.length > 0) {\n throw new Error(`Directory not empty: ${path}`);\n }\n return;\n }\n\n // Delete all objects with this prefix\n const bucket = await this.getReadyBucket();\n const prefix = this.toKey(path).replace(/\\/$/, '') + '/';\n\n await bucket.deleteFiles({ prefix });\n }\n\n async readdir(path: string, options?: ListOptions): Promise<FileEntry[]> {\n const bucket = await this.getReadyBucket();\n\n const prefix = this.toKey(path).replace(/\\/$/, '');\n const searchPrefix = prefix ? prefix + '/' : '';\n\n const entries: FileEntry[] = [];\n const seenDirs = new Set<string>();\n\n const [files] = await bucket.getFiles({\n prefix: searchPrefix,\n autoPaginate: true,\n });\n\n for (const file of files) {\n const key = file.name;\n if (!key || key === searchPrefix) continue;\n\n const relativePath = key.slice(searchPrefix.length);\n if (!relativePath) continue;\n\n // Skip if this looks like a directory marker\n if (relativePath.endsWith('/')) {\n const dirName = relativePath.slice(0, -1);\n if (!seenDirs.has(dirName)) {\n seenDirs.add(dirName);\n entries.push({ name: dirName, type: 'directory' });\n }\n continue;\n }\n\n const name = options?.recursive ? relativePath : relativePath.split('/')[0];\n\n // Skip if name is undefined or empty\n if (!name) continue;\n\n // For non-recursive listing, if there's a slash, it's a directory\n if (!options?.recursive && relativePath.includes('/')) {\n if (!seenDirs.has(name)) {\n seenDirs.add(name);\n entries.push({ name, type: 'directory' });\n }\n continue;\n }\n\n // Filter by extension if specified\n if (options?.extension) {\n const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];\n if (!extensions.some(ext => name.endsWith(ext))) {\n continue;\n }\n }\n\n entries.push({\n name,\n type: 'file',\n size: file.metadata.size != null ? Number(file.metadata.size) : undefined,\n });\n }\n\n return entries;\n }\n\n // ---------------------------------------------------------------------------\n // Path Operations\n // ---------------------------------------------------------------------------\n\n async exists(path: string): Promise<boolean> {\n const key = this.toKey(path);\n\n // Root path always exists (it's the bucket itself)\n if (!key) return true;\n\n const bucket = await this.getReadyBucket();\n const file = bucket.file(key);\n\n // Check if it's a file\n const [exists] = await file.exists();\n if (exists) return true;\n\n // Check if it's a \"directory\" (has objects with this prefix)\n const [files] = await bucket.getFiles({\n prefix: key.replace(/\\/$/, '') + '/',\n maxResults: 1,\n });\n\n return files.length > 0;\n }\n\n async stat(path: string): Promise<FileStat> {\n const key = this.toKey(path);\n\n // Root path is always a directory\n if (!key) {\n return {\n name: '',\n path,\n type: 'directory',\n size: 0,\n createdAt: new Date(),\n modifiedAt: new Date(),\n };\n }\n\n const bucket = await this.getReadyBucket();\n const file = bucket.file(key);\n\n const [exists] = await file.exists();\n if (exists) {\n const [metadata] = await file.getMetadata();\n const name = path.split('/').pop() ?? '';\n\n return {\n name,\n path,\n type: 'file',\n size: Number(metadata.size) || 0,\n createdAt: metadata.timeCreated ? new Date(metadata.timeCreated) : new Date(),\n modifiedAt: metadata.updated ? new Date(metadata.updated) : new Date(),\n };\n }\n\n // Check if it's a directory\n const isDir = await this.isDirectory(path);\n if (isDir) {\n const name = path.split('/').filter(Boolean).pop() ?? '';\n return {\n name,\n path,\n type: 'directory',\n size: 0,\n createdAt: new Date(),\n modifiedAt: new Date(),\n };\n }\n\n throw new FileNotFoundError(path);\n }\n\n async isFile(path: string): Promise<boolean> {\n const key = this.toKey(path);\n if (!key) return false; // Root is a directory, not a file\n\n const bucket = await this.getReadyBucket();\n const file = bucket.file(key);\n\n const [exists] = await file.exists();\n return exists;\n }\n\n async isDirectory(path: string): Promise<boolean> {\n const key = this.toKey(path);\n if (!key) return true; // Root is always a directory\n\n const bucket = await this.getReadyBucket();\n\n const [files] = await bucket.getFiles({\n prefix: key.replace(/\\/$/, '') + '/',\n maxResults: 1,\n });\n\n return files.length > 0;\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle (overrides base class protected methods)\n // ---------------------------------------------------------------------------\n\n /**\n * Initialize the GCS client.\n * Status management is handled by the base class.\n */\n async init(): Promise<void> {\n // Verify we can access the bucket\n const bucket = this.getBucket();\n try {\n const [exists] = await bucket.exists();\n if (!exists) {\n const err = new Error(`Bucket \"${this.bucketName}\" does not exist`) as Error & { status?: number };\n err.status = 404;\n throw err;\n }\n } catch (error) {\n // Check if error already has status (from our 404 throw above)\n if ((error as { status?: number }).status) {\n throw error;\n }\n // Extract status code from GCS errors and add to error for proper HTTP response\n const code = (error as { code?: number }).code;\n if (typeof code === 'number') {\n const message = error instanceof Error ? error.message : String(error);\n const err = new Error(\n message,\n // code === 403\n // ? `Access denied to bucket \"${this.bucketName}\" - check credentials and permissions`\n // : message,\n ) as Error & { status?: number };\n err.status = code;\n throw err;\n }\n throw error;\n }\n }\n\n /**\n * Clean up the GCS client.\n * Status management is handled by the base class.\n */\n async destroy(): Promise<void> {\n this._storage = null;\n this._bucket = null;\n }\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mastra/gcs - Google Cloud Storage Filesystem Provider
|
|
3
|
+
*
|
|
4
|
+
* A filesystem implementation backed by Google Cloud Storage.
|
|
5
|
+
*/
|
|
6
|
+
export { GCSFilesystem, type GCSFilesystemOptions, type GCSMountConfig } from './filesystem/index.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { Storage } from '@google-cloud/storage';
|
|
2
|
+
import { MastraFilesystem, FileNotFoundError } from '@mastra/core/workspace';
|
|
3
|
+
|
|
4
|
+
// src/filesystem/index.ts
|
|
5
|
+
var MIME_TYPES = {
|
|
6
|
+
// Text
|
|
7
|
+
".txt": "text/plain",
|
|
8
|
+
".md": "text/markdown",
|
|
9
|
+
".markdown": "text/markdown",
|
|
10
|
+
".html": "text/html",
|
|
11
|
+
".htm": "text/html",
|
|
12
|
+
".css": "text/css",
|
|
13
|
+
".csv": "text/csv",
|
|
14
|
+
".xml": "text/xml",
|
|
15
|
+
// Code
|
|
16
|
+
".js": "text/javascript",
|
|
17
|
+
".mjs": "text/javascript",
|
|
18
|
+
".ts": "text/typescript",
|
|
19
|
+
".tsx": "text/typescript",
|
|
20
|
+
".jsx": "text/javascript",
|
|
21
|
+
".json": "application/json",
|
|
22
|
+
".yaml": "text/yaml",
|
|
23
|
+
".yml": "text/yaml",
|
|
24
|
+
".py": "text/x-python",
|
|
25
|
+
".rb": "text/x-ruby",
|
|
26
|
+
".sh": "text/x-shellscript",
|
|
27
|
+
".bash": "text/x-shellscript",
|
|
28
|
+
// Images
|
|
29
|
+
".png": "image/png",
|
|
30
|
+
".jpg": "image/jpeg",
|
|
31
|
+
".jpeg": "image/jpeg",
|
|
32
|
+
".gif": "image/gif",
|
|
33
|
+
".svg": "image/svg+xml",
|
|
34
|
+
".webp": "image/webp",
|
|
35
|
+
".ico": "image/x-icon",
|
|
36
|
+
// Documents
|
|
37
|
+
".pdf": "application/pdf",
|
|
38
|
+
// Archives
|
|
39
|
+
".zip": "application/zip",
|
|
40
|
+
".gz": "application/gzip",
|
|
41
|
+
".tar": "application/x-tar"
|
|
42
|
+
};
|
|
43
|
+
function getMimeType(path) {
|
|
44
|
+
const ext = path.toLowerCase().match(/\.[^.]+$/)?.[0];
|
|
45
|
+
return ext ? MIME_TYPES[ext] ?? "application/octet-stream" : "application/octet-stream";
|
|
46
|
+
}
|
|
47
|
+
function trimSlashes(s) {
|
|
48
|
+
let start = 0;
|
|
49
|
+
let end = s.length;
|
|
50
|
+
while (start < end && s[start] === "/") start++;
|
|
51
|
+
while (end > start && s[end - 1] === "/") end--;
|
|
52
|
+
return s.slice(start, end);
|
|
53
|
+
}
|
|
54
|
+
var GCSFilesystem = class extends MastraFilesystem {
|
|
55
|
+
id;
|
|
56
|
+
name = "GCSFilesystem";
|
|
57
|
+
provider = "gcs";
|
|
58
|
+
readOnly;
|
|
59
|
+
status = "pending";
|
|
60
|
+
// Display metadata for UI
|
|
61
|
+
displayName;
|
|
62
|
+
icon = "gcs";
|
|
63
|
+
description;
|
|
64
|
+
bucketName;
|
|
65
|
+
projectId;
|
|
66
|
+
credentials;
|
|
67
|
+
prefix;
|
|
68
|
+
endpoint;
|
|
69
|
+
_storage = null;
|
|
70
|
+
_bucket = null;
|
|
71
|
+
constructor(options) {
|
|
72
|
+
super({ name: "GCSFilesystem" });
|
|
73
|
+
this.id = options.id ?? `gcs-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
74
|
+
this.bucketName = options.bucket;
|
|
75
|
+
this.projectId = options.projectId;
|
|
76
|
+
this.credentials = options.credentials;
|
|
77
|
+
this.prefix = options.prefix ? trimSlashes(options.prefix) + "/" : "";
|
|
78
|
+
this.endpoint = options.endpoint;
|
|
79
|
+
this.displayName = options.displayName ?? "Google Cloud Storage";
|
|
80
|
+
this.icon = options.icon ?? "gcs";
|
|
81
|
+
this.description = options.description;
|
|
82
|
+
this.readOnly = options.readOnly;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get mount configuration for E2B sandbox.
|
|
86
|
+
* Returns GCS-compatible config that works with gcsfuse.
|
|
87
|
+
*/
|
|
88
|
+
getMountConfig() {
|
|
89
|
+
const config = {
|
|
90
|
+
type: "gcs",
|
|
91
|
+
bucket: this.bucketName
|
|
92
|
+
};
|
|
93
|
+
if (this.credentials && typeof this.credentials === "object") {
|
|
94
|
+
config.serviceAccountKey = JSON.stringify(this.credentials);
|
|
95
|
+
}
|
|
96
|
+
return config;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get filesystem info for status reporting.
|
|
100
|
+
*/
|
|
101
|
+
getInfo() {
|
|
102
|
+
return {
|
|
103
|
+
id: this.id,
|
|
104
|
+
name: this.name,
|
|
105
|
+
provider: this.provider,
|
|
106
|
+
status: this.status,
|
|
107
|
+
icon: this.icon,
|
|
108
|
+
metadata: {
|
|
109
|
+
bucket: this.bucketName,
|
|
110
|
+
...this.endpoint && { endpoint: this.endpoint },
|
|
111
|
+
...this.prefix && { prefix: this.prefix }
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get instructions describing this GCS filesystem.
|
|
117
|
+
* Used by agents to understand storage semantics.
|
|
118
|
+
*/
|
|
119
|
+
getInstructions() {
|
|
120
|
+
const access = this.readOnly ? "Read-only" : "Persistent";
|
|
121
|
+
return `Google Cloud Storage in bucket "${this.bucketName}". ${access} storage - files are retained across sessions.`;
|
|
122
|
+
}
|
|
123
|
+
getStorage() {
|
|
124
|
+
if (this._storage) return this._storage;
|
|
125
|
+
const options = {};
|
|
126
|
+
if (this.projectId) {
|
|
127
|
+
options.projectId = this.projectId;
|
|
128
|
+
}
|
|
129
|
+
if (this.credentials) {
|
|
130
|
+
if (typeof this.credentials === "string") {
|
|
131
|
+
options.keyFilename = this.credentials;
|
|
132
|
+
} else {
|
|
133
|
+
options.credentials = this.credentials;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (this.endpoint) {
|
|
137
|
+
options.apiEndpoint = this.endpoint;
|
|
138
|
+
}
|
|
139
|
+
this._storage = new Storage(options);
|
|
140
|
+
return this._storage;
|
|
141
|
+
}
|
|
142
|
+
getBucket() {
|
|
143
|
+
if (this._bucket) return this._bucket;
|
|
144
|
+
const storage = this.getStorage();
|
|
145
|
+
this._bucket = storage.bucket(this.bucketName);
|
|
146
|
+
return this._bucket;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Ensure the filesystem is initialized and return the bucket.
|
|
150
|
+
* Uses base class ensureReady() for status management, then returns bucket.
|
|
151
|
+
*/
|
|
152
|
+
async getReadyBucket() {
|
|
153
|
+
await this.ensureReady();
|
|
154
|
+
return this.getBucket();
|
|
155
|
+
}
|
|
156
|
+
toKey(path) {
|
|
157
|
+
const cleanPath = path.replace(/^\/+/, "");
|
|
158
|
+
return this.prefix + cleanPath;
|
|
159
|
+
}
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// File Operations
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
async readFile(path, options) {
|
|
164
|
+
const bucket = await this.getReadyBucket();
|
|
165
|
+
const file = bucket.file(this.toKey(path));
|
|
166
|
+
try {
|
|
167
|
+
const [content] = await file.download();
|
|
168
|
+
if (options?.encoding) {
|
|
169
|
+
return content.toString(options.encoding);
|
|
170
|
+
}
|
|
171
|
+
return content;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error && typeof error === "object" && "code" in error && error.code === 404) {
|
|
174
|
+
throw new FileNotFoundError(path);
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async writeFile(path, content, _options) {
|
|
180
|
+
const bucket = await this.getReadyBucket();
|
|
181
|
+
const file = bucket.file(this.toKey(path));
|
|
182
|
+
const body = typeof content === "string" ? Buffer.from(content, "utf-8") : Buffer.from(content);
|
|
183
|
+
const contentType = getMimeType(path);
|
|
184
|
+
await file.save(body, {
|
|
185
|
+
contentType,
|
|
186
|
+
resumable: false
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async appendFile(path, content) {
|
|
190
|
+
let existing = "";
|
|
191
|
+
try {
|
|
192
|
+
existing = await this.readFile(path, { encoding: "utf-8" });
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (error instanceof FileNotFoundError) ; else {
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const appendContent = typeof content === "string" ? content : Buffer.from(content).toString("utf-8");
|
|
199
|
+
await this.writeFile(path, existing + appendContent);
|
|
200
|
+
}
|
|
201
|
+
async deleteFile(path, options) {
|
|
202
|
+
const isDir = await this.isDirectory(path);
|
|
203
|
+
if (isDir) {
|
|
204
|
+
await this.rmdir(path, { recursive: true, force: options?.force });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const bucket = await this.getReadyBucket();
|
|
208
|
+
const file = bucket.file(this.toKey(path));
|
|
209
|
+
try {
|
|
210
|
+
await file.delete();
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (!options?.force) {
|
|
213
|
+
if (error && typeof error === "object" && "code" in error && error.code === 404) {
|
|
214
|
+
throw new FileNotFoundError(path);
|
|
215
|
+
}
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async copyFile(src, dest, _options) {
|
|
221
|
+
const bucket = await this.getReadyBucket();
|
|
222
|
+
const srcFile = bucket.file(this.toKey(src));
|
|
223
|
+
const destFile = bucket.file(this.toKey(dest));
|
|
224
|
+
try {
|
|
225
|
+
await srcFile.copy(destFile);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
if (error && typeof error === "object" && "code" in error && error.code === 404) {
|
|
228
|
+
throw new FileNotFoundError(src);
|
|
229
|
+
}
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async moveFile(src, dest, options) {
|
|
234
|
+
await this.copyFile(src, dest, options);
|
|
235
|
+
await this.deleteFile(src, { force: true });
|
|
236
|
+
}
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// Directory Operations
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
async mkdir(_path, _options) {
|
|
241
|
+
}
|
|
242
|
+
async rmdir(path, options) {
|
|
243
|
+
if (!options?.recursive) {
|
|
244
|
+
const bucket2 = await this.getReadyBucket();
|
|
245
|
+
const prefix2 = this.toKey(path).replace(/\/$/, "") + "/";
|
|
246
|
+
const [files] = await bucket2.getFiles({ prefix: prefix2, maxResults: 1 });
|
|
247
|
+
if (files.length > 0) {
|
|
248
|
+
throw new Error(`Directory not empty: ${path}`);
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const bucket = await this.getReadyBucket();
|
|
253
|
+
const prefix = this.toKey(path).replace(/\/$/, "") + "/";
|
|
254
|
+
await bucket.deleteFiles({ prefix });
|
|
255
|
+
}
|
|
256
|
+
async readdir(path, options) {
|
|
257
|
+
const bucket = await this.getReadyBucket();
|
|
258
|
+
const prefix = this.toKey(path).replace(/\/$/, "");
|
|
259
|
+
const searchPrefix = prefix ? prefix + "/" : "";
|
|
260
|
+
const entries = [];
|
|
261
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
262
|
+
const [files] = await bucket.getFiles({
|
|
263
|
+
prefix: searchPrefix,
|
|
264
|
+
autoPaginate: true
|
|
265
|
+
});
|
|
266
|
+
for (const file of files) {
|
|
267
|
+
const key = file.name;
|
|
268
|
+
if (!key || key === searchPrefix) continue;
|
|
269
|
+
const relativePath = key.slice(searchPrefix.length);
|
|
270
|
+
if (!relativePath) continue;
|
|
271
|
+
if (relativePath.endsWith("/")) {
|
|
272
|
+
const dirName = relativePath.slice(0, -1);
|
|
273
|
+
if (!seenDirs.has(dirName)) {
|
|
274
|
+
seenDirs.add(dirName);
|
|
275
|
+
entries.push({ name: dirName, type: "directory" });
|
|
276
|
+
}
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const name = options?.recursive ? relativePath : relativePath.split("/")[0];
|
|
280
|
+
if (!name) continue;
|
|
281
|
+
if (!options?.recursive && relativePath.includes("/")) {
|
|
282
|
+
if (!seenDirs.has(name)) {
|
|
283
|
+
seenDirs.add(name);
|
|
284
|
+
entries.push({ name, type: "directory" });
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (options?.extension) {
|
|
289
|
+
const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
|
|
290
|
+
if (!extensions.some((ext) => name.endsWith(ext))) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
entries.push({
|
|
295
|
+
name,
|
|
296
|
+
type: "file",
|
|
297
|
+
size: file.metadata.size != null ? Number(file.metadata.size) : void 0
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return entries;
|
|
301
|
+
}
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Path Operations
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
async exists(path) {
|
|
306
|
+
const key = this.toKey(path);
|
|
307
|
+
if (!key) return true;
|
|
308
|
+
const bucket = await this.getReadyBucket();
|
|
309
|
+
const file = bucket.file(key);
|
|
310
|
+
const [exists] = await file.exists();
|
|
311
|
+
if (exists) return true;
|
|
312
|
+
const [files] = await bucket.getFiles({
|
|
313
|
+
prefix: key.replace(/\/$/, "") + "/",
|
|
314
|
+
maxResults: 1
|
|
315
|
+
});
|
|
316
|
+
return files.length > 0;
|
|
317
|
+
}
|
|
318
|
+
async stat(path) {
|
|
319
|
+
const key = this.toKey(path);
|
|
320
|
+
if (!key) {
|
|
321
|
+
return {
|
|
322
|
+
name: "",
|
|
323
|
+
path,
|
|
324
|
+
type: "directory",
|
|
325
|
+
size: 0,
|
|
326
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
327
|
+
modifiedAt: /* @__PURE__ */ new Date()
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const bucket = await this.getReadyBucket();
|
|
331
|
+
const file = bucket.file(key);
|
|
332
|
+
const [exists] = await file.exists();
|
|
333
|
+
if (exists) {
|
|
334
|
+
const [metadata] = await file.getMetadata();
|
|
335
|
+
const name = path.split("/").pop() ?? "";
|
|
336
|
+
return {
|
|
337
|
+
name,
|
|
338
|
+
path,
|
|
339
|
+
type: "file",
|
|
340
|
+
size: Number(metadata.size) || 0,
|
|
341
|
+
createdAt: metadata.timeCreated ? new Date(metadata.timeCreated) : /* @__PURE__ */ new Date(),
|
|
342
|
+
modifiedAt: metadata.updated ? new Date(metadata.updated) : /* @__PURE__ */ new Date()
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
const isDir = await this.isDirectory(path);
|
|
346
|
+
if (isDir) {
|
|
347
|
+
const name = path.split("/").filter(Boolean).pop() ?? "";
|
|
348
|
+
return {
|
|
349
|
+
name,
|
|
350
|
+
path,
|
|
351
|
+
type: "directory",
|
|
352
|
+
size: 0,
|
|
353
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
354
|
+
modifiedAt: /* @__PURE__ */ new Date()
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
throw new FileNotFoundError(path);
|
|
358
|
+
}
|
|
359
|
+
async isFile(path) {
|
|
360
|
+
const key = this.toKey(path);
|
|
361
|
+
if (!key) return false;
|
|
362
|
+
const bucket = await this.getReadyBucket();
|
|
363
|
+
const file = bucket.file(key);
|
|
364
|
+
const [exists] = await file.exists();
|
|
365
|
+
return exists;
|
|
366
|
+
}
|
|
367
|
+
async isDirectory(path) {
|
|
368
|
+
const key = this.toKey(path);
|
|
369
|
+
if (!key) return true;
|
|
370
|
+
const bucket = await this.getReadyBucket();
|
|
371
|
+
const [files] = await bucket.getFiles({
|
|
372
|
+
prefix: key.replace(/\/$/, "") + "/",
|
|
373
|
+
maxResults: 1
|
|
374
|
+
});
|
|
375
|
+
return files.length > 0;
|
|
376
|
+
}
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
// Lifecycle (overrides base class protected methods)
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
/**
|
|
381
|
+
* Initialize the GCS client.
|
|
382
|
+
* Status management is handled by the base class.
|
|
383
|
+
*/
|
|
384
|
+
async init() {
|
|
385
|
+
const bucket = this.getBucket();
|
|
386
|
+
try {
|
|
387
|
+
const [exists] = await bucket.exists();
|
|
388
|
+
if (!exists) {
|
|
389
|
+
const err = new Error(`Bucket "${this.bucketName}" does not exist`);
|
|
390
|
+
err.status = 404;
|
|
391
|
+
throw err;
|
|
392
|
+
}
|
|
393
|
+
} catch (error) {
|
|
394
|
+
if (error.status) {
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
const code = error.code;
|
|
398
|
+
if (typeof code === "number") {
|
|
399
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
400
|
+
const err = new Error(
|
|
401
|
+
message
|
|
402
|
+
// code === 403
|
|
403
|
+
// ? `Access denied to bucket "${this.bucketName}" - check credentials and permissions`
|
|
404
|
+
// : message,
|
|
405
|
+
);
|
|
406
|
+
err.status = code;
|
|
407
|
+
throw err;
|
|
408
|
+
}
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Clean up the GCS client.
|
|
414
|
+
* Status management is handled by the base class.
|
|
415
|
+
*/
|
|
416
|
+
async destroy() {
|
|
417
|
+
this._storage = null;
|
|
418
|
+
this._bucket = null;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
export { GCSFilesystem };
|
|
423
|
+
//# sourceMappingURL=index.js.map
|
|
424
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/filesystem/index.ts"],"names":["bucket","prefix"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * GCS Filesystem Provider\n *\n * A filesystem implementation backed by Google Cloud Storage.\n */\n\nimport { Storage } from '@google-cloud/storage';\nimport type { Bucket } from '@google-cloud/storage';\n\nimport type {\n FileContent,\n FileStat,\n FileEntry,\n ReadOptions,\n WriteOptions,\n ListOptions,\n RemoveOptions,\n CopyOptions,\n FilesystemInfo,\n FilesystemMountConfig,\n FilesystemIcon,\n ProviderStatus,\n} from '@mastra/core/workspace';\nimport { MastraFilesystem, FileNotFoundError } from '@mastra/core/workspace';\n\n/**\n * GCS mount configuration.\n * Returned by GCSFilesystem.getMountConfig() for FUSE mounting in sandboxes.\n */\nexport interface GCSMountConfig extends FilesystemMountConfig {\n type: 'gcs';\n /** GCS bucket name */\n bucket: string;\n /** Service account key JSON (optional - omit for public buckets or ADC) */\n serviceAccountKey?: string;\n}\n\n/**\n * Common MIME types by file extension.\n */\nconst MIME_TYPES: Record<string, string> = {\n // Text\n '.txt': 'text/plain',\n '.md': 'text/markdown',\n '.markdown': 'text/markdown',\n '.html': 'text/html',\n '.htm': 'text/html',\n '.css': 'text/css',\n '.csv': 'text/csv',\n '.xml': 'text/xml',\n // Code\n '.js': 'text/javascript',\n '.mjs': 'text/javascript',\n '.ts': 'text/typescript',\n '.tsx': 'text/typescript',\n '.jsx': 'text/javascript',\n '.json': 'application/json',\n '.yaml': 'text/yaml',\n '.yml': 'text/yaml',\n '.py': 'text/x-python',\n '.rb': 'text/x-ruby',\n '.sh': 'text/x-shellscript',\n '.bash': 'text/x-shellscript',\n // Images\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n // Documents\n '.pdf': 'application/pdf',\n // Archives\n '.zip': 'application/zip',\n '.gz': 'application/gzip',\n '.tar': 'application/x-tar',\n};\n\n/**\n * Get MIME type from file path extension.\n */\nfunction getMimeType(path: string): string {\n const ext = path.toLowerCase().match(/\\.[^.]+$/)?.[0];\n return ext ? (MIME_TYPES[ext] ?? 'application/octet-stream') : 'application/octet-stream';\n}\n\n/**\n * GCS filesystem provider configuration.\n */\nexport interface GCSFilesystemOptions {\n /** Unique identifier for this filesystem instance */\n id?: string;\n /** GCS bucket name */\n bucket: string;\n /** Human-friendly display name for the UI */\n displayName?: string;\n /** Icon identifier for the UI (defaults to 'gcs') */\n icon?: FilesystemIcon;\n /** Description shown in tooltips */\n description?: string;\n /**\n * GCS project ID.\n * Required when using service account credentials.\n */\n projectId?: string;\n /**\n * Service account key JSON object or path to key file.\n * If not provided, uses Application Default Credentials (ADC).\n */\n credentials?: object | string;\n /** Optional prefix for all keys (acts like a subdirectory) */\n prefix?: string;\n /** Mount as read-only (blocks write operations, mounts read-only in sandboxes) */\n readOnly?: boolean;\n /**\n * Custom API endpoint URL.\n * Used for local development with emulators like fake-gcs-server.\n */\n endpoint?: string;\n}\n\n/**\n * GCS filesystem implementation.\n *\n * Stores files in a Google Cloud Storage bucket.\n * Supports mounting into E2B sandboxes via gcsfuse.\n *\n * @example Using Application Default Credentials\n * ```typescript\n * import { GCSFilesystem } from '@mastra/gcs';\n *\n * // Uses ADC (gcloud auth application-default login)\n * const fs = new GCSFilesystem({\n * bucket: 'my-bucket',\n * projectId: 'my-project',\n * });\n * ```\n *\n * @example Using Service Account Key\n * ```typescript\n * import { GCSFilesystem } from '@mastra/gcs';\n *\n * const fs = new GCSFilesystem({\n * bucket: 'my-bucket',\n * projectId: 'my-project',\n * credentials: {\n * type: 'service_account',\n * project_id: 'my-project',\n * private_key_id: '...',\n * private_key: '-----BEGIN PRIVATE KEY-----\\n...',\n * client_email: '...@...iam.gserviceaccount.com',\n * // ... rest of service account key\n * },\n * });\n * ```\n *\n * @example Using Key File Path\n * ```typescript\n * import { GCSFilesystem } from '@mastra/gcs';\n *\n * const fs = new GCSFilesystem({\n * bucket: 'my-bucket',\n * projectId: 'my-project',\n * credentials: '/path/to/service-account-key.json',\n * });\n * ```\n */\n\n/** Trim leading and trailing slashes without regex (avoids polynomial regex on user input). */\nfunction trimSlashes(s: string): string {\n let start = 0;\n let end = s.length;\n while (start < end && s[start] === '/') start++;\n while (end > start && s[end - 1] === '/') end--;\n return s.slice(start, end);\n}\n\nexport class GCSFilesystem extends MastraFilesystem {\n readonly id: string;\n readonly name = 'GCSFilesystem';\n readonly provider = 'gcs';\n readonly readOnly?: boolean;\n\n status: ProviderStatus = 'pending';\n\n // Display metadata for UI\n readonly displayName?: string;\n readonly icon: FilesystemIcon = 'gcs';\n readonly description?: string;\n\n private readonly bucketName: string;\n private readonly projectId?: string;\n private readonly credentials?: object | string;\n private readonly prefix: string;\n private readonly endpoint?: string;\n\n private _storage: Storage | null = null;\n private _bucket: Bucket | null = null;\n\n constructor(options: GCSFilesystemOptions) {\n super({ name: 'GCSFilesystem' });\n this.id = options.id ?? `gcs-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n this.bucketName = options.bucket;\n this.projectId = options.projectId;\n this.credentials = options.credentials;\n // Trim leading/trailing slashes from prefix using iterative approach (avoids polynomial regex)\n this.prefix = options.prefix ? trimSlashes(options.prefix) + '/' : '';\n this.endpoint = options.endpoint;\n\n // Display metadata\n this.displayName = options.displayName ?? 'Google Cloud Storage';\n this.icon = options.icon ?? 'gcs';\n this.description = options.description;\n this.readOnly = options.readOnly;\n }\n\n /**\n * Get mount configuration for E2B sandbox.\n * Returns GCS-compatible config that works with gcsfuse.\n */\n getMountConfig(): GCSMountConfig {\n const config: GCSMountConfig = {\n type: 'gcs',\n bucket: this.bucketName,\n };\n\n // Include service account key if credentials are an object\n if (this.credentials && typeof this.credentials === 'object') {\n config.serviceAccountKey = JSON.stringify(this.credentials);\n }\n\n return config;\n }\n\n /**\n * Get filesystem info for status reporting.\n */\n getInfo(): FilesystemInfo {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n icon: this.icon,\n metadata: {\n bucket: this.bucketName,\n ...(this.endpoint && { endpoint: this.endpoint }),\n ...(this.prefix && { prefix: this.prefix }),\n },\n };\n }\n\n /**\n * Get instructions describing this GCS filesystem.\n * Used by agents to understand storage semantics.\n */\n getInstructions(): string {\n const access = this.readOnly ? 'Read-only' : 'Persistent';\n return `Google Cloud Storage in bucket \"${this.bucketName}\". ${access} storage - files are retained across sessions.`;\n }\n\n private getStorage(): Storage {\n if (this._storage) return this._storage;\n\n const options: { projectId?: string; credentials?: object; keyFilename?: string; apiEndpoint?: string } = {};\n\n if (this.projectId) {\n options.projectId = this.projectId;\n }\n\n if (this.credentials) {\n if (typeof this.credentials === 'string') {\n // Path to key file\n options.keyFilename = this.credentials;\n } else {\n // Credentials object\n options.credentials = this.credentials;\n }\n }\n\n if (this.endpoint) {\n options.apiEndpoint = this.endpoint;\n }\n\n this._storage = new Storage(options);\n return this._storage;\n }\n\n private getBucket(): Bucket {\n if (this._bucket) return this._bucket;\n\n const storage = this.getStorage();\n this._bucket = storage.bucket(this.bucketName);\n return this._bucket;\n }\n\n /**\n * Ensure the filesystem is initialized and return the bucket.\n * Uses base class ensureReady() for status management, then returns bucket.\n */\n private async getReadyBucket(): Promise<Bucket> {\n await this.ensureReady();\n return this.getBucket();\n }\n\n private toKey(path: string): string {\n // Remove leading slash and add prefix\n const cleanPath = path.replace(/^\\/+/, '');\n return this.prefix + cleanPath;\n }\n\n // ---------------------------------------------------------------------------\n // File Operations\n // ---------------------------------------------------------------------------\n\n async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {\n const bucket = await this.getReadyBucket();\n const file = bucket.file(this.toKey(path));\n\n try {\n const [content] = await file.download();\n\n if (options?.encoding) {\n return content.toString(options.encoding);\n }\n return content;\n } catch (error: unknown) {\n if (error && typeof error === 'object' && 'code' in error && error.code === 404) {\n throw new FileNotFoundError(path);\n }\n throw error;\n }\n }\n\n async writeFile(path: string, content: FileContent, _options?: WriteOptions): Promise<void> {\n const bucket = await this.getReadyBucket();\n const file = bucket.file(this.toKey(path));\n\n const body = typeof content === 'string' ? Buffer.from(content, 'utf-8') : Buffer.from(content);\n const contentType = getMimeType(path);\n\n await file.save(body, {\n contentType,\n resumable: false,\n });\n }\n\n async appendFile(path: string, content: FileContent): Promise<void> {\n // GCS doesn't support append, so read + write\n let existing = '';\n try {\n existing = (await this.readFile(path, { encoding: 'utf-8' })) as string;\n } catch (error) {\n if (error instanceof FileNotFoundError) {\n // File doesn't exist, start fresh\n } else {\n throw error;\n }\n }\n\n const appendContent = typeof content === 'string' ? content : Buffer.from(content).toString('utf-8');\n await this.writeFile(path, existing + appendContent);\n }\n\n async deleteFile(path: string, options?: RemoveOptions): Promise<void> {\n // Check if this is a directory - if so, use rmdir instead\n const isDir = await this.isDirectory(path);\n if (isDir) {\n await this.rmdir(path, { recursive: true, force: options?.force });\n return;\n }\n\n const bucket = await this.getReadyBucket();\n const file = bucket.file(this.toKey(path));\n\n try {\n await file.delete();\n } catch (error: unknown) {\n if (!options?.force) {\n if (error && typeof error === 'object' && 'code' in error && error.code === 404) {\n throw new FileNotFoundError(path);\n }\n throw error;\n }\n }\n }\n\n async copyFile(src: string, dest: string, _options?: CopyOptions): Promise<void> {\n const bucket = await this.getReadyBucket();\n const srcFile = bucket.file(this.toKey(src));\n const destFile = bucket.file(this.toKey(dest));\n\n try {\n await srcFile.copy(destFile);\n } catch (error: unknown) {\n if (error && typeof error === 'object' && 'code' in error && error.code === 404) {\n throw new FileNotFoundError(src);\n }\n throw error;\n }\n }\n\n async moveFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n await this.copyFile(src, dest, options);\n await this.deleteFile(src, { force: true });\n }\n\n // ---------------------------------------------------------------------------\n // Directory Operations\n // ---------------------------------------------------------------------------\n\n async mkdir(_path: string, _options?: { recursive?: boolean }): Promise<void> {\n // GCS doesn't have real directories - they're just key prefixes\n // No-op, directories are created implicitly when files are written\n }\n\n async rmdir(path: string, options?: RemoveOptions): Promise<void> {\n if (!options?.recursive) {\n // Quick emptiness check — only fetch one object instead of full readdir\n const bucket = await this.getReadyBucket();\n const prefix = this.toKey(path).replace(/\\/$/, '') + '/';\n const [files] = await bucket.getFiles({ prefix, maxResults: 1 });\n if (files.length > 0) {\n throw new Error(`Directory not empty: ${path}`);\n }\n return;\n }\n\n // Delete all objects with this prefix\n const bucket = await this.getReadyBucket();\n const prefix = this.toKey(path).replace(/\\/$/, '') + '/';\n\n await bucket.deleteFiles({ prefix });\n }\n\n async readdir(path: string, options?: ListOptions): Promise<FileEntry[]> {\n const bucket = await this.getReadyBucket();\n\n const prefix = this.toKey(path).replace(/\\/$/, '');\n const searchPrefix = prefix ? prefix + '/' : '';\n\n const entries: FileEntry[] = [];\n const seenDirs = new Set<string>();\n\n const [files] = await bucket.getFiles({\n prefix: searchPrefix,\n autoPaginate: true,\n });\n\n for (const file of files) {\n const key = file.name;\n if (!key || key === searchPrefix) continue;\n\n const relativePath = key.slice(searchPrefix.length);\n if (!relativePath) continue;\n\n // Skip if this looks like a directory marker\n if (relativePath.endsWith('/')) {\n const dirName = relativePath.slice(0, -1);\n if (!seenDirs.has(dirName)) {\n seenDirs.add(dirName);\n entries.push({ name: dirName, type: 'directory' });\n }\n continue;\n }\n\n const name = options?.recursive ? relativePath : relativePath.split('/')[0];\n\n // Skip if name is undefined or empty\n if (!name) continue;\n\n // For non-recursive listing, if there's a slash, it's a directory\n if (!options?.recursive && relativePath.includes('/')) {\n if (!seenDirs.has(name)) {\n seenDirs.add(name);\n entries.push({ name, type: 'directory' });\n }\n continue;\n }\n\n // Filter by extension if specified\n if (options?.extension) {\n const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];\n if (!extensions.some(ext => name.endsWith(ext))) {\n continue;\n }\n }\n\n entries.push({\n name,\n type: 'file',\n size: file.metadata.size != null ? Number(file.metadata.size) : undefined,\n });\n }\n\n return entries;\n }\n\n // ---------------------------------------------------------------------------\n // Path Operations\n // ---------------------------------------------------------------------------\n\n async exists(path: string): Promise<boolean> {\n const key = this.toKey(path);\n\n // Root path always exists (it's the bucket itself)\n if (!key) return true;\n\n const bucket = await this.getReadyBucket();\n const file = bucket.file(key);\n\n // Check if it's a file\n const [exists] = await file.exists();\n if (exists) return true;\n\n // Check if it's a \"directory\" (has objects with this prefix)\n const [files] = await bucket.getFiles({\n prefix: key.replace(/\\/$/, '') + '/',\n maxResults: 1,\n });\n\n return files.length > 0;\n }\n\n async stat(path: string): Promise<FileStat> {\n const key = this.toKey(path);\n\n // Root path is always a directory\n if (!key) {\n return {\n name: '',\n path,\n type: 'directory',\n size: 0,\n createdAt: new Date(),\n modifiedAt: new Date(),\n };\n }\n\n const bucket = await this.getReadyBucket();\n const file = bucket.file(key);\n\n const [exists] = await file.exists();\n if (exists) {\n const [metadata] = await file.getMetadata();\n const name = path.split('/').pop() ?? '';\n\n return {\n name,\n path,\n type: 'file',\n size: Number(metadata.size) || 0,\n createdAt: metadata.timeCreated ? new Date(metadata.timeCreated) : new Date(),\n modifiedAt: metadata.updated ? new Date(metadata.updated) : new Date(),\n };\n }\n\n // Check if it's a directory\n const isDir = await this.isDirectory(path);\n if (isDir) {\n const name = path.split('/').filter(Boolean).pop() ?? '';\n return {\n name,\n path,\n type: 'directory',\n size: 0,\n createdAt: new Date(),\n modifiedAt: new Date(),\n };\n }\n\n throw new FileNotFoundError(path);\n }\n\n async isFile(path: string): Promise<boolean> {\n const key = this.toKey(path);\n if (!key) return false; // Root is a directory, not a file\n\n const bucket = await this.getReadyBucket();\n const file = bucket.file(key);\n\n const [exists] = await file.exists();\n return exists;\n }\n\n async isDirectory(path: string): Promise<boolean> {\n const key = this.toKey(path);\n if (!key) return true; // Root is always a directory\n\n const bucket = await this.getReadyBucket();\n\n const [files] = await bucket.getFiles({\n prefix: key.replace(/\\/$/, '') + '/',\n maxResults: 1,\n });\n\n return files.length > 0;\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle (overrides base class protected methods)\n // ---------------------------------------------------------------------------\n\n /**\n * Initialize the GCS client.\n * Status management is handled by the base class.\n */\n async init(): Promise<void> {\n // Verify we can access the bucket\n const bucket = this.getBucket();\n try {\n const [exists] = await bucket.exists();\n if (!exists) {\n const err = new Error(`Bucket \"${this.bucketName}\" does not exist`) as Error & { status?: number };\n err.status = 404;\n throw err;\n }\n } catch (error) {\n // Check if error already has status (from our 404 throw above)\n if ((error as { status?: number }).status) {\n throw error;\n }\n // Extract status code from GCS errors and add to error for proper HTTP response\n const code = (error as { code?: number }).code;\n if (typeof code === 'number') {\n const message = error instanceof Error ? error.message : String(error);\n const err = new Error(\n message,\n // code === 403\n // ? `Access denied to bucket \"${this.bucketName}\" - check credentials and permissions`\n // : message,\n ) as Error & { status?: number };\n err.status = code;\n throw err;\n }\n throw error;\n }\n }\n\n /**\n * Clean up the GCS client.\n * Status management is handled by the base class.\n */\n async destroy(): Promise<void> {\n this._storage = null;\n this._bucket = null;\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mastra/gcs",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Google Cloud Storage filesystem provider for Mastra workspaces",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@google-cloud/storage": "^7.18.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "22.19.7",
|
|
27
|
+
"dotenv": "^17.2.3",
|
|
28
|
+
"@vitest/coverage-v8": "4.0.12",
|
|
29
|
+
"@vitest/ui": "4.0.12",
|
|
30
|
+
"eslint": "^9.37.0",
|
|
31
|
+
"tsup": "^8.5.1",
|
|
32
|
+
"typescript": "^5.9.3",
|
|
33
|
+
"vitest": "4.0.16",
|
|
34
|
+
"@internal/lint": "0.0.57",
|
|
35
|
+
"@internal/workspace-test-utils": "0.0.1",
|
|
36
|
+
"@internal/types-builder": "0.0.32",
|
|
37
|
+
"@mastra/core": "1.2.1-alpha.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@mastra/core": ">=1.3.0-0 <2.0.0-0"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"CHANGELOG.md"
|
|
45
|
+
],
|
|
46
|
+
"homepage": "https://mastra.ai",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/mastra-ai/mastra.git",
|
|
50
|
+
"directory": "workspaces/gcs"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/mastra-ai/mastra/issues"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=22.13.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsup --silent --config tsup.config.ts",
|
|
60
|
+
"build:lib": "pnpm build",
|
|
61
|
+
"build:watch": "pnpm build --watch",
|
|
62
|
+
"test:unit": "vitest run --exclude '**/*.integration.test.ts'",
|
|
63
|
+
"test:watch": "vitest watch",
|
|
64
|
+
"pretest": "docker compose up -d && sleep 3",
|
|
65
|
+
"test": "GCS_ENDPOINT=http://localhost:4443 TEST_GCS_BUCKET=test-bucket vitest run ./src/**/*.integration.test.ts",
|
|
66
|
+
"posttest": "docker compose down -v",
|
|
67
|
+
"test:cloud": "vitest run ./src/**/*.integration.test.ts",
|
|
68
|
+
"lint": "eslint ."
|
|
69
|
+
}
|
|
70
|
+
}
|