@microsoft/agents-hosting 1.9.0-beta.18.g6b8111bba5 → 1.9.0-beta.20.g0c48a31a21

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.
Files changed (71) hide show
  1. package/README.md +18 -10
  2. package/dist/package.json +3 -3
  3. package/dist/src/app/agentApplication.d.ts +1 -0
  4. package/dist/src/app/agentApplication.js +18 -5
  5. package/dist/src/app/agentApplication.js.map +1 -1
  6. package/dist/src/app/agentApplicationBuilder.d.ts +9 -2
  7. package/dist/src/app/agentApplicationBuilder.js +9 -0
  8. package/dist/src/app/agentApplicationBuilder.js.map +1 -1
  9. package/dist/src/app/agentApplicationOptions.d.ts +10 -4
  10. package/dist/src/app/auth/handlerStorage.d.ts +5 -3
  11. package/dist/src/app/auth/handlerStorage.js +36 -10
  12. package/dist/src/app/auth/handlerStorage.js.map +1 -1
  13. package/dist/src/app/auth/types.d.ts +2 -2
  14. package/dist/src/app/index.d.ts +1 -0
  15. package/dist/src/app/proactive/proactive.js +10 -6
  16. package/dist/src/app/proactive/proactive.js.map +1 -1
  17. package/dist/src/app/proactive/proactiveOptions.d.ts +2 -2
  18. package/dist/src/app/rateLimit.d.ts +315 -0
  19. package/dist/src/app/rateLimit.js +333 -0
  20. package/dist/src/app/rateLimit.js.map +1 -0
  21. package/dist/src/app/turnState.d.ts +6 -3
  22. package/dist/src/app/turnState.js +43 -4
  23. package/dist/src/app/turnState.js.map +1 -1
  24. package/dist/src/errorHelper.js +40 -9
  25. package/dist/src/errorHelper.js.map +1 -1
  26. package/dist/src/observability/traces.d.ts +6 -4
  27. package/dist/src/observability/traces.js +10 -9
  28. package/dist/src/observability/traces.js.map +1 -1
  29. package/dist/src/state/agentState.d.ts +8 -3
  30. package/dist/src/state/agentState.js +26 -8
  31. package/dist/src/state/agentState.js.map +1 -1
  32. package/dist/src/state/conversationState.d.ts +2 -2
  33. package/dist/src/state/conversationState.js.map +1 -1
  34. package/dist/src/state/userState.d.ts +2 -2
  35. package/dist/src/state/userState.js.map +1 -1
  36. package/dist/src/statusCodes.d.ts +5 -0
  37. package/dist/src/statusCodes.js +5 -0
  38. package/dist/src/statusCodes.js.map +1 -1
  39. package/dist/src/storage/fileStorage.d.ts +83 -71
  40. package/dist/src/storage/fileStorage.js +272 -136
  41. package/dist/src/storage/fileStorage.js.map +1 -1
  42. package/dist/src/storage/memoryStorage.d.ts +85 -62
  43. package/dist/src/storage/memoryStorage.js +240 -109
  44. package/dist/src/storage/memoryStorage.js.map +1 -1
  45. package/dist/src/storage/storage.d.ts +101 -16
  46. package/dist/src/storage/storage.js +25 -0
  47. package/dist/src/storage/storage.js.map +1 -1
  48. package/dist/src/storage/storageCompatibility.d.ts +17 -0
  49. package/dist/src/storage/storageCompatibility.js +157 -0
  50. package/dist/src/storage/storageCompatibility.js.map +1 -0
  51. package/package.json +3 -3
  52. package/src/app/agentApplication.ts +16 -0
  53. package/src/app/agentApplicationBuilder.ts +13 -2
  54. package/src/app/agentApplicationOptions.ts +11 -4
  55. package/src/app/auth/handlerStorage.ts +51 -7
  56. package/src/app/auth/types.ts +2 -2
  57. package/src/app/index.ts +7 -0
  58. package/src/app/proactive/proactive.ts +19 -9
  59. package/src/app/proactive/proactiveOptions.ts +2 -2
  60. package/src/app/rateLimit.ts +659 -0
  61. package/src/app/turnState.ts +50 -7
  62. package/src/errorHelper.ts +46 -8
  63. package/src/observability/traces.ts +8 -7
  64. package/src/state/agentState.ts +39 -13
  65. package/src/state/conversationState.ts +2 -2
  66. package/src/state/userState.ts +2 -2
  67. package/src/statusCodes.ts +6 -0
  68. package/src/storage/fileStorage.ts +289 -134
  69. package/src/storage/memoryStorage.ts +266 -109
  70. package/src/storage/storage.ts +115 -17
  71. package/src/storage/storageCompatibility.ts +185 -0
