@microsoft/agents-hosting-storage-blob 1.9.0-beta.3.gb2d23c1c44 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,15 +2,23 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- This package allows to configure Azure Blob Storage as the backend for Agents conversation State
5
+ This package allows to configure Azure Blob Storage as the backend for Agents conversation State
6
+
7
+ `BlobsStorage` retains the legacy `Storage` contract. Use `BlobsStorageV2` for
8
+ the separate `StorageV2` contract; V2 calls return keyed operation results with
9
+ `value`, `status`, and `version`.
6
10
 
7
11
  ## Usage with connectionStrings
8
12
 
9
13
  ```ts
10
14
  const blobStorage = new BlobsStorage(process.env.BLOB_CONTAINER_ID!, process.env.BLOB_STORAGE_CONNECTION_STRING!)
11
15
  const conversationState = new ConversationState(blobStorage)
12
- const userState = new UserState(blobStorage)
13
- ```
16
+ const userState = new UserState(blobStorage)
17
+ ```
18
+
19
+ ```ts
20
+ const blobStorageV2 = new BlobsStorageV2(containerName, connectionString)
21
+ ```
14
22
 
15
23
 
16
24
  ## Usage with EntraID authentication
@@ -18,9 +26,19 @@ const userState = new UserState(blobStorage)
18
26
  >note: you must assign RBAC permissions to your storage account
19
27
 
20
28
  ```ts
