@mastra/azure 0.0.0-ag-example-20260516005230
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/LICENSE.md +30 -0
- package/README.md +79 -0
- package/dist/blob/blob-store/index.d.ts +84 -0
- package/dist/blob/blob-store/index.d.ts.map +1 -0
- package/dist/blob/filesystem/index.d.ts +120 -0
- package/dist/blob/filesystem/index.d.ts.map +1 -0
- package/dist/blob/index.cjs +24 -0
- package/dist/blob/index.cjs.map +1 -0
- package/dist/blob/index.d.ts +9 -0
- package/dist/blob/index.d.ts.map +1 -0
- package/dist/blob/index.js +3 -0
- package/dist/blob/index.js.map +1 -0
- package/dist/blob/provider.d.ts +6 -0
- package/dist/blob/provider.d.ts.map +1 -0
- package/dist/chunk-74CBJSIM.cjs +764 -0
- package/dist/chunk-74CBJSIM.cjs.map +1 -0
- package/dist/chunk-T7WJTVIJ.js +759 -0
- package/dist/chunk-T7WJTVIJ.js.map +1 -0
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var storageBlob = require('@azure/storage-blob');
|
|
4
|
+
var workspace = require('@mastra/core/workspace');
|
|
5
|
+
var storage = require('@mastra/core/storage');
|
|
6
|
+
|
|
7
|
+
// src/blob/filesystem/index.ts
|
|
8
|
+
var MIME_TYPES = {
|
|
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
|
+
".js": "text/javascript",
|
|
18
|
+
".mjs": "text/javascript",
|
|
19
|
+
".ts": "text/typescript",
|
|
20
|
+
".tsx": "text/typescript",
|
|
21
|
+
".jsx": "text/javascript",
|
|
22
|
+
".json": "application/json",
|
|
23
|
+
".yaml": "text/yaml",
|
|
24
|
+
".yml": "text/yaml",
|
|
25
|
+
".py": "text/x-python",
|
|
26
|
+
".rb": "text/x-ruby",
|
|
27
|
+
".sh": "text/x-shellscript",
|
|
28
|
+
".bash": "text/x-shellscript",
|
|
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
|
+
".pdf": "application/pdf",
|
|
37
|
+
".zip": "application/zip",
|
|
38
|
+
".gz": "application/gzip",
|
|
39
|
+
".tar": "application/x-tar"
|
|
40
|
+
};
|
|
41
|
+
function getMimeType(path) {
|
|
42
|
+
const ext = path.toLowerCase().match(/\.[^.]+$/)?.[0];
|
|
43
|
+
return ext ? MIME_TYPES[ext] ?? "application/octet-stream" : "application/octet-stream";
|
|
44
|
+
}
|
|
45
|
+
function isNotFoundError(error) {
|
|
46
|
+
if (!error || typeof error !== "object") return false;
|
|
47
|
+
const restErr = error;
|
|
48
|
+
return restErr.statusCode === 404;
|
|
49
|
+
}
|
|
50
|
+
function isAccessDeniedError(error) {
|
|
51
|
+
if (!error || typeof error !== "object") return false;
|
|
52
|
+
const restErr = error;
|
|
53
|
+
return restErr.statusCode === 403;
|
|
54
|
+
}
|
|
55
|
+
function trimSlashes(s) {
|
|
56
|
+
let start = 0;
|
|
57
|
+
let end = s.length;
|
|
58
|
+
while (start < end && s[start] === "/") start++;
|
|
59
|
+
while (end > start && s[end - 1] === "/") end--;
|
|
60
|
+
return s.slice(start, end);
|
|
61
|
+
}
|
|
62
|
+
async function streamToBuffer(stream) {
|
|
63
|
+
if (!stream) return Buffer.alloc(0);
|
|
64
|
+
const chunks = [];
|
|
65
|
+
for await (const chunk of stream) {
|
|
66
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
67
|
+
}
|
|
68
|
+
return Buffer.concat(chunks);
|
|
69
|
+
}
|
|
70
|
+
function toBuffer(content) {
|
|
71
|
+
if (Buffer.isBuffer(content)) return content;
|
|
72
|
+
return typeof content === "string" ? Buffer.from(content, "utf-8") : Buffer.from(content);
|
|
73
|
+
}
|
|
74
|
+
var AzureBlobFilesystem = class extends workspace.MastraFilesystem {
|
|
75
|
+
id;
|
|
76
|
+
name = "AzureBlobFilesystem";
|
|
77
|
+
provider = "azure-blob";
|
|
78
|
+
readOnly;
|
|
79
|
+
status = "pending";
|
|
80
|
+
displayName;
|
|
81
|
+
icon = "azure-blob";
|
|
82
|
+
description;
|
|
83
|
+
containerName;
|
|
84
|
+
accountName;
|
|
85
|
+
accountKey;
|
|
86
|
+
sasToken;
|
|
87
|
+
connectionString;
|
|
88
|
+
useDefaultCredential;
|
|
89
|
+
prefix;
|
|
90
|
+
endpoint;
|
|
91
|
+
_containerClient = null;
|
|
92
|
+
constructor(options) {
|
|
93
|
+
super({ ...options, name: "AzureBlobFilesystem" });
|
|
94
|
+
this.id = options.id ?? `azure-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
95
|
+
this.containerName = options.container;
|
|
96
|
+
this.accountName = options.accountName;
|
|
97
|
+
this.accountKey = options.accountKey;
|
|
98
|
+
this.sasToken = options.sasToken;
|
|
99
|
+
this.connectionString = options.connectionString;
|
|
100
|
+
this.useDefaultCredential = options.useDefaultCredential ?? false;
|
|
101
|
+
this.prefix = options.prefix ? trimSlashes(options.prefix) + "/" : "";
|
|
102
|
+
this.endpoint = options.endpoint;
|
|
103
|
+
this.displayName = options.displayName ?? "Azure Blob Storage";
|
|
104
|
+
this.icon = options.icon ?? "azure-blob";
|
|
105
|
+
this.description = options.description;
|
|
106
|
+
this.readOnly = options.readOnly;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get the underlying ContainerClient for direct access to Azure Blob APIs.
|
|
110
|
+
*
|
|
111
|
+
* Use this when you need features not exposed through the WorkspaceFilesystem
|
|
112
|
+
* interface (e.g., SAS URL generation, lease management, etc.).
|
|
113
|
+
*
|
|
114
|
+
* This is async because DefaultAzureCredential requires a dynamic import.
|
|
115
|
+
* For non-DefaultAzureCredential auth methods, the promise resolves immediately.
|
|
116
|
+
*/
|
|
117
|
+
getContainer() {
|
|
118
|
+
return this.getContainerClient();
|
|
119
|
+
}
|
|
120
|
+
getMountConfig() {
|
|
121
|
+
const config = {
|
|
122
|
+
type: "azure-blob",
|
|
123
|
+
container: this.containerName
|
|
124
|
+
};
|
|
125
|
+
if (this.connectionString) {
|
|
126
|
+
config.connectionString = this.connectionString;
|
|
127
|
+
} else {
|
|
128
|
+
if (this.accountName) {
|
|
129
|
+
config.accountName = this.accountName;
|
|
130
|
+
}
|
|
131
|
+
if (this.accountKey) {
|
|
132
|
+
config.accountKey = this.accountKey;
|
|
133
|
+
}
|
|
134
|
+
if (this.sasToken) {
|
|
135
|
+
config.sasToken = this.sasToken;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (this.useDefaultCredential) {
|
|
139
|
+
config.useDefaultCredential = true;
|
|
140
|
+
}
|
|
141
|
+
if (this.endpoint) {
|
|
142
|
+
config.endpoint = this.endpoint;
|
|
143
|
+
}
|
|
144
|
+
if (this.prefix) {
|
|
145
|
+
config.prefix = this.prefix;
|
|
146
|
+
}
|
|
147
|
+
if (this.readOnly) {
|
|
148
|
+
config.readOnly = true;
|
|
149
|
+
}
|
|
150
|
+
return config;
|
|
151
|
+
}
|
|
152
|
+
getInfo() {
|
|
153
|
+
return {
|
|
154
|
+
id: this.id,
|
|
155
|
+
name: this.name,
|
|
156
|
+
provider: this.provider,
|
|
157
|
+
status: this.status,
|
|
158
|
+
error: this.error,
|
|
159
|
+
readOnly: this.readOnly,
|
|
160
|
+
icon: this.icon,
|
|
161
|
+
metadata: {
|
|
162
|
+
container: this.containerName,
|
|
163
|
+
...this.endpoint && { endpoint: this.endpoint },
|
|
164
|
+
...this.prefix && { prefix: this.prefix }
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
getInstructions() {
|
|
169
|
+
const access = this.readOnly ? "Read-only" : "Persistent";
|
|
170
|
+
return `Azure Blob Storage in container "${this.containerName}". ${access} storage - files are retained across sessions.`;
|
|
171
|
+
}
|
|
172
|
+
async getContainerClient() {
|
|
173
|
+
if (this._containerClient) return this._containerClient;
|
|
174
|
+
let serviceClient;
|
|
175
|
+
if (this.connectionString) {
|
|
176
|
+
serviceClient = storageBlob.BlobServiceClient.fromConnectionString(this.connectionString);
|
|
177
|
+
} else {
|
|
178
|
+
if (!this.endpoint && !this.accountName) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
"Azure Blob Storage requires either a connectionString, or an accountName/endpoint. Provide at least one of: connectionString, accountName, or endpoint."
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const baseUrl = this.endpoint ?? `https://${this.accountName}.blob.core.windows.net`;
|
|
184
|
+
if (this.accountName && this.accountKey) {
|
|
185
|
+
const credential = new storageBlob.StorageSharedKeyCredential(this.accountName, this.accountKey);
|
|
186
|
+
serviceClient = new storageBlob.BlobServiceClient(baseUrl, credential);
|
|
187
|
+
} else if (this.sasToken) {
|
|
188
|
+
const sas = this.sasToken.replace(/^\?+/, "");
|
|
189
|
+
const separator = baseUrl.includes("?") ? "&" : "?";
|
|
190
|
+
serviceClient = new storageBlob.BlobServiceClient(`${baseUrl}${separator}${sas}`);
|
|
191
|
+
} else if (this.useDefaultCredential) {
|
|
192
|
+
try {
|
|
193
|
+
const identity = await import('@azure/identity');
|
|
194
|
+
const credential = new identity.DefaultAzureCredential();
|
|
195
|
+
serviceClient = new storageBlob.BlobServiceClient(baseUrl, credential);
|
|
196
|
+
} catch {
|
|
197
|
+
throw new Error(
|
|
198
|
+
"DefaultAzureCredential requires @azure/identity to be installed. Install it with: npm install @azure/identity"
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
serviceClient = new storageBlob.BlobServiceClient(baseUrl);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this._containerClient = serviceClient.getContainerClient(this.containerName);
|
|
206
|
+
return this._containerClient;
|
|
207
|
+
}
|
|
208
|
+
async getReadyContainer() {
|
|
209
|
+
await this.ensureReady();
|
|
210
|
+
return this.getContainerClient();
|
|
211
|
+
}
|
|
212
|
+
toKey(path) {
|
|
213
|
+
const cleanPath = path.replace(/^\/+/, "");
|
|
214
|
+
return this.prefix + cleanPath;
|
|
215
|
+
}
|
|
216
|
+
handleError(error) {
|
|
217
|
+
if (isAccessDeniedError(error)) {
|
|
218
|
+
this.status = "error";
|
|
219
|
+
this.error = "Access denied - check credentials and container permissions";
|
|
220
|
+
}
|
|
221
|
+
return error;
|
|
222
|
+
}
|
|
223
|
+
assertWritable(path, operation) {
|
|
224
|
+
if (this.readOnly) {
|
|
225
|
+
throw new workspace.PermissionError(path, `${operation} (filesystem is read-only)`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// File Operations
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
async readFile(path, options) {
|
|
232
|
+
const containerClient = await this.getReadyContainer();
|
|
233
|
+
const blobClient = containerClient.getBlockBlobClient(this.toKey(path));
|
|
234
|
+
try {
|
|
235
|
+
const response = await blobClient.download(0);
|
|
236
|
+
const buffer = await streamToBuffer(response.readableStreamBody);
|
|
237
|
+
if (options?.encoding) {
|
|
238
|
+
return buffer.toString(options.encoding);
|
|
239
|
+
}
|
|
240
|
+
return buffer;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (isNotFoundError(error)) {
|
|
243
|
+
throw new workspace.FileNotFoundError(path);
|
|
244
|
+
}
|
|
245
|
+
throw this.handleError(error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async writeFile(path, content, options) {
|
|
249
|
+
this.assertWritable(path, "write");
|
|
250
|
+
const containerClient = await this.getReadyContainer();
|
|
251
|
+
if (options?.overwrite === false && await this.exists(path)) {
|
|
252
|
+
throw new workspace.FileExistsError(path);
|
|
253
|
+
}
|
|
254
|
+
const body = toBuffer(content);
|
|
255
|
+
const contentType = getMimeType(path);
|
|
256
|
+
const blobClient = containerClient.getBlockBlobClient(this.toKey(path));
|
|
257
|
+
await blobClient.upload(body, body.length, {
|
|
258
|
+
blobHTTPHeaders: { blobContentType: contentType }
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
async appendFile(path, content) {
|
|
262
|
+
this.assertWritable(path, "append");
|
|
263
|
+
let existing = Buffer.alloc(0);
|
|
264
|
+
try {
|
|
265
|
+
const read = await this.readFile(path);
|
|
266
|
+
existing = Buffer.isBuffer(read) ? read : Buffer.from(read);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (error instanceof workspace.FileNotFoundError) ; else {
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const appendBuffer = toBuffer(content);
|
|
273
|
+
await this.writeFile(path, Buffer.concat([existing, appendBuffer]));
|
|
274
|
+
}
|
|
275
|
+
async deleteFile(path, options) {
|
|
276
|
+
this.assertWritable(path, "delete");
|
|
277
|
+
const isDir = await this.isDirectory(path);
|
|
278
|
+
if (isDir) {
|
|
279
|
+
await this.rmdir(path, { recursive: true, force: options?.force });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const containerClient = await this.getReadyContainer();
|
|
283
|
+
const blobClient = containerClient.getBlobClient(this.toKey(path));
|
|
284
|
+
try {
|
|
285
|
+
await blobClient.delete();
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (isNotFoundError(error)) {
|
|
288
|
+
if (options?.force) return;
|
|
289
|
+
throw new workspace.FileNotFoundError(path);
|
|
290
|
+
}
|
|
291
|
+
throw this.handleError(error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async copyFile(src, dest, options) {
|
|
295
|
+
this.assertWritable(dest, "copy");
|
|
296
|
+
if (options?.overwrite === false && await this.exists(dest)) {
|
|
297
|
+
throw new workspace.FileExistsError(dest);
|
|
298
|
+
}
|
|
299
|
+
const containerClient = await this.getReadyContainer();
|
|
300
|
+
const srcBlob = containerClient.getBlobClient(this.toKey(src));
|
|
301
|
+
const destBlob = containerClient.getBlobClient(this.toKey(dest));
|
|
302
|
+
try {
|
|
303
|
+
const sasUrl = await srcBlob.generateSasUrl({
|
|
304
|
+
permissions: storageBlob.BlobSASPermissions.parse("r"),
|
|
305
|
+
expiresOn: new Date(Date.now() + 5 * 60 * 1e3)
|
|
306
|
+
});
|
|
307
|
+
const properties = await srcBlob.getProperties();
|
|
308
|
+
if (properties.contentLength === 0) {
|
|
309
|
+
await destBlob.getBlockBlobClient().upload(Buffer.alloc(0), 0);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const MAX_SYNC_COPY_SIZE = 256 * 1024 * 1024;
|
|
313
|
+
if ((properties.contentLength ?? 0) <= MAX_SYNC_COPY_SIZE) {
|
|
314
|
+
await destBlob.syncCopyFromURL(sasUrl);
|
|
315
|
+
} else {
|
|
316
|
+
const poller = await destBlob.beginCopyFromURL(sasUrl);
|
|
317
|
+
await poller.pollUntilDone();
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (isNotFoundError(error)) {
|
|
321
|
+
throw new workspace.FileNotFoundError(src);
|
|
322
|
+
}
|
|
323
|
+
if (error instanceof Error && (error.message.includes("generateSasUrl") || error.message.includes("SAS") && error.message.includes("shared key credential"))) {
|
|
324
|
+
const content = await this.readFile(src);
|
|
325
|
+
await this.writeFile(dest, content);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
throw this.handleError(error);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async moveFile(src, dest, options) {
|
|
332
|
+
this.assertWritable(dest, "move");
|
|
333
|
+
await this.copyFile(src, dest, options);
|
|
334
|
+
await this.deleteFile(src, { force: true });
|
|
335
|
+
}
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Directory Operations
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
async mkdir(_path, _options) {
|
|
340
|
+
}
|
|
341
|
+
async rmdir(path, options) {
|
|
342
|
+
this.assertWritable(path, "rmdir");
|
|
343
|
+
const containerClient = await this.getReadyContainer();
|
|
344
|
+
const prefix = this.toKey(path).replace(/\/$/, "") + "/";
|
|
345
|
+
if (!options?.recursive) {
|
|
346
|
+
const iter = containerClient.listBlobsFlat({ prefix });
|
|
347
|
+
const first = await iter.next();
|
|
348
|
+
if (!first.done) {
|
|
349
|
+
throw new Error(`Directory not empty: ${path}`);
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
let blobNames = [];
|
|
354
|
+
for await (const blob of containerClient.listBlobsFlat({ prefix })) {
|
|
355
|
+
blobNames.push(blob.name);
|
|
356
|
+
if (blobNames.length >= 256) {
|
|
357
|
+
await this.deleteBlobBatch(containerClient, blobNames);
|
|
358
|
+
blobNames = [];
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (blobNames.length > 0) {
|
|
362
|
+
await this.deleteBlobBatch(containerClient, blobNames);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async deleteBlobBatch(containerClient, blobNames) {
|
|
366
|
+
const blobClients = blobNames.map((name) => containerClient.getBlobClient(name));
|
|
367
|
+
await containerClient.getBlobBatchClient().deleteBlobs(blobClients);
|
|
368
|
+
}
|
|
369
|
+
async readdir(path, options) {
|
|
370
|
+
const containerClient = await this.getReadyContainer();
|
|
371
|
+
const prefix = this.toKey(path).replace(/\/$/, "");
|
|
372
|
+
const searchPrefix = prefix ? prefix + "/" : "";
|
|
373
|
+
const entries = [];
|
|
374
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
375
|
+
if (options?.recursive) {
|
|
376
|
+
for await (const blob of containerClient.listBlobsFlat({ prefix: searchPrefix })) {
|
|
377
|
+
const key = blob.name;
|
|
378
|
+
if (!key || key === searchPrefix) continue;
|
|
379
|
+
const relativePath = key.slice(searchPrefix.length);
|
|
380
|
+
if (!relativePath) continue;
|
|
381
|
+
if (relativePath.endsWith("/")) {
|
|
382
|
+
const dirName = relativePath.slice(0, -1);
|
|
383
|
+
if (!seenDirs.has(dirName)) {
|
|
384
|
+
seenDirs.add(dirName);
|
|
385
|
+
entries.push({ name: dirName, type: "directory" });
|
|
386
|
+
}
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (options?.extension) {
|
|
390
|
+
const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
|
|
391
|
+
if (!extensions.some((ext) => relativePath.endsWith(ext))) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
entries.push({
|
|
396
|
+
name: relativePath,
|
|
397
|
+
type: "file",
|
|
398
|
+
size: blob.properties.contentLength
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
for await (const item of containerClient.listBlobsByHierarchy("/", { prefix: searchPrefix })) {
|
|
403
|
+
if (item.kind === "prefix") {
|
|
404
|
+
const dirName = item.name.slice(searchPrefix.length).replace(/\/$/, "");
|
|
405
|
+
if (dirName && !seenDirs.has(dirName)) {
|
|
406
|
+
seenDirs.add(dirName);
|
|
407
|
+
entries.push({ name: dirName, type: "directory" });
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
const key = item.name;
|
|
411
|
+
if (!key || key === searchPrefix) continue;
|
|
412
|
+
const relativePath = key.slice(searchPrefix.length);
|
|
413
|
+
if (!relativePath) continue;
|
|
414
|
+
if (relativePath.endsWith("/")) {
|
|
415
|
+
const dirName = relativePath.slice(0, -1);
|
|
416
|
+
if (!seenDirs.has(dirName)) {
|
|
417
|
+
seenDirs.add(dirName);
|
|
418
|
+
entries.push({ name: dirName, type: "directory" });
|
|
419
|
+
}
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (options?.extension) {
|
|
423
|
+
const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
|
|
424
|
+
if (!extensions.some((ext) => relativePath.endsWith(ext))) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
entries.push({
|
|
429
|
+
name: relativePath,
|
|
430
|
+
type: "file",
|
|
431
|
+
size: item.properties.contentLength
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return entries;
|
|
437
|
+
}
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
// Path Operations
|
|
440
|
+
// ---------------------------------------------------------------------------
|
|
441
|
+
async exists(path) {
|
|
442
|
+
const key = this.toKey(path);
|
|
443
|
+
if (!key) return true;
|
|
444
|
+
const containerClient = await this.getReadyContainer();
|
|
445
|
+
const blobClient = containerClient.getBlobClient(key);
|
|
446
|
+
const exists = await blobClient.exists();
|
|
447
|
+
if (exists) return true;
|
|
448
|
+
const prefix = key.replace(/\/$/, "") + "/";
|
|
449
|
+
const iter = containerClient.listBlobsFlat({ prefix });
|
|
450
|
+
const first = await iter.next();
|
|
451
|
+
return !first.done;
|
|
452
|
+
}
|
|
453
|
+
async stat(path) {
|
|
454
|
+
const key = this.toKey(path);
|
|
455
|
+
if (!key) {
|
|
456
|
+
return {
|
|
457
|
+
name: "",
|
|
458
|
+
path,
|
|
459
|
+
type: "directory",
|
|
460
|
+
size: 0,
|
|
461
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
462
|
+
modifiedAt: /* @__PURE__ */ new Date()
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const containerClient = await this.getReadyContainer();
|
|
466
|
+
const blobClient = containerClient.getBlobClient(key);
|
|
467
|
+
try {
|
|
468
|
+
const properties = await blobClient.getProperties();
|
|
469
|
+
const name = path.split("/").pop() ?? "";
|
|
470
|
+
return {
|
|
471
|
+
name,
|
|
472
|
+
path,
|
|
473
|
+
type: "file",
|
|
474
|
+
size: properties.contentLength ?? 0,
|
|
475
|
+
createdAt: properties.createdOn ?? /* @__PURE__ */ new Date(),
|
|
476
|
+
modifiedAt: properties.lastModified ?? /* @__PURE__ */ new Date()
|
|
477
|
+
};
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (!isNotFoundError(error)) throw this.handleError(error);
|
|
480
|
+
const isDir = await this.isDirectory(path);
|
|
481
|
+
if (isDir) {
|
|
482
|
+
const name = path.split("/").filter(Boolean).pop() ?? "";
|
|
483
|
+
return {
|
|
484
|
+
name,
|
|
485
|
+
path,
|
|
486
|
+
type: "directory",
|
|
487
|
+
size: 0,
|
|
488
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
489
|
+
modifiedAt: /* @__PURE__ */ new Date()
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
throw new workspace.FileNotFoundError(path);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async isFile(path) {
|
|
496
|
+
const key = this.toKey(path);
|
|
497
|
+
if (!key) return false;
|
|
498
|
+
const containerClient = await this.getReadyContainer();
|
|
499
|
+
const blobClient = containerClient.getBlobClient(key);
|
|
500
|
+
return blobClient.exists();
|
|
501
|
+
}
|
|
502
|
+
async isDirectory(path) {
|
|
503
|
+
const key = this.toKey(path);
|
|
504
|
+
if (!key) return true;
|
|
505
|
+
const containerClient = await this.getReadyContainer();
|
|
506
|
+
const prefix = key.replace(/\/$/, "") + "/";
|
|
507
|
+
const iter = containerClient.listBlobsFlat({ prefix });
|
|
508
|
+
const first = await iter.next();
|
|
509
|
+
return !first.done;
|
|
510
|
+
}
|
|
511
|
+
// ---------------------------------------------------------------------------
|
|
512
|
+
// Lifecycle
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
async init() {
|
|
515
|
+
const containerClient = await this.getContainerClient();
|
|
516
|
+
try {
|
|
517
|
+
if (this.sasToken) {
|
|
518
|
+
const iter = containerClient.listBlobsFlat({ prefix: this.prefix });
|
|
519
|
+
await iter.next();
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const exists = await containerClient.exists();
|
|
523
|
+
if (!exists) {
|
|
524
|
+
const err = new Error(`Container "${this.containerName}" does not exist`);
|
|
525
|
+
err.status = 404;
|
|
526
|
+
throw err;
|
|
527
|
+
}
|
|
528
|
+
} catch (error) {
|
|
529
|
+
if (error.status) {
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
532
|
+
const statusCode = error.statusCode;
|
|
533
|
+
if (typeof statusCode === "number") {
|
|
534
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
535
|
+
const err = new Error(message);
|
|
536
|
+
err.status = statusCode;
|
|
537
|
+
throw err;
|
|
538
|
+
}
|
|
539
|
+
throw error;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
async destroy() {
|
|
543
|
+
this._containerClient = null;
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
function trimSlashes2(s) {
|
|
547
|
+
let start = 0;
|
|
548
|
+
let end = s.length;
|
|
549
|
+
while (start < end && s[start] === "/") start++;
|
|
550
|
+
while (end > start && s[end - 1] === "/") end--;
|
|
551
|
+
return s.slice(start, end);
|
|
552
|
+
}
|
|
553
|
+
function isNotFoundError2(error) {
|
|
554
|
+
if (!error || typeof error !== "object") return false;
|
|
555
|
+
return error.statusCode === 404;
|
|
556
|
+
}
|
|
557
|
+
var BATCH_DELETE_SIZE = 256;
|
|
558
|
+
var AzureBlobStore = class extends storage.BlobStore {
|
|
559
|
+
containerName;
|
|
560
|
+
accountName;
|
|
561
|
+
accountKey;
|
|
562
|
+
sasToken;
|
|
563
|
+
connectionString;
|
|
564
|
+
useDefaultCredential;
|
|
565
|
+
endpoint;
|
|
566
|
+
prefix;
|
|
567
|
+
_containerClient = null;
|
|
568
|
+
constructor(options) {
|
|
569
|
+
super();
|
|
570
|
+
this.containerName = options.container;
|
|
571
|
+
this.accountName = options.accountName;
|
|
572
|
+
this.accountKey = options.accountKey;
|
|
573
|
+
this.sasToken = options.sasToken;
|
|
574
|
+
this.connectionString = options.connectionString;
|
|
575
|
+
this.useDefaultCredential = options.useDefaultCredential ?? false;
|
|
576
|
+
this.endpoint = options.endpoint;
|
|
577
|
+
this.prefix = options.prefix ? trimSlashes2(options.prefix) + "/" : "mastra_skill_blobs/";
|
|
578
|
+
}
|
|
579
|
+
async getContainerClient() {
|
|
580
|
+
if (this._containerClient) return this._containerClient;
|
|
581
|
+
let serviceClient;
|
|
582
|
+
if (this.connectionString) {
|
|
583
|
+
serviceClient = storageBlob.BlobServiceClient.fromConnectionString(this.connectionString);
|
|
584
|
+
} else {
|
|
585
|
+
if (!this.endpoint && !this.accountName) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
"Azure Blob Storage requires either a connectionString, or an accountName/endpoint. Provide at least one of: connectionString, accountName, or endpoint."
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
const baseUrl = this.endpoint ?? `https://${this.accountName}.blob.core.windows.net`;
|
|
591
|
+
if (this.accountName && this.accountKey) {
|
|
592
|
+
const credential = new storageBlob.StorageSharedKeyCredential(this.accountName, this.accountKey);
|
|
593
|
+
serviceClient = new storageBlob.BlobServiceClient(baseUrl, credential);
|
|
594
|
+
} else if (this.sasToken) {
|
|
595
|
+
const sas = this.sasToken.replace(/^\?+/, "");
|
|
596
|
+
const separator = baseUrl.includes("?") ? "&" : "?";
|
|
597
|
+
serviceClient = new storageBlob.BlobServiceClient(`${baseUrl}${separator}${sas}`);
|
|
598
|
+
} else if (this.useDefaultCredential) {
|
|
599
|
+
try {
|
|
600
|
+
const identity = await import('@azure/identity');
|
|
601
|
+
const credential = new identity.DefaultAzureCredential();
|
|
602
|
+
serviceClient = new storageBlob.BlobServiceClient(baseUrl, credential);
|
|
603
|
+
} catch {
|
|
604
|
+
throw new Error(
|
|
605
|
+
"DefaultAzureCredential requires @azure/identity to be installed. Install it with: npm install @azure/identity"
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
} else {
|
|
609
|
+
serviceClient = new storageBlob.BlobServiceClient(baseUrl);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
this._containerClient = serviceClient.getContainerClient(this.containerName);
|
|
613
|
+
return this._containerClient;
|
|
614
|
+
}
|
|
615
|
+
toKey(hash) {
|
|
616
|
+
return this.prefix + hash;
|
|
617
|
+
}
|
|
618
|
+
async init() {
|
|
619
|
+
}
|
|
620
|
+
async put(entry) {
|
|
621
|
+
const containerClient = await this.getContainerClient();
|
|
622
|
+
const blobClient = containerClient.getBlockBlobClient(this.toKey(entry.hash));
|
|
623
|
+
const now = entry.createdAt ?? /* @__PURE__ */ new Date();
|
|
624
|
+
const buffer = Buffer.from(entry.content, "utf-8");
|
|
625
|
+
await blobClient.uploadData(buffer, {
|
|
626
|
+
blobHTTPHeaders: {
|
|
627
|
+
blobContentType: entry.mimeType ?? "application/octet-stream"
|
|
628
|
+
},
|
|
629
|
+
metadata: {
|
|
630
|
+
size: String(entry.size),
|
|
631
|
+
createdat: now.toISOString(),
|
|
632
|
+
...entry.mimeType ? { mimetype: entry.mimeType } : {}
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
async get(hash) {
|
|
637
|
+
const containerClient = await this.getContainerClient();
|
|
638
|
+
const blobClient = containerClient.getBlockBlobClient(this.toKey(hash));
|
|
639
|
+
try {
|
|
640
|
+
const buffer = await blobClient.downloadToBuffer();
|
|
641
|
+
const properties = await blobClient.getProperties();
|
|
642
|
+
const metadata = properties.metadata ?? {};
|
|
643
|
+
const content = buffer.toString("utf-8");
|
|
644
|
+
return {
|
|
645
|
+
hash,
|
|
646
|
+
content,
|
|
647
|
+
size: metadata.size != null ? Number(metadata.size) : Buffer.byteLength(content, "utf-8"),
|
|
648
|
+
mimeType: metadata.mimetype || properties.contentType || void 0,
|
|
649
|
+
createdAt: metadata.createdat ? new Date(metadata.createdat) : /* @__PURE__ */ new Date()
|
|
650
|
+
};
|
|
651
|
+
} catch (error) {
|
|
652
|
+
if (isNotFoundError2(error)) return null;
|
|
653
|
+
throw error;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async has(hash) {
|
|
657
|
+
const containerClient = await this.getContainerClient();
|
|
658
|
+
const blobClient = containerClient.getBlockBlobClient(this.toKey(hash));
|
|
659
|
+
try {
|
|
660
|
+
await blobClient.getProperties();
|
|
661
|
+
return true;
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (isNotFoundError2(error)) return false;
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
async delete(hash) {
|
|
668
|
+
const containerClient = await this.getContainerClient();
|
|
669
|
+
const blobClient = containerClient.getBlockBlobClient(this.toKey(hash));
|
|
670
|
+
const response = await blobClient.deleteIfExists();
|
|
671
|
+
return response.succeeded;
|
|
672
|
+
}
|
|
673
|
+
async putMany(entries) {
|
|
674
|
+
if (entries.length === 0) return;
|
|
675
|
+
await Promise.all(entries.map((entry) => this.put(entry)));
|
|
676
|
+
}
|
|
677
|
+
async getMany(hashes) {
|
|
678
|
+
const result = /* @__PURE__ */ new Map();
|
|
679
|
+
if (hashes.length === 0) return result;
|
|
680
|
+
const entries = await Promise.all(hashes.map((hash) => this.get(hash)));
|
|
681
|
+
for (const entry of entries) {
|
|
682
|
+
if (entry) {
|
|
683
|
+
result.set(entry.hash, entry);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return result;
|
|
687
|
+
}
|
|
688
|
+
async dangerouslyClearAll() {
|
|
689
|
+
const containerClient = await this.getContainerClient();
|
|
690
|
+
let batch = [];
|
|
691
|
+
for await (const blob of containerClient.listBlobsFlat({ prefix: this.prefix })) {
|
|
692
|
+
batch.push(blob.name);
|
|
693
|
+
if (batch.length >= BATCH_DELETE_SIZE) {
|
|
694
|
+
await this.deleteBlobBatch(containerClient, batch);
|
|
695
|
+
batch = [];
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (batch.length > 0) {
|
|
699
|
+
await this.deleteBlobBatch(containerClient, batch);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
async deleteBlobBatch(containerClient, blobNames) {
|
|
703
|
+
const blobClients = blobNames.map((name) => containerClient.getBlobClient(name));
|
|
704
|
+
await containerClient.getBlobBatchClient().deleteBlobs(blobClients);
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// src/blob/provider.ts
|
|
709
|
+
var azureBlobFilesystemProvider = {
|
|
710
|
+
id: "azure-blob",
|
|
711
|
+
name: "Azure Blob Storage",
|
|
712
|
+
description: "Azure Blob Storage container",
|
|
713
|
+
configSchema: {
|
|
714
|
+
type: "object",
|
|
715
|
+
required: ["container"],
|
|
716
|
+
properties: {
|
|
717
|
+
container: { type: "string", description: "Azure Blob container name" },
|
|
718
|
+
accountName: { type: "string", description: "Storage account name" },
|
|
719
|
+
accountKey: { type: "string", description: "Storage account key" },
|
|
720
|
+
sasToken: { type: "string", description: "Shared Access Signature token" },
|
|
721
|
+
connectionString: { type: "string", description: "Full connection string (overrides other auth options)" },
|
|
722
|
+
useDefaultCredential: {
|
|
723
|
+
type: "boolean",
|
|
724
|
+
description: "Use DefaultAzureCredential (requires @azure/identity)",
|
|
725
|
+
default: false
|
|
726
|
+
},
|
|
727
|
+
prefix: { type: "string", description: "Key prefix (acts like a subdirectory)" },
|
|
728
|
+
readOnly: { type: "boolean", description: "Mount as read-only", default: false },
|
|
729
|
+
endpoint: { type: "string", description: "Custom endpoint URL (for Azurite emulator)" }
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
createFilesystem: (config) => new AzureBlobFilesystem(config)
|
|
733
|
+
};
|
|
734
|
+
var azureBlobStoreProvider = {
|
|
735
|
+
id: "azure-blob",
|
|
736
|
+
name: "Azure Blob Store",
|
|
737
|
+
description: "Content-addressable blob storage backed by Azure Blob Storage",
|
|
738
|
+
configSchema: {
|
|
739
|
+
type: "object",
|
|
740
|
+
required: ["container"],
|
|
741
|
+
properties: {
|
|
742
|
+
container: { type: "string", description: "Azure Blob container name" },
|
|
743
|
+
accountName: { type: "string", description: "Storage account name" },
|
|
744
|
+
accountKey: { type: "string", description: "Storage account key" },
|
|
745
|
+
sasToken: { type: "string", description: "Shared Access Signature token" },
|
|
746
|
+
connectionString: { type: "string", description: "Full connection string (overrides other auth options)" },
|
|
747
|
+
useDefaultCredential: {
|
|
748
|
+
type: "boolean",
|
|
749
|
+
description: "Use DefaultAzureCredential (requires @azure/identity)",
|
|
750
|
+
default: false
|
|
751
|
+
},
|
|
752
|
+
prefix: { type: "string", description: "Key prefix for blob objects (default: mastra_skill_blobs/)" },
|
|
753
|
+
endpoint: { type: "string", description: "Custom endpoint URL (for Azurite emulator)" }
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
createBlobStore: (config) => new AzureBlobStore(config)
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
exports.AzureBlobFilesystem = AzureBlobFilesystem;
|
|
760
|
+
exports.AzureBlobStore = AzureBlobStore;
|
|
761
|
+
exports.azureBlobFilesystemProvider = azureBlobFilesystemProvider;
|
|
762
|
+
exports.azureBlobStoreProvider = azureBlobStoreProvider;
|
|
763
|
+
//# sourceMappingURL=chunk-74CBJSIM.cjs.map
|
|
764
|
+
//# sourceMappingURL=chunk-74CBJSIM.cjs.map
|