@@ -3,68 +3,94 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { redactString, trace } from '@microsoft/agents-telemetry'
7
6
  import { ExceptionHelper } from '@microsoft/agents-activity'
7
+ import { debug, redactString, trace } from '@microsoft/agents-telemetry'
8
+ import { Errors } from '../errorHelper'
8
9
  import { StorageTraceDefinitions } from '../observability'
9
- import { Storage, StoreItem, StorageWriteOptions } from './storage'
10
10
  import { getStorageWriteExpiry } from './storageExpiry'
11
- import { debug } from '@microsoft/agents-telemetry'
12
- import { Errors } from '../errorHelper'
11
+ import {
12
+ StorageDeleteOptions,
13
+ StorageDeleteResults,
14
+ StorageOperationStatus,
15
+ StorageReadResults,
16
+ StorageWriteMode,
17
+ StorageWriteOptions,
18
+ StorageWriteResults,
19
+ Storage,
20
+ StorageV2,
21
+ StoreItem,
22
+ } from './storage'
13
23
 
14
24
  const logger = debug('agents:memory-storage')
15
25
 
16
- /**
17
- * A simple in-memory storage provider that implements the Storage interface.
18
- *
19
- * @remarks
20
- * This class provides a volatile storage solution that keeps data in memory,
21
- * which means data is lost when the process terminates. It's primarily useful for:
22
- * - Development and testing scenarios
23
- * - Simple applications that don't require data persistence across restarts
24
- * - Stateless environments where external storage isn't available
25
- *
26
- * MemoryStorage supports optimistic concurrency control through eTags and
27
- * can be used as a singleton through the {@link MemoryStorage.getSingleInstance | getSingleInstance() method} to
28
- * share state across different parts of an application.
29
- */
30
- export class MemoryStorage implements Storage {
31
- private static instance: MemoryStorage
32
- private expirations: { [k: string]: number } = {}
33
- /**
34
- * Counter used to generate unique eTags for stored items
35
- */
36
- private etag: number = 1
26
+ interface MemoryStorageState {
27
+ memory: { [key: string]: string };
28
+ versions: { [key: string]: string };
29
+ expirations: { [key: string]: number };
30
+ etag: number;
31
+ }
37
32
 
38
- /**
39
- * Creates a new instance of the MemoryStorage class.
40
- *
41
- * @param memory An optional initial memory store to seed the storage with data
42
- */
43
- constructor (private memory: { [k: string]: string } = {}) { }
33
+ class MemoryStorageInternals {
34
+ private static readonly states = new WeakMap<object, MemoryStorageState>()
35
+
36
+ readonly state: MemoryStorageState
44
37
 
45
38
  /**
46
- * Gets a single shared instance of the MemoryStorage class.
47
- *
48
- * @returns The singleton instance of MemoryStorage
49
- *
50
- * @remarks
51
- * Using this method ensures that the same storage instance is used across
52
- * the application, allowing for shared state without passing references.
53
- *
39
+ * Creates an internal in-memory provider for the selected storage contract.
54
40
  */
55
- static getSingleInstance (): MemoryStorage {
56
- if (!MemoryStorage.instance) {
57
- MemoryStorage.instance = new MemoryStorage()
41
+ constructor (memory: { [key: string]: string } = {}) {
42
+ let state = MemoryStorageInternals.states.get(memory)
43
+ if (!state) {
44
+ state = { memory, versions: {}, expirations: {}, etag: getNextETag(memory) }
45
+ MemoryStorageInternals.states.set(memory, state)
58
46
  }
59
- return MemoryStorage.instance
47
+ this.state = state
48
+ }
49
+
50
+ save (key: string, item: unknown, expiresAt?: number): string {
51
+ const version = (this.state.etag++).toString()
52
+ this.state.memory[key] = JSON.stringify(item)
53
+ this.state.versions[key] = version
54
+ if (expiresAt === undefined) delete this.state.expirations[key]
55
+ else this.state.expirations[key] = expiresAt
56
+ return version
57
+ }
58
+
59
+ getVersion (key: string, value: StoreItem): string | undefined {
60
+ return this.state.versions[key] ?? value.eTag as string | undefined
61
+ }
62
+
63
+ isExpired (key: string): boolean {
64
+ const expiresAt = this.state.expirations[key]
65
+ return expiresAt !== undefined && expiresAt <= Date.now()
66
+ }
67
+
68
+ remove (key: string): void {
69
+ delete this.state.memory[key]
70
+ delete this.state.versions[key]
71
+ delete this.state.expirations[key]
72
+ }
73
+ }
74
+
75
+ /**
76
+ * A simple in-memory storage provider for development and testing.
77
+ *
78
+ * This class implements the legacy {@link Storage} contract. Use
79
+ * {@link MemoryStorageV2} for the structured StorageV2 contract.
80
+ */
81
+ export class MemoryStorage extends MemoryStorageInternals implements Storage {
82
+ private static instance: MemoryStorage
83
+
84
+ constructor (memory: { [key: string]: string } = {}) {
85
+ super(memory)
60
86
  }
61
87
 
62
88
  /**
63
- * Reads storage items from memory.
89
+ * Reads legacy items from process-local memory.
64
90
  *
65
- * @param keys The keys of the items to read
66
- * @returns A promise that resolves to the read items
67
- * @throws Will throw an error if keys are not provided or the array is empty
91
+ * @param keys The keys to read; must not be empty.
92
+ * @returns Existing items keyed by storage key. Missing keys are omitted and returned items
93
+ * include their legacy `eTag`.
68
94
  */
69
95
  async read (keys: string[]): Promise<StoreItem> {
70
96
  return trace(StorageTraceDefinitions.read, async ({ record }) => {
@@ -76,109 +102,240 @@ export class MemoryStorage implements Storage {
76
102
  const data: StoreItem = {}
77
103
  for (const key of keys) {
78
104
  logger.debug(`Reading key: ${key}`)
79
- if (this.isExpired(key)) {
80
- logger.info('Item expired, deleting from memory', { key: redactString(key, true) })
81
- delete this.memory[key]
82
- delete this.expirations[key]
83
- continue
84
- }
85
- const item = this.memory[key]
105
+ const item = this.state.memory[key]
86
106
  if (item) {
87
- data[key] = JSON.parse(item)
107
+ const value = JSON.parse(item)
108
+ const version = this.getVersion(key, value)
109
+ data[key] = version === undefined ? value : { ...value, eTag: version }
88
110
  }
89
111
  }
90
-
91
112
  return data
92
113
  })
93
114
  }
94
115
 
95
116
  /**
96
- * Writes storage items to memory.
117
+ * Writes legacy items to process-local memory.
97
118
  *
98
- * @param changes The items to write, indexed by key
99
- * @returns A promise that resolves when the write operation is complete
100
- * @throws Will throw an error if changes are not provided or if there's an eTag conflict
101
- *
102
- * @remarks
103
- * This method supports optimistic concurrency control through eTags.
104
- * If an item has an eTag, it will only be updated if the existing item
105
- * has the same eTag. If an item has an eTag of '*' or no eTag, it will
106
- * always be written regardless of the current state.
119
+ * @param changes The items to write, keyed by storage key.
120
+ * @throws If `changes` is invalid or an item supplies a stale legacy `eTag`.
107
121
  */
108
- async write (changes: StoreItem, options?: StorageWriteOptions): Promise<void> {
122
+ async write (changes: StoreItem): Promise<void> {
109
123
  return trace(StorageTraceDefinitions.write, async ({ record }) => {
110
- const keys = changes ? Object.keys(changes) : []
111
- if (!changes || keys.length === 0) {
124
+ record({ keyCount: changes ? Object.keys(changes).length : undefined })
125
+ if (!changes || typeof changes !== 'object' || Array.isArray(changes)) {
112
126
  throw ExceptionHelper.generateException(ReferenceError, Errors.StorageWriteChangesRequired)
113
127
  }
114
- const expiresAt = getStorageWriteExpiry(options)
115
- record({ keyCount: keys.length })
116
128
 
117
129
  for (const [key, newItem] of Object.entries(changes)) {
118
130
  logger.debug(`Writing key: ${key}`)
119
- if (this.isExpired(key)) {
120
- logger.info('Item expired, deleting from memory', { key: redactString(key, true) })
121
- delete this.memory[key]
122
- delete this.expirations[key]
123
- }
124
- const oldItemStr = this.memory[key]
131
+ const oldItemStr = this.state.memory[key]
125
132
  if (!oldItemStr || newItem.eTag === '*' || !newItem.eTag) {
126
- this.saveItem(key, newItem, expiresAt)
133
+ const { eTag: _eTag, ...value } = newItem
134
+ this.save(key, { ...value, eTag: (this.state.etag).toString() })
135
+ continue
136
+ }
137
+ const oldItem = JSON.parse(oldItemStr)
138
+ if (newItem.eTag === this.getVersion(key, oldItem)) {
139
+ const { eTag: _eTag, ...value } = newItem
140
+ this.save(key, { ...value, eTag: (this.state.etag).toString() })
127
141
  } else {
128
- const oldItem = JSON.parse(oldItemStr)
129
- if (newItem.eTag === oldItem.eTag) {
130
- this.saveItem(key, newItem, expiresAt)
131
- } else {
132
- throw ExceptionHelper.generateException(Error, Errors.StorageETagConflict, undefined, { key })
133
- }
142
+ throw ExceptionHelper.generateException(Error, Errors.StorageETagConflict, undefined, { key })
134
143
  }
135
144
  }
136
145
  })
137
146
  }
138
147
 
139
148
  /**
140
- * Deletes storage items from memory.
149
+ * Deletes legacy items from process-local memory.
141
150
  *
142
- * @param keys The keys of the items to delete
143
- * @returns A promise that resolves when the delete operation is complete
151
+ * @param keys The keys to delete. Missing keys are ignored.
144
152
  */
145
153
  async delete (keys: string[]): Promise<void> {
146
154
  return trace(StorageTraceDefinitions.delete, async ({ record }) => {
147
155
  record({ keyCount: keys?.length })
148
156
  logger.debug(`Deleting keys: ${keys.join(', ')}`)
149
157
  for (const key of keys) {
150
- delete this.memory[key]
151
- delete this.expirations[key]
158
+ this.remove(key)
152
159
  }
153
160
  })
154
161
  }
155
162
 
163
+ static getSingleInstance (): MemoryStorage {
164
+ if (!MemoryStorage.instance) MemoryStorage.instance = new MemoryStorage()
165
+ return MemoryStorage.instance
166
+ }
167
+ }
168
+
169
+ /**
170
+ * An in-memory provider for the structured {@link StorageV2} contract.
171
+ *
172
+ * Unlike {@link MemoryStorage}, every read returns an outcome for every requested key, and every
173
+ * write and delete returns an outcome for every supplied key. Storage versions are returned
174
+ * separately from application values, so an `eTag` property in a value is preserved. Use
175
+ * {@link StorageWriteMode.CreateOnly}, {@link StorageWriteMode.Replace}, or `expectedVersion` to
176
+ * guard concurrent writes.
177
+ *
178
+ * This provider is intended for development and testing only; state is process-local.
179
+ */
180
+ export class MemoryStorageV2 extends StorageV2 {
181
+ private static instance: MemoryStorageV2
182
+ private readonly internals: MemoryStorageInternals
183
+
184
+ /**
185
+ * Creates a V2 in-memory storage provider.
186
+ *
187
+ * @param memory Optional backing store to share with another in-memory storage instance.
188
+ */
189
+ constructor (memory: { [key: string]: string } = {}) {
190
+ super()
191
+ this.internals = new MemoryStorageInternals(memory)
192
+ }
193
+
156
194
  /**
157
- * Saves an item to memory with a new eTag.
195
+ * Reads items from process-local memory.
158
196
  *
159
- * @param key The key of the item to save
160
- * @param item The item to save
197
+ * @param keys The keys to read. Empty batches are valid.
198
+ * @returns A result for every key, with `notFound` for missing items and a separate storage
199
+ * version for successful reads.
200
+ */
201
+ async read<T extends object = Record<string, unknown>> (keys: string[]): Promise<StorageReadResults<T>> {
202
+ return trace(StorageTraceDefinitions.read, async ({ record }) => {
203
+ record({ keyCount: keys?.length })
204
+ if (!Array.isArray(keys)) {
205
+ throw ExceptionHelper.generateException(ReferenceError, Errors.StorageReadKeysRequired)
206
+ }
207
+ if (keys.some(key => typeof key !== 'string' || key.trim() === '')) {
208
+ throw ExceptionHelper.generateException(ReferenceError, Errors.StorageV2KeyRequired)
209
+ }
210
+
211
+ const results: StorageReadResults<T> = {}
212
+ for (const key of keys) {
213
+ logger.debug(`Reading key: ${key}`)
214
+ if (this.internals.isExpired(key)) {
215
+ logger.info('Item expired, deleting from memory', { key: redactString(key, true) })
216
+ this.internals.remove(key)
217
+ }
218
+ const item = this.internals.state.memory[key]
219
+ if (!item) {
220
+ results[key] = { key, status: StorageOperationStatus.NotFound }
221
+ continue
222
+ }
223
+ const value = JSON.parse(item) as T & StoreItem
224
+ results[key] = { key, status: StorageOperationStatus.Succeeded, value, version: this.internals.getVersion(key, value) }
225
+ }
226
+ return results
227
+ })
228
+ }
229
+
230
+ /**
231
+ * Writes items to process-local memory with optional concurrency conditions.
161
232
  *
162
- * @remarks
163
- * This private method handles the details of:
164
- * - Creating a clone of the item to prevent modification of the original
165
- * - Generating a new eTag for optimistic concurrency control
166
- * - Converting the item to a JSON string for storage
233
+ * @param changes The values to write, keyed by storage key.
234
+ * @param options Create-only, replace, or expected-version conditions.
235
+ * @returns A result for every supplied key, including `conflict` or `conditionNotMet` when a
236
+ * condition cannot be satisfied.
237
+ */
238
+ async write<T extends object = Record<string, unknown>> (changes: Record<string, T>, options?: StorageWriteOptions): Promise<StorageWriteResults> {
239
+ return trace(StorageTraceDefinitions.write, async ({ record }) => {
240
+ record({ keyCount: changes ? Object.keys(changes).length : undefined })
241
+ if (options?.expectedVersion === '') {
242
+ throw ExceptionHelper.generateException(RangeError, Errors.StorageV2ExpectedVersionEmpty)
243
+ }
244
+ if (!changes || typeof changes !== 'object' || Array.isArray(changes)) {
245
+ throw ExceptionHelper.generateException(ReferenceError, Errors.StorageWriteChangesRequired)
246
+ }
247
+ if (Object.values(changes).some(value => value === null || typeof value !== 'object' || Array.isArray(value))) {
248
+ throw ExceptionHelper.generateException(TypeError, Errors.StorageV2ValueRequired)
249
+ }
250
+ if (Object.keys(changes).some(key => key.trim() === '')) {
251
+ throw ExceptionHelper.generateException(ReferenceError, Errors.StorageV2KeyRequired)
252
+ }
253
+
254
+ const results: StorageWriteResults = {}
255
+ const expiresAt = getStorageWriteExpiry(options)
256
+ const mode = options?.mode ?? StorageWriteMode.Upsert
257
+ if (!Object.values(StorageWriteMode).includes(mode)) {
258
+ throw ExceptionHelper.generateException(RangeError, Errors.StorageV2WriteModeUnsupported, undefined, { mode: String(mode) })
259
+ }
260
+ for (const [key, newItem] of Object.entries(changes)) {
261
+ if (this.internals.isExpired(key)) {
262
+ logger.info('Item expired, deleting from memory', { key: redactString(key, true) })
263
+ this.internals.remove(key)
264
+ }
265
+ const oldItemStr = this.internals.state.memory[key]
266
+ const oldItem = oldItemStr ? JSON.parse(oldItemStr) as StoreItem : undefined
267
+ const currentVersion = oldItem ? this.internals.getVersion(key, oldItem) : undefined
268
+ if (mode === StorageWriteMode.CreateOnly && oldItemStr) {
269
+ results[key] = { key, status: StorageOperationStatus.Conflict, version: currentVersion }
270
+ } else if (mode === StorageWriteMode.Replace && !oldItemStr) {
271
+ results[key] = { key, status: StorageOperationStatus.NotFound }
272
+ } else if (options?.expectedVersion !== undefined && options.expectedVersion !== currentVersion) {
273
+ results[key] = { key, status: StorageOperationStatus.ConditionNotMet, version: currentVersion }
274
+ } else {
275
+ results[key] = { key, status: StorageOperationStatus.Succeeded, version: this.internals.save(key, newItem, expiresAt) }
276
+ }
277
+ }
278
+ return results
279
+ })
280
+ }
281
+
282
+ /**
283
+ * Deletes items from process-local memory with an optional version condition.
167
284
  *
168
- * @private
285
+ * @param keys The keys to delete. Empty batches are valid.
286
+ * @param options An optional expected storage version.
287
+ * @returns A result for every supplied key.
169
288
  */
170
- private saveItem (key: string, item: unknown, expiresAt?: number): void {
171
- const clone = Object.assign({}, item, { eTag: (this.etag++).toString() })
172
- this.memory[key] = JSON.stringify(clone)
173
- if (expiresAt === undefined) {
174
- delete this.expirations[key]
175
- } else {
176
- this.expirations[key] = expiresAt
177
- }
289
+ async delete (keys: string[], options?: StorageDeleteOptions): Promise<StorageDeleteResults> {
290
+ return trace(StorageTraceDefinitions.delete, async ({ record }) => {
291
+ record({ keyCount: keys?.length })
292
+ if (options?.expectedVersion === '') {
293
+ throw ExceptionHelper.generateException(RangeError, Errors.StorageV2ExpectedVersionEmpty)
294
+ }
295
+ if (!Array.isArray(keys)) {
296
+ throw ExceptionHelper.generateException(ReferenceError, Errors.StorageReadKeysRequired)
297
+ }
298
+ if (keys.some(key => typeof key !== 'string' || key.trim() === '')) {
299
+ throw ExceptionHelper.generateException(ReferenceError, Errors.StorageV2KeyRequired)
300
+ }
301
+
302
+ const results: StorageDeleteResults = {}
303
+ for (const key of keys) {
304
+ if (this.internals.isExpired(key)) {
305
+ logger.info('Item expired, deleting from memory', { key: redactString(key, true) })
306
+ this.internals.remove(key)
307
+ }
308
+ const item = this.internals.state.memory[key]
309
+ if (!item) {
310
+ results[key] = { key, status: StorageOperationStatus.NotFound }
311
+ continue
312
+ }
313
+ const value = JSON.parse(item) as StoreItem
314
+ const version = this.internals.getVersion(key, value)
315
+ if (options?.expectedVersion !== undefined && options.expectedVersion !== version) {
316
+ results[key] = { key, status: StorageOperationStatus.ConditionNotMet, version }
317
+ continue
318
+ }
319
+ this.internals.remove(key)
320
+ results[key] = { key, status: StorageOperationStatus.Succeeded, version }
321
+ }
322
+ return results
323
+ })
178
324
  }
179
325
 
180
- private isExpired (key: string): boolean {
181
- const expiresAt = this.expirations[key]
182
- return expiresAt !== undefined && expiresAt <= Date.now()
326
+ static getSingleInstance (): MemoryStorageV2 {
327
+ if (!MemoryStorageV2.instance) MemoryStorageV2.instance = new MemoryStorageV2()
328
+ return MemoryStorageV2.instance
183
329
  }
184
330
  }
331
+
332
+ function getNextETag (memory: { [key: string]: string }): number {
333
+ return Object.values(memory).reduce((next, item) => {
334
+ try {
335
+ const version = Number(JSON.parse(item)?.eTag)
336
+ return Number.isSafeInteger(version) && version >= next ? version + 1 : next
337
+ } catch {
338
+ return next
339
+ }
340
+ }, 1)
341
+ }
@@ -50,20 +50,6 @@ export interface StoreItems {
50
50
  [key: string]: any;
51
51
  }
52
52
 
53
- /**
54
- * Options for writing items to storage.
55
- */
56
- export interface StorageWriteOptions {
57
- /**
58
- * Optional time to live, in seconds, for written items.
59
- *
60
- * @remarks
61
- * When set, storage providers should stop returning the written items after
62
- * this duration has elapsed. Values must be finite numbers greater than zero.
63
- */
64
- ttl?: number;
65
- }
66
-
67
53
  /**
68
54
  * A factory function to generate storage keys based on the conversation context.
69
55
  *
@@ -77,7 +63,7 @@ export interface StorageWriteOptions {
77
63
  export type StorageKeyFactory = (context: TurnContext) => string | Promise<string>
78
64
 
79
65
  /**
80
- * Defines the interface for storage operations in the Agents platform.
66
+ * Defines the version 1 interface for storage operations in the Agents platform.
81
67
  *
82
68
  * @remarks
83
69
  * Storage providers persist state data across conversation turns, enabling
@@ -86,7 +72,9 @@ export type StorageKeyFactory = (context: TurnContext) => string | Promise<strin
86
72
  *
87
73
  * The interface is designed to be simple with just three core operations:
88
74
  * read, write, and delete. All operations are asynchronous to support both
89
- * in-memory and remote storage providers.
75
+ * in-memory and remote storage providers. New implementations should use
76
+ * {@link StorageV2}; this V1 interface remains supported by
77
+ * {@link StorageProvider} through a compatibility adapter.
90
78
  */
91
79
  export interface Storage {
92
80
  /**
@@ -105,7 +93,7 @@ export interface Storage {
105
93
  * @returns A promise that resolves when the write operation is complete
106
94
  * @throws If the changes object is empty or undefined, or if an eTag conflict occurs and optimistic concurrency is enabled
107
95
  */
108
- write: (changes: StoreItem, options?: StorageWriteOptions) => Promise<void>;
96
+ write: (changes: StoreItem) => Promise<void>;
109
97
 
110
98
  /**
111
99
  * Deletes store items from storage.
@@ -115,3 +103,113 @@ export interface Storage {
115
103
  */
116
104
  delete: (keys: string[]) => Promise<void>;
117
105
  }
106
+
107
+ /**
108
+ * The result of a version 2 storage read operation.
109
+ *
110
+ * A result is returned for every requested key. `value` and `version` are
111
+ * available when `status` is {@link StorageOperationStatus.Succeeded}.
112
+ */
113
+ export interface StorageReadResult<T extends object = Record<string, unknown>> {
114
+ key: string;
115
+ status: StorageOperationStatus;
116
+ value?: T;
117
+ version?: string;
118
+ }
119
+
120
+ /**
121
+ * The result of a version 2 storage write operation.
122
+ */
123
+ export interface StorageWriteResult {
124
+ key: string;
125
+ status: StorageOperationStatus;
126
+ version?: string;
127
+ }
128
+
129
+ /**
130
+ * The result of a version 2 storage delete operation.
131
+ */
132
+ export interface StorageDeleteResult {
133
+ key: string;
134
+ status: StorageOperationStatus;
135
+ version?: string;
136
+ }
137
+
138
+ /** A keyed set of storage read results. */
139
+ export type StorageReadResults<T extends object = Record<string, unknown>> = Record<string, StorageReadResult<T>>
140
+
141
+ /** A keyed set of storage write results. */
142
+ export type StorageWriteResults = Record<string, StorageWriteResult>
143
+
144
+ /** A keyed set of storage delete results. */
145
+ export type StorageDeleteResults = Record<string, StorageDeleteResult>
146
+
147
+ /** The outcome of one version 2 storage operation. */
148
+ export enum StorageOperationStatus {
149
+ Succeeded = 'succeeded',
150
+ NotFound = 'notFound',
151
+ Conflict = 'conflict',
152
+ ConditionNotMet = 'conditionNotMet',
153
+ }
154
+
155
+ /** The write mode for a version 2 storage operation. */
156
+ export enum StorageWriteMode {
157
+ Upsert = 'upsert',
158
+ CreateOnly = 'createOnly',
159
+ Replace = 'replace',
160
+ }
161
+
162
+ /** Options applied to every item in a version 2 write operation. */
163
+ export interface StorageWriteOptions {
164
+ /**
165
+ * Optional time to live, in seconds, for written items.
166
+ *
167
+ * When set, storage providers stop returning the written items after this
168
+ * duration has elapsed. Values must be finite numbers greater than zero.
169
+ */
170
+ ttl?: number;
171
+ mode?: StorageWriteMode;
172
+ expectedVersion?: string;
173
+ }
174
+
175
+ /** Options applied to every item in a version 2 delete operation. */
176
+ export interface StorageDeleteOptions {
177
+ expectedVersion?: string;
178
+ }
179
+
180
+ /**
181
+ * The version 2 storage contract.
182
+ *
183
+ * This does not extend {@link Storage}: JavaScript cannot overload methods by
184
+ * return type at runtime. V2 providers are exposed as separately named classes.
185
+ */
186
+ export abstract class StorageV2 {
187
+ /**
188
+ * Reads stored values and returns one result for every requested key.
189
+ *
190
+ * @param keys The keys to read. An empty array is valid.
191
+ * @returns Keyed results containing a value and storage version when found.
192
+ */
193
+ abstract read<T extends object = Record<string, unknown>> (keys: string[]): Promise<StorageReadResults<T>>
194
+
195
+ /**
196
+ * Writes values with optional create, replace, or version conditions.
197
+ *
198
+ * @param changes Values indexed by their storage keys.
199
+ * @param options Conditions applied to every value in the batch.
200
+ * @returns One result for every supplied key.
201
+ */
202
+ abstract write<T extends object = Record<string, unknown>> (changes: Record<string, T>, options?: StorageWriteOptions): Promise<StorageWriteResults>
203
+
204
+ /**
205
+ * Deletes values with an optional version condition.
206
+ *
207
+ * @param keys The keys to delete. An empty array is valid.
208
+ * @param options Conditions applied to every key in the batch.
209
+ * @returns One result for every supplied key.
210
+ */
211
+ abstract delete (keys: string[], options?: StorageDeleteOptions): Promise<StorageDeleteResults>
212
+ }
213
+
214
+ /** A storage implementation supported by public hosting interfaces. */
215
+ export type StorageProvider = Storage | StorageV2