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