21
- const echo = new AgentApplication<TurnState>({
22
- storage: new BlobsStorage('', undefined, undefined,
23
- 'https://agentsstate.blob.core.windows.net/nodejs-conversations',
24
- new MsalTokenCredential(loadAuthConfigFromEnv()))
25
- })
29
+ const echo = new AgentApplication<TurnState>({
30
+ storage: new BlobsStorage('', undefined, undefined,
31
+ 'https://agentsstate.blob.core.windows.net/nodejs-conversations',
32
+ new MsalTokenCredential(loadAuthConfigFromEnv()))
33
+ })
34
+ ```
35
+
36
+ ## TTL writes
37
+
38
+ `BlobsStorage` supports the shared storage TTL option:
39
+
40
+ ```ts
41
+ await blobStorage.write({ 'session/123': { value: 'temporary' } }, { ttl: 3600 })
26
42
  ```
43
+
44
+ Expired blobs are omitted from reads and deleted on a best-effort basis when encountered. Physical auto-eviction still depends on Azure Blob lifecycle policies.
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@microsoft/agents-hosting-storage-blob",
4
- "version": "1.9.0-beta.3.gb2d23c1c44",
4
+ "version": "1.9.1",
5
5
  "homepage": "https://github.com/microsoft/Agents-for-js",
6
6
  "repository": {
7
7
  "type": "git",
@@ -18,9 +18,9 @@
18
18
  "dependencies": {
19
19
  "@azure/core-auth": "1.10.1",
20
20
  "@azure/storage-blob": "12.31.0",
21
- "@microsoft/agents-hosting": "1.9.0-beta.3.gb2d23c1c44",
22
- "@microsoft/agents-activity": "1.9.0-beta.3.gb2d23c1c44",
23
- "@microsoft/agents-telemetry": "1.9.0-beta.3.gb2d23c1c44",
21
+ "@microsoft/agents-hosting": "1.9.1",
22
+ "@microsoft/agents-activity": "1.9.1",
23
+ "@microsoft/agents-telemetry": "1.9.1",
24
24
  "zod": "3.25.75"
25
25
  },
26
26
  "license": "MIT",
@@ -1,57 +1,89 @@
1
1
  import { TokenCredential } from '@azure/core-auth';
2
- import { AnonymousCredential, StoragePipelineOptions, StorageSharedKeyCredential } from '@azure/storage-blob';
3
- import { Storage, StoreItems } from '@microsoft/agents-hosting';
4
- /**
5
- * Options for configuring the BlobsStorage.
6
- */
2
+ import { AnonymousCredential, ContainerClient, StoragePipelineOptions, StorageSharedKeyCredential } from '@azure/storage-blob';
3
+ import { StorageDeleteOptions, StorageDeleteResults, StorageReadResults, StorageWriteOptions, StorageWriteResults, Storage, StorageV2, StoreItems } from '@microsoft/agents-hosting';
4
+ /** Options for configuring Blob storage. */
7
5
  export interface BlobsStorageOptions {
6
+ /** Optional Azure Storage pipeline options to customize request behavior. */
7
+ storagePipelineOptions?: StoragePipelineOptions;
8
+ }
9
+ /** Shared Blob client setup and raw Blob primitives. */
10
+ declare class BlobsStorageInternals {
11
+ readonly _containerClient: ContainerClient;
12
+ private readonly _concurrency;
13
+ private _initializePromise?;
14
+ constructor(containerName: string, connectionString?: string, options?: BlobsStorageOptions, url?: string, credential?: StorageSharedKeyCredential | AnonymousCredential | TokenCredential);
15
+ _initialize(): Promise<unknown>;
16
+ getVersion(key: string): Promise<string | undefined>;
17
+ }
18
+ /** A legacy Azure Blob storage provider. */
19
+ export declare class BlobsStorage extends BlobsStorageInternals implements Storage {
20
+ constructor(containerName: string, connectionString?: string, options?: BlobsStorageOptions, url?: string, credential?: StorageSharedKeyCredential | AnonymousCredential | TokenCredential);
8
21
  /**
9
- * Optional Azure Storage pipeline options to customize request behavior
22
+ * Reads legacy items from Azure Blob Storage.
23
+ *
24
+ * @param keys The keys to read.
25
+ * @returns Existing items keyed by storage key. Missing blobs are omitted and returned items
26
+ * include the Blob ETag as their legacy `eTag`.
10
27
  */
11
- storagePipelineOptions?: StoragePipelineOptions;
28
+ read(keys: string[]): Promise<StoreItems>;
29
+ /**
30
+ * Writes legacy items to Azure Blob Storage.
31
+ *
32
+ * @param changes The items to write, keyed by storage key.
33
+ * @throws If the input is invalid, Blob Storage cannot complete the write, or a legacy `eTag`
34
+ * condition fails.
35
+ */
36
+ write(changes: StoreItems): Promise<void>;
37
+ /**
38
+ * Deletes legacy items from Azure Blob Storage.
39
+ *
40
+ * @param keys The keys to delete. Missing blobs are ignored.
41
+ */
42
+ delete(keys: string[]): Promise<void>;
12
43
  }
13
44
  /**
14
- * A class that implements the Storage interface using Azure Blob Storage.
15
- * Provides persistence for bot state data using Azure's Blob Storage service.
45
+ * An Azure Blob Storage provider for the structured {@link StorageV2} contract.
46
+ *
47
+ * This is the V2 counterpart to {@link BlobsStorage} and accepts the same connection settings.
48
+ * It returns a result for every requested key and exposes the Azure Blob ETag as the separate
49
+ * storage `version`, preserving any `eTag` property in an application value. Writes support
50
+ * create-only, replace, and expected-version conditions to detect concurrent updates.
16
51
  */
17
- export declare class BlobsStorage implements Storage {
18
- private readonly _containerClient;
19
- private readonly _concurrency;
20
- private _initializePromise?;
52
+ export declare class BlobsStorageV2 extends StorageV2 {
53
+ private readonly internals;
21
54
  /**
22
- * Creates a new instance of the BlobsStorage class.
55
+ * Creates a V2 Azure Blob Storage provider.
23
56
  *
24
- * @param containerName The name of the Blob container to use
25
- * @param connectionString Optional, The Azure Storage connection string
26
- * @param options Optional configuration settings for the storage provider
27
- * @param url Optional URL to the blob service (used instead of connectionString if provided)
28
- * @param credential Optional credential for authentication (used with url if provided)
57
+ * @param containerName The Blob container name when using a connection string.
58
+ * @param connectionString The Azure Storage connection string.
59
+ * @param options Optional Azure Storage pipeline options.
60
+ * @param url The Blob service or container URL when using a credential.
61
+ * @param credential The credential used with `url`.
29
62
  */
30
63
  constructor(containerName: string, connectionString?: string, options?: BlobsStorageOptions, url?: string, credential?: StorageSharedKeyCredential | AnonymousCredential | TokenCredential);
31
- private toJSON;
32
- private _initialize;
33
64
  /**
34
- * Reads storage items from blob storage.
65
+ * Reads Blob values and ETag versions.
35
66
  *
36
- * @param keys Array of item keys to read
37
- * @returns A promise that resolves to a StoreItems object containing the retrieved items
38
- * @throws Will throw if keys parameter is invalid or if there's an error reading from storage
67
+ * @param keys The keys to read. Empty batches are valid.
68
+ * @returns A result for every key, with `notFound` for missing blobs and the Blob ETag as the
69
+ * version for successful reads.
39
70
  */
40
- read(keys: string[]): Promise<StoreItems>;
71
+ read<T extends object = Record<string, unknown>>(keys: string[]): Promise<StorageReadResults<T>>;
41
72
  /**
42
- * Writes storage items to blob storage.
73
+ * Writes Blob values with optional concurrency conditions.
43
74
  *
44
- * @param changes The items to write to storage
45
- * @returns A promise that resolves when the write operation is complete
46
- * @throws Will throw if there's a validation error, eTag conflict, or other storage error
75
+ * @param changes The values to write, keyed by storage key.
76
+ * @param options Create-only, replace, or expected-version conditions.
77
+ * @returns A result for every supplied key, including Blob concurrency-condition outcomes.
47
78
  */
48
- write(changes: StoreItems): Promise<void>;
79
+ write<T extends object = Record<string, unknown>>(changes: Record<string, T>, options?: StorageWriteOptions): Promise<StorageWriteResults>;
49
80
  /**
50
- * Deletes storage items from blob storage.
81
+ * Deletes Blob values with an optional ETag version condition.
51
82
  *
52
- * @param keys Array of item keys to delete
53
- * @returns A promise that resolves when the delete operation is complete
54
- * @throws Will throw if keys parameter is invalid
83
+ * @param keys The keys to delete. Empty batches are valid.
84
+ * @param options An optional expected Blob ETag version.
85
+ * @returns A result for every supplied key.
55
86
  */
56
- delete(keys: string[]): Promise<void>;
87
+ delete(keys: string[], options?: StorageDeleteOptions): Promise<StorageDeleteResults>;
57
88
  }
89
+ export {};
@@ -36,11 +36,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.BlobsStorage = void 0;
39
+ exports.BlobsStorageV2 = exports.BlobsStorage = void 0;
40
40
  const z = __importStar(require("zod"));
41
41
  const consumers_1 = __importDefault(require("stream/consumers"));
42
42
  const core_auth_1 = require("@azure/core-auth");
43
43
  const storage_blob_1 = require("@azure/storage-blob");
44
+ const agents_hosting_1 = require("@microsoft/agents-hosting");
44
45
  const agents_activity_1 = require("@microsoft/agents-activity");
45
46
  const errorHelper_1 = require("./errorHelper");
46
47
  const blobsTranscriptStore_1 = require("./blobsTranscriptStore");
@@ -48,40 +49,22 @@ const ignoreError_1 = require("./ignoreError");
48
49
  const agents_telemetry_1 = require("@microsoft/agents-telemetry");
49
50
  const observability_1 = require("./observability");
50
51
  const logger = (0, agents_telemetry_1.debug)('agents:blob-storage');
51
- /**
52
- * A class that implements the Storage interface using Azure Blob Storage.
53
- * Provides persistence for bot state data using Azure's Blob Storage service.
54
- */
55
- class BlobsStorage {
56
- /**
57
- * Creates a new instance of the BlobsStorage class.
58
- *
59
- * @param containerName The name of the Blob container to use
60
- * @param connectionString Optional, The Azure Storage connection string
61
- * @param options Optional configuration settings for the storage provider
62
- * @param url Optional URL to the blob service (used instead of connectionString if provided)
63
- * @param credential Optional credential for authentication (used with url if provided)
64
- */
52
+ const expirationMetadataKey = 'agentsstorageexpiresat';
53
+ /** Shared Blob client setup and raw Blob primitives. */
54
+ class BlobsStorageInternals {
65
55
  constructor(containerName, connectionString, options, url = '', credential) {
66
56
  this._concurrency = Infinity;
67
57
  if (url.trim() !== '') {
68
- z.object({ url: z.string() }).parse({
69
- url,
70
- });
58
+ z.object({ url: z.string() }).parse({ url });
71
59
  this._containerClient = new storage_blob_1.ContainerClient(url, credential, options === null || options === void 0 ? void 0 : options.storagePipelineOptions);
72
- if (url.trim() === 'UseDevelopmentStorage=true;') {
60
+ if (url.trim() === 'UseDevelopmentStorage=true;')
73
61
  this._concurrency = 1;
74
- }
75
62
  }
76
63
  else {
77
- z.object({ connectionString: z.string(), containerName: z.string() }).parse({
78
- connectionString,
79
- containerName,
80
- });
64
+ z.object({ connectionString: z.string(), containerName: z.string() }).parse({ connectionString, containerName });
81
65
  this._containerClient = new storage_blob_1.ContainerClient(connectionString, containerName, options === null || options === void 0 ? void 0 : options.storagePipelineOptions);
82
- if (connectionString.trim() === 'UseDevelopmentStorage=true;') {
66
+ if (connectionString.trim() === 'UseDevelopmentStorage=true;')
83
67
  this._concurrency = 1;
84
- }
85
68
  }
86
69
  logger.info('BlobsStorage settings loaded', {
87
70
  container: containerName,
@@ -92,21 +75,33 @@ class BlobsStorage {
92
75
  pipeline: (options === null || options === void 0 ? void 0 : options.storagePipelineOptions) !== undefined ? 'custom' : 'default',
93
76
  });
94
77
  }
95
- toJSON() {
96
- return { name: 'BlobsStorage' };
97
- }
98
78
  _initialize() {
99
- if (!this._initializePromise) {
79
+ if (!this._initializePromise)
100
80
  this._initializePromise = this._containerClient.createIfNotExists();
101
- }
102
81
  return this._initializePromise;
103
82
  }
83
+ async getVersion(key) {
84
+ try {
85
+ return (await this._containerClient.getBlobClient((0, blobsTranscriptStore_1.sanitizeBlobKey)(key)).getProperties()).etag;
86
+ }
87
+ catch (err) {
88
+ if ((0, ignoreError_1.isStatusCodeError)(404)(err))
89
+ return undefined;
90
+ throwStorageOperationError('read version', key, err);
91
+ }
92
+ }
93
+ }
94
+ /** A legacy Azure Blob storage provider. */
95
+ class BlobsStorage extends BlobsStorageInternals {
96
+ constructor(containerName, connectionString, options, url = '', credential) {
97
+ super(containerName, connectionString, options, url, credential);
98
+ }
104
99
  /**
105
- * Reads storage items from blob storage.
100
+ * Reads legacy items from Azure Blob Storage.
106
101
  *
107
- * @param keys Array of item keys to read
108
- * @returns A promise that resolves to a StoreItems object containing the retrieved items
109
- * @throws Will throw if keys parameter is invalid or if there's an error reading from storage
102
+ * @param keys The keys to read.
103
+ * @returns Existing items keyed by storage key. Missing blobs are omitted and returned items
104
+ * include the Blob ETag as their legacy `eTag`.
110
105
  */
111
106
  async read(keys) {
112
107
  return (0, agents_telemetry_1.trace)(observability_1.BlobsStorageTraceDefinitions.read, async ({ record }) => {
@@ -117,60 +112,54 @@ class BlobsStorage {
117
112
  const results = await Promise.all(keys.map(async (key) => {
118
113
  const result = { key, value: undefined };
119
114
  const blob = await (0, ignoreError_1.ignoreError)(this._containerClient.getBlobClient((0, blobsTranscriptStore_1.sanitizeBlobKey)(key)).download(), (0, ignoreError_1.isStatusCodeError)(404));
120
- if (!blob) {
121
- return result;
122
- }
123
- const { etag: eTag, readableStreamBody } = blob;
124
- if (!readableStreamBody) {
115
+ if (!(blob === null || blob === void 0 ? void 0 : blob.readableStreamBody))
125
116
  return result;
126
- }
127
- const parsed = (await consumers_1.default.json(readableStreamBody));
128
- result.value = { ...parsed, eTag };
129
- logger.debug(`Read blob: ${key}, eTag: ${eTag}`);
117
+ const parsed = await consumers_1.default.json(blob.readableStreamBody);
118
+ result.value = { ...parsed, eTag: blob.etag };
119
+ logger.debug(`Read blob: ${key}, eTag: ${blob.etag}`);
130
120
  return result;
131
121
  }));
132
- return results.reduce((acc, { key, value }) => (value ? { ...acc, [key]: value } : acc), {});
122
+ return results.reduce((items, { key, value }) => value ? { ...items, [key]: value } : items, {});
133
123
  });
134
124
  }
135
125
  /**
136
- * Writes storage items to blob storage.
126
+ * Writes legacy items to Azure Blob Storage.
137
127
  *
138
- * @param changes The items to write to storage
139
- * @returns A promise that resolves when the write operation is complete
140
- * @throws Will throw if there's a validation error, eTag conflict, or other storage error
128
+ * @param changes The items to write, keyed by storage key.
129
+ * @throws If the input is invalid, Blob Storage cannot complete the write, or a legacy `eTag`
130
+ * condition fails.
141
131
  */
142
132
  async write(changes) {
143
133
  return (0, agents_telemetry_1.trace)(observability_1.BlobsStorageTraceDefinitions.write, async ({ record }) => {
134
+ record({ keyCount: changes ? Object.keys(changes).length : 0 });
144
135
  z.record(z.unknown()).parse(changes);
145
- record({ keyCount: Object.keys(changes).length });
146
136
  await this._initialize();
147
137
  await Promise.all(Object.entries(changes).map(async ([key, { eTag = '', ...change }]) => {
148
138
  try {
149
139
  const blob = this._containerClient.getBlockBlobClient((0, blobsTranscriptStore_1.sanitizeBlobKey)(key));
150
140
  const serialized = JSON.stringify(change);
151
141
  logger.debug(`Writing blob: ${key}, eTag: ${eTag}, size: ${serialized.length}`);
152
- return await blob.upload(serialized, serialized.length, {
153
- conditions: typeof eTag === 'string' && eTag !== '*' ? { ifMatch: eTag } : {},
142
+ const conditions = typeof eTag === 'string' && eTag !== '*'
143
+ ? { ifMatch: eTag }
144
+ : {};
145
+ await blob.upload(serialized, serialized.length, {
146
+ conditions,
154
147
  blobHTTPHeaders: { blobContentType: 'application/json' },
155
148
  });
156
149
  }
157
150
  catch (err) {
158
151
  if (err.statusCode === 412) {
159
- throw agents_activity_1.ExceptionHelper.generateException(Error, errorHelper_1.Errors.ETagConflict);
160
- }
161
- else {
162
- throw err;
152
+ throw agents_activity_1.ExceptionHelper.generateException(Error, errorHelper_1.Errors.ETagConflict, undefined, { key });
163
153
  }
154
+ throw agents_activity_1.ExceptionHelper.generateException(Error, errorHelper_1.Errors.StorageWriteFailed, err, { key });
164
155
  }
165
156
  }));
166
157
  });
167
158
  }
168
159
  /**
169
- * Deletes storage items from blob storage.
160
+ * Deletes legacy items from Azure Blob Storage.
170
161
  *
171
- * @param keys Array of item keys to delete
172
- * @returns A promise that resolves when the delete operation is complete
173
- * @throws Will throw if keys parameter is invalid
162
+ * @param keys The keys to delete. Missing blobs are ignored.
174
163
  */
175
164
  async delete(keys) {
176
165
  return (0, agents_telemetry_1.trace)(observability_1.BlobsStorageTraceDefinitions.delete, async ({ record }) => {
@@ -178,9 +167,245 @@ class BlobsStorage {
178
167
  record({ keyCount: (_a = keys === null || keys === void 0 ? void 0 : keys.length) !== null && _a !== void 0 ? _a : 0 });
179
168
  z.object({ keys: z.array(z.string()) }).parse({ keys });
180
169
  await this._initialize();
181
- await Promise.all(keys.map((key) => (0, ignoreError_1.ignoreError)(this._containerClient.deleteBlob((0, blobsTranscriptStore_1.sanitizeBlobKey)(key)), (0, ignoreError_1.isStatusCodeError)(404))));
170
+ await Promise.all(keys.map(key => (0, ignoreError_1.ignoreError)(this._containerClient.deleteBlob((0, blobsTranscriptStore_1.sanitizeBlobKey)(key)), (0, ignoreError_1.isStatusCodeError)(404))));
182
171
  });
183
172
  }
184
173
  }
185
174
  exports.BlobsStorage = BlobsStorage;
175
+ /**
176
+ * An Azure Blob Storage provider for the structured {@link StorageV2} contract.
177
+ *
178
+ * This is the V2 counterpart to {@link BlobsStorage} and accepts the same connection settings.
179
+ * It returns a result for every requested key and exposes the Azure Blob ETag as the separate
180
+ * storage `version`, preserving any `eTag` property in an application value. Writes support
181
+ * create-only, replace, and expected-version conditions to detect concurrent updates.
182
+ */
183
+ class BlobsStorageV2 extends agents_hosting_1.StorageV2 {
184
+ /**
185
+ * Creates a V2 Azure Blob Storage provider.
186
+ *
187
+ * @param containerName The Blob container name when using a connection string.
188
+ * @param connectionString The Azure Storage connection string.
189
+ * @param options Optional Azure Storage pipeline options.
190
+ * @param url The Blob service or container URL when using a credential.
191
+ * @param credential The credential used with `url`.
192
+ */
193
+ constructor(containerName, connectionString, options, url = '', credential) {
194
+ super();
195
+ this.internals = new BlobsStorageInternals(containerName, connectionString, options, url, credential);
196
+ }
197
+ /**
198
+ * Reads Blob values and ETag versions.
199
+ *
200
+ * @param keys The keys to read. Empty batches are valid.
201
+ * @returns A result for every key, with `notFound` for missing blobs and the Blob ETag as the
202
+ * version for successful reads.
203
+ */
204
+ async read(keys) {
205
+ return (0, agents_telemetry_1.trace)(observability_1.BlobsStorageTraceDefinitions.read, async ({ record }) => {
206
+ var _a;
207
+ record({ keyCount: (_a = keys === null || keys === void 0 ? void 0 : keys.length) !== null && _a !== void 0 ? _a : 0 });
208
+ validateV2Keys(keys);
209
+ if (keys.length === 0)
210
+ return {};
211
+ await this.internals._initialize();
212
+ const results = {};
213
+ await Promise.all(keys.map(async (key) => {
214
+ try {
215
+ const { etag: version, metadata, readableStreamBody } = await this.internals._containerClient.getBlobClient((0, blobsTranscriptStore_1.sanitizeBlobKey)(key)).download();
216
+ if (!readableStreamBody) {
217
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.NotFound };
218
+ return;
219
+ }
220
+ if (isExpired(metadata === null || metadata === void 0 ? void 0 : metadata[expirationMetadataKey])) {
221
+ readableStreamBody.destroy();
222
+ logger.info('Blob expired, deleting from storage', {
223
+ key: (0, agents_telemetry_1.redactString)(key, true),
224
+ eTag: (0, agents_telemetry_1.redactString)(version, true),
225
+ expiresAt: metadata === null || metadata === void 0 ? void 0 : metadata[expirationMetadataKey],
226
+ });
227
+ await (0, ignoreError_1.ignoreError)(this.internals._containerClient.deleteBlob((0, blobsTranscriptStore_1.sanitizeBlobKey)(key), { conditions: { ifMatch: version } }), (0, ignoreError_1.isStatusCodeError)(404, 412));
228
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.NotFound };
229
+ return;
230
+ }
231
+ const value = await consumers_1.default.json(readableStreamBody);
232
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.Succeeded, value, version };
233
+ logger.debug(`Read blob: ${key}, eTag: ${version}`);
234
+ }
235
+ catch (err) {
236
+ if ((0, ignoreError_1.isStatusCodeError)(404)(err)) {
237
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.NotFound };
238
+ return;
239
+ }
240
+ throwStorageOperationError('read', key, err);
241
+ }
242
+ }));
243
+ return results;
244
+ });
245
+ }
246
+ /**
247
+ * Writes Blob values with optional concurrency conditions.
248
+ *
249
+ * @param changes The values to write, keyed by storage key.
250
+ * @param options Create-only, replace, or expected-version conditions.
251
+ * @returns A result for every supplied key, including Blob concurrency-condition outcomes.
252
+ */
253
+ async write(changes, options) {
254
+ return (0, agents_telemetry_1.trace)(observability_1.BlobsStorageTraceDefinitions.write, async ({ record }) => {
255
+ var _a;
256
+ record({ keyCount: changes ? Object.keys(changes).length : 0 });
257
+ validateExpectedVersion(options === null || options === void 0 ? void 0 : options.expectedVersion);
258
+ validateV2Changes(changes);
259
+ const mode = (_a = options === null || options === void 0 ? void 0 : options.mode) !== null && _a !== void 0 ? _a : agents_hosting_1.StorageWriteMode.Upsert;
260
+ validateWriteMode(mode);
261
+ if (Object.keys(changes).length === 0)
262
+ return {};
263
+ await this.internals._initialize();
264
+ const results = {};
265
+ const expiresAt = (0, agents_hosting_1.getStorageWriteExpiry)(options);
266
+ await Promise.all(Object.entries(changes).map(async ([key, change]) => {
267
+ const blob = this.internals._containerClient.getBlockBlobClient((0, blobsTranscriptStore_1.sanitizeBlobKey)(key));
268
+ const needsCurrentVersion = mode !== agents_hosting_1.StorageWriteMode.Upsert || (options === null || options === void 0 ? void 0 : options.expectedVersion) !== undefined;
269
+ const currentVersion = needsCurrentVersion
270
+ ? await this.internals.getVersion(key)
271
+ : undefined;
272
+ if (mode === agents_hosting_1.StorageWriteMode.CreateOnly && currentVersion !== undefined) {
273
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.Conflict, version: currentVersion };
274
+ return;
275
+ }
276
+ if (mode === agents_hosting_1.StorageWriteMode.Replace && currentVersion === undefined) {
277
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.NotFound };
278
+ return;
279
+ }
280
+ if ((options === null || options === void 0 ? void 0 : options.expectedVersion) !== undefined && options.expectedVersion !== currentVersion) {
281
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.ConditionNotMet, version: currentVersion };
282
+ return;
283
+ }
284
+ const serialized = JSON.stringify(change);
285
+ const conditions = mode === agents_hosting_1.StorageWriteMode.CreateOnly
286
+ ? { ifNoneMatch: '*' }
287
+ : (options === null || options === void 0 ? void 0 : options.expectedVersion) !== undefined
288
+ ? { ifMatch: options.expectedVersion }
289
+ : mode === agents_hosting_1.StorageWriteMode.Replace && currentVersion !== undefined
290
+ ? { ifMatch: currentVersion }
291
+ : undefined;
292
+ try {
293
+ const response = await blob.upload(serialized, serialized.length, {
294
+ conditions,
295
+ blobHTTPHeaders: { blobContentType: 'application/json' },
296
+ metadata: expiresAt === undefined ? {} : { [expirationMetadataKey]: expiresAt.toString() },
297
+ });
298
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.Succeeded, version: response.etag };
299
+ }
300
+ catch (err) {
301
+ const statusCode = err.statusCode;
302
+ if (mode === agents_hosting_1.StorageWriteMode.CreateOnly && (statusCode === 409 || statusCode === 412)) {
303
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.Conflict, version: currentVersion };
304
+ return;
305
+ }
306
+ if (statusCode === 412) {
307
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.ConditionNotMet, version: currentVersion };
308
+ return;
309
+ }
310
+ if (statusCode === 404) {
311
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.NotFound };
312
+ return;
313
+ }
314
+ throwStorageOperationError('write', key, err);
315
+ }
316
+ }));
317
+ return results;
318
+ });
319
+ }
320
+ /**
321
+ * Deletes Blob values with an optional ETag version condition.
322
+ *
323
+ * @param keys The keys to delete. Empty batches are valid.
324
+ * @param options An optional expected Blob ETag version.
325
+ * @returns A result for every supplied key.
326
+ */
327
+ async delete(keys, options) {
328
+ return (0, agents_telemetry_1.trace)(observability_1.BlobsStorageTraceDefinitions.delete, async ({ record }) => {
329
+ var _a;
330
+ record({ keyCount: (_a = keys === null || keys === void 0 ? void 0 : keys.length) !== null && _a !== void 0 ? _a : 0 });
331
+ validateExpectedVersion(options === null || options === void 0 ? void 0 : options.expectedVersion);
332
+ validateV2Keys(keys);
333
+ if (keys.length === 0)
334
+ return {};
335
+ await this.internals._initialize();
336
+ const results = {};
337
+ await Promise.all(keys.map(async (key) => {
338
+ let currentVersion;
339
+ if ((options === null || options === void 0 ? void 0 : options.expectedVersion) !== undefined) {
340
+ currentVersion = await this.internals.getVersion(key);
341
+ if (currentVersion === undefined) {
342
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.NotFound };
343
+ return;
344
+ }
345
+ if (options.expectedVersion !== currentVersion) {
346
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.ConditionNotMet, version: currentVersion };
347
+ return;
348
+ }
349
+ }
350
+ try {
351
+ const deleteOptions = (options === null || options === void 0 ? void 0 : options.expectedVersion) === undefined
352
+ ? undefined
353
+ : { conditions: { ifMatch: options.expectedVersion } };
354
+ await this.internals._containerClient.deleteBlob((0, blobsTranscriptStore_1.sanitizeBlobKey)(key), deleteOptions);
355
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.Succeeded, version: currentVersion };
356
+ }
357
+ catch (err) {
358
+ const statusCode = err.statusCode;
359
+ if (statusCode === 412) {
360
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.ConditionNotMet, version: currentVersion };
361
+ return;
362
+ }
363
+ if (statusCode === 404) {
364
+ results[key] = { key, status: agents_hosting_1.StorageOperationStatus.NotFound };
365
+ return;
366
+ }
367
+ throwStorageOperationError('delete', key, err);
368
+ }
369
+ }));
370
+ return results;
371
+ });
372
+ }
373
+ }
374
+ exports.BlobsStorageV2 = BlobsStorageV2;
375
+ function validateExpectedVersion(expectedVersion) {
376
+ if (expectedVersion === '')
377
+ throw agents_activity_1.ExceptionHelper.generateException(RangeError, errorHelper_1.Errors.StorageV2ExpectedVersionEmpty);
378
+ }
379
+ function validateWriteMode(mode) {
380
+ if (!Object.values(agents_hosting_1.StorageWriteMode).includes(mode)) {
381
+ throw agents_activity_1.ExceptionHelper.generateException(RangeError, errorHelper_1.Errors.StorageV2WriteModeUnsupported, undefined, { mode: String(mode) });
382
+ }
383
+ }
384
+ function validateV2Changes(changes) {
385
+ if (changes === null || typeof changes !== 'object' || Array.isArray(changes)) {
386
+ throw agents_activity_1.ExceptionHelper.generateException(ReferenceError, errorHelper_1.Errors.StorageV2ChangesRequired);
387
+ }
388
+ if (Object.keys(changes).some(key => key.trim() === '')) {
389
+ throw agents_activity_1.ExceptionHelper.generateException(ReferenceError, errorHelper_1.Errors.StorageV2KeyRequired);
390
+ }
391
+ if (Object.values(changes).some(value => value === null || typeof value !== 'object' || Array.isArray(value))) {
392
+ throw agents_activity_1.ExceptionHelper.generateException(TypeError, errorHelper_1.Errors.StorageV2ValueRequired);
393
+ }
394
+ }
395
+ function validateV2Keys(keys) {
396
+ if (!Array.isArray(keys))
397
+ throw agents_activity_1.ExceptionHelper.generateException(ReferenceError, errorHelper_1.Errors.StorageV2KeysRequired);
398
+ if (keys.some(key => typeof key !== 'string' || key.trim() === '')) {
399
+ throw agents_activity_1.ExceptionHelper.generateException(ReferenceError, errorHelper_1.Errors.StorageV2KeyRequired);
400
+ }
401
+ }
402
+ function throwStorageOperationError(operation, key, error) {
403
+ throw agents_activity_1.ExceptionHelper.generateException(Error, errorHelper_1.Errors.StorageV2OperationFailed, error instanceof Error ? error : undefined, { operation, key });
404
+ }
405
+ function isExpired(expiresAt) {
406
+ if (expiresAt === undefined)
407
+ return false;
408
+ const timestamp = Number(expiresAt);
409
+ return Number.isFinite(timestamp) && timestamp <= Date.now();
410
+ }
186
411
  //# sourceMappingURL=blobsStorage.js.map