@microsoft/agents-hosting 1.9.0-beta.8.gf58e66b2ee → 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 +182 -4
- package/dist/package.json +3 -3
- package/dist/src/app/agentApplication.d.ts +4 -3
- package/dist/src/app/agentApplication.js +24 -21
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/agentApplicationBuilder.d.ts +9 -2
- package/dist/src/app/agentApplicationBuilder.js +9 -0
- package/dist/src/app/agentApplicationBuilder.js.map +1 -1
- package/dist/src/app/agentApplicationOptions.d.ts +16 -4
- package/dist/src/app/auth/authorizationManager.d.ts +0 -8
- package/dist/src/app/auth/authorizationManager.js +104 -178
- package/dist/src/app/auth/authorizationManager.js.map +1 -1
- package/dist/src/app/auth/handlerStorage.d.ts +5 -3
- package/dist/src/app/auth/handlerStorage.js +36 -10
- package/dist/src/app/auth/handlerStorage.js.map +1 -1
- package/dist/src/app/auth/types.d.ts +2 -2
- package/dist/src/app/index.d.ts +1 -0
- package/dist/src/app/proactive/proactive.js +10 -6
- package/dist/src/app/proactive/proactive.js.map +1 -1
- package/dist/src/app/proactive/proactiveOptions.d.ts +2 -2
- package/dist/src/app/rateLimit.d.ts +315 -0
- package/dist/src/app/rateLimit.js +333 -0
- package/dist/src/app/rateLimit.js.map +1 -0
- package/dist/src/app/turnState.d.ts +6 -3
- package/dist/src/app/turnState.js +43 -4
- package/dist/src/app/turnState.js.map +1 -1
- package/dist/src/auth/authConfiguration.d.ts +10 -3
- package/dist/src/auth/authConfiguration.js +292 -259
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/settings.d.ts +8 -70
- package/dist/src/auth/settings.js +5 -67
- package/dist/src/auth/settings.js.map +1 -1
- package/dist/src/cloudAdapter.d.ts +5 -0
- package/dist/src/cloudAdapter.js +13 -103
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.d.ts +23 -0
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js +183 -0
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js.map +1 -0
- package/dist/src/configuration/configuration.d.ts +83 -0
- package/dist/src/configuration/configuration.js +836 -0
- package/dist/src/configuration/configuration.js.map +1 -0
- package/dist/src/configuration/configurationSource.d.ts +57 -0
- package/dist/src/configuration/configurationSource.js +7 -0
- package/dist/src/configuration/configurationSource.js.map +1 -0
- package/dist/src/configuration/environmentConfiguration.d.ts +14 -0
- package/dist/src/configuration/environmentConfiguration.js +271 -0
- package/dist/src/configuration/environmentConfiguration.js.map +1 -0
- package/dist/src/configuration/index.d.ts +7 -0
- package/dist/src/configuration/index.js +14 -0
- package/dist/src/configuration/index.js.map +1 -0
- package/dist/src/configuration/jsonFileConfigurationSource.d.ts +15 -0
- package/dist/src/configuration/jsonFileConfigurationSource.js +58 -0
- package/dist/src/configuration/jsonFileConfigurationSource.js.map +1 -0
- package/dist/src/createCloudAdapter.d.ts +30 -5
- package/dist/src/createCloudAdapter.js +17 -10
- package/dist/src/createCloudAdapter.js.map +1 -1
- package/dist/src/errorHelper.js +117 -5
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/observability/traces.d.ts +6 -4
- package/dist/src/observability/traces.js +10 -9
- package/dist/src/observability/traces.js.map +1 -1
- package/dist/src/outboundHostValidator.d.ts +4 -1
- package/dist/src/outboundHostValidator.js +25 -48
- package/dist/src/outboundHostValidator.js.map +1 -1
- package/dist/src/state/agentState.d.ts +8 -3
- package/dist/src/state/agentState.js +26 -8
- package/dist/src/state/agentState.js.map +1 -1
- package/dist/src/state/conversationState.d.ts +2 -2
- package/dist/src/state/conversationState.js.map +1 -1
- package/dist/src/state/userState.d.ts +2 -2
- package/dist/src/state/userState.js.map +1 -1
- package/dist/src/statusCodes.d.ts +5 -0
- package/dist/src/statusCodes.js +5 -0
- package/dist/src/statusCodes.js.map +1 -1
- package/dist/src/storage/fileStorage.d.ts +83 -68
- package/dist/src/storage/fileStorage.js +279 -105
- package/dist/src/storage/fileStorage.js.map +1 -1
- package/dist/src/storage/index.d.ts +1 -0
- package/dist/src/storage/index.js +1 -0
- package/dist/src/storage/index.js.map +1 -1
- package/dist/src/storage/memoryStorage.d.ts +85 -60
- package/dist/src/storage/memoryStorage.js +244 -87
- package/dist/src/storage/memoryStorage.js.map +1 -1
- package/dist/src/storage/storage.d.ts +100 -2
- package/dist/src/storage/storage.js +25 -0
- package/dist/src/storage/storage.js.map +1 -1
- package/dist/src/storage/storageCompatibility.d.ts +17 -0
- package/dist/src/storage/storageCompatibility.js +157 -0
- package/dist/src/storage/storageCompatibility.js.map +1 -0
- package/dist/src/storage/storageExpiry.d.ts +6 -0
- package/dist/src/storage/storageExpiry.js +20 -0
- package/dist/src/storage/storageExpiry.js.map +1 -0
- package/dist/src/utils/env.d.ts +43 -0
- package/dist/src/utils/env.js +42 -6
- package/dist/src/utils/env.js.map +1 -1
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/utils.js +10 -1
- package/dist/src/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/app/agentApplication.ts +27 -14
- package/src/app/agentApplicationBuilder.ts +13 -2
- package/src/app/agentApplicationOptions.ts +18 -4
- package/src/app/auth/authorizationManager.ts +126 -204
- package/src/app/auth/handlerStorage.ts +51 -7
- package/src/app/auth/types.ts +2 -2
- package/src/app/index.ts +7 -0
- package/src/app/proactive/proactive.ts +19 -9
- package/src/app/proactive/proactiveOptions.ts +2 -2
- package/src/app/rateLimit.ts +659 -0
- package/src/app/turnState.ts +50 -7
- package/src/auth/authConfiguration.ts +415 -260
- package/src/auth/settings.ts +5 -82
- package/src/cloudAdapter.ts +38 -113
- package/src/configuration/botFrameworkEnvironmentCompatibility.ts +245 -0
- package/src/configuration/configuration.ts +1076 -0
- package/src/configuration/configurationSource.ts +76 -0
- package/src/configuration/environmentConfiguration.ts +377 -0
- package/src/configuration/index.ts +19 -0
- package/src/configuration/jsonFileConfigurationSource.ts +74 -0
- package/src/createCloudAdapter.ts +52 -9
- package/src/errorHelper.ts +143 -5
- package/src/index.ts +1 -0
- package/src/observability/traces.ts +8 -7
- package/src/outboundHostValidator.ts +37 -44
- package/src/state/agentState.ts +39 -13
- package/src/state/conversationState.ts +2 -2
- package/src/state/userState.ts +2 -2
- package/src/statusCodes.ts +6 -0
- package/src/storage/fileStorage.ts +299 -106
- package/src/storage/index.ts +1 -0
- package/src/storage/memoryStorage.ts +268 -85
- package/src/storage/storage.ts +114 -2
- package/src/storage/storageCompatibility.ts +185 -0
- package/src/storage/storageExpiry.ts +21 -0
- package/src/utils/env.ts +63 -0
- package/src/utils.ts +10 -0
|
@@ -3,66 +3,94 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { trace } from '@microsoft/agents-telemetry'
|
|
7
6
|
import { ExceptionHelper } from '@microsoft/agents-activity'
|
|
8
|
-
import {
|
|
9
|
-
import { Storage, StoreItem } from './storage'
|
|
10
|
-
import { debug } from '@microsoft/agents-telemetry'
|
|
7
|
+
import { debug, redactString, trace } from '@microsoft/agents-telemetry'
|
|
11
8
|
import { Errors } from '../errorHelper'
|
|
9
|
+
import { StorageTraceDefinitions } from '../observability'
|
|
10
|
+
import { getStorageWriteExpiry } from './storageExpiry'
|
|
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'
|
|
12
23
|
|
|
13
24
|
const logger = debug('agents:memory-storage')
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* - Development and testing scenarios
|
|
22
|
-
* - Simple applications that don't require data persistence across restarts
|
|
23
|
-
* - Stateless environments where external storage isn't available
|
|
24
|
-
*
|
|
25
|
-
* MemoryStorage supports optimistic concurrency control through eTags and
|
|
26
|
-
* can be used as a singleton through the {@link MemoryStorage.getSingleInstance | getSingleInstance() method} to
|
|
27
|
-
* share state across different parts of an application.
|
|
28
|
-
*/
|
|
29
|
-
export class MemoryStorage implements Storage {
|
|
30
|
-
private static instance: MemoryStorage
|
|
31
|
-
/**
|
|
32
|
-
* Counter used to generate unique eTags for stored items
|
|
33
|
-
*/
|
|
34
|
-
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
|
+
}
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*/
|
|
41
|
-
constructor (private memory: { [k: string]: string } = {}) { }
|
|
33
|
+
class MemoryStorageInternals {
|
|
34
|
+
private static readonly states = new WeakMap<object, MemoryStorageState>()
|
|
35
|
+
|
|
36
|
+
readonly state: MemoryStorageState
|
|
42
37
|
|
|
43
38
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* @returns The singleton instance of MemoryStorage
|
|
47
|
-
*
|
|
48
|
-
* @remarks
|
|
49
|
-
* Using this method ensures that the same storage instance is used across
|
|
50
|
-
* the application, allowing for shared state without passing references.
|
|
51
|
-
*
|
|
39
|
+
* Creates an internal in-memory provider for the selected storage contract.
|
|
52
40
|
*/
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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)
|
|
56
46
|
}
|
|
57
|
-
|
|
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)
|
|
58
86
|
}
|
|
59
87
|
|
|
60
88
|
/**
|
|
61
|
-
* Reads
|
|
89
|
+
* Reads legacy items from process-local memory.
|
|
62
90
|
*
|
|
63
|
-
* @param keys The keys
|
|
64
|
-
* @returns
|
|
65
|
-
*
|
|
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`.
|
|
66
94
|
*/
|
|
67
95
|
async read (keys: string[]): Promise<StoreItem> {
|
|
68
96
|
return trace(StorageTraceDefinitions.read, async ({ record }) => {
|
|
@@ -74,85 +102,240 @@ export class MemoryStorage implements Storage {
|
|
|
74
102
|
const data: StoreItem = {}
|
|
75
103
|
for (const key of keys) {
|
|
76
104
|
logger.debug(`Reading key: ${key}`)
|
|
77
|
-
const item = this.memory[key]
|
|
105
|
+
const item = this.state.memory[key]
|
|
78
106
|
if (item) {
|
|
79
|
-
|
|
107
|
+
const value = JSON.parse(item)
|
|
108
|
+
const version = this.getVersion(key, value)
|
|
109
|
+
data[key] = version === undefined ? value : { ...value, eTag: version }
|
|
80
110
|
}
|
|
81
111
|
}
|
|
82
|
-
|
|
83
112
|
return data
|
|
84
113
|
})
|
|
85
114
|
}
|
|
86
115
|
|
|
87
116
|
/**
|
|
88
|
-
* Writes
|
|
89
|
-
*
|
|
90
|
-
* @param changes The items to write, indexed by key
|
|
91
|
-
* @returns A promise that resolves when the write operation is complete
|
|
92
|
-
* @throws Will throw an error if changes are not provided or if there's an eTag conflict
|
|
117
|
+
* Writes legacy items to process-local memory.
|
|
93
118
|
*
|
|
94
|
-
* @
|
|
95
|
-
*
|
|
96
|
-
* If an item has an eTag, it will only be updated if the existing item
|
|
97
|
-
* has the same eTag. If an item has an eTag of '*' or no eTag, it will
|
|
98
|
-
* 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`.
|
|
99
121
|
*/
|
|
100
122
|
async write (changes: StoreItem): Promise<void> {
|
|
101
123
|
return trace(StorageTraceDefinitions.write, async ({ record }) => {
|
|
102
|
-
|
|
124
|
+
record({ keyCount: changes ? Object.keys(changes).length : undefined })
|
|
125
|
+
if (!changes || typeof changes !== 'object' || Array.isArray(changes)) {
|
|
103
126
|
throw ExceptionHelper.generateException(ReferenceError, Errors.StorageWriteChangesRequired)
|
|
104
127
|
}
|
|
105
|
-
record({ keyCount: changes.length })
|
|
106
128
|
|
|
107
129
|
for (const [key, newItem] of Object.entries(changes)) {
|
|
108
130
|
logger.debug(`Writing key: ${key}`)
|
|
109
|
-
const oldItemStr = this.memory[key]
|
|
131
|
+
const oldItemStr = this.state.memory[key]
|
|
110
132
|
if (!oldItemStr || newItem.eTag === '*' || !newItem.eTag) {
|
|
111
|
-
|
|
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() })
|
|
112
141
|
} else {
|
|
113
|
-
|
|
114
|
-
if (newItem.eTag === oldItem.eTag) {
|
|
115
|
-
this.saveItem(key, newItem)
|
|
116
|
-
} else {
|
|
117
|
-
throw ExceptionHelper.generateException(Error, Errors.StorageETagConflict, undefined, { key })
|
|
118
|
-
}
|
|
142
|
+
throw ExceptionHelper.generateException(Error, Errors.StorageETagConflict, undefined, { key })
|
|
119
143
|
}
|
|
120
144
|
}
|
|
121
145
|
})
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
/**
|
|
125
|
-
* Deletes
|
|
149
|
+
* Deletes legacy items from process-local memory.
|
|
126
150
|
*
|
|
127
|
-
* @param keys The keys
|
|
128
|
-
* @returns A promise that resolves when the delete operation is complete
|
|
151
|
+
* @param keys The keys to delete. Missing keys are ignored.
|
|
129
152
|
*/
|
|
130
153
|
async delete (keys: string[]): Promise<void> {
|
|
131
154
|
return trace(StorageTraceDefinitions.delete, async ({ record }) => {
|
|
132
155
|
record({ keyCount: keys?.length })
|
|
133
156
|
logger.debug(`Deleting keys: ${keys.join(', ')}`)
|
|
134
157
|
for (const key of keys) {
|
|
135
|
-
|
|
158
|
+
this.remove(key)
|
|
136
159
|
}
|
|
137
160
|
})
|
|
138
161
|
}
|
|
139
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
|
+
|
|
140
184
|
/**
|
|
141
|
-
*
|
|
185
|
+
* Creates a V2 in-memory storage provider.
|
|
142
186
|
*
|
|
143
|
-
* @param
|
|
144
|
-
|
|
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
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Reads items from process-local memory.
|
|
145
196
|
*
|
|
146
|
-
* @
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
|
|
150
|
-
|
|
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.
|
|
232
|
+
*
|
|
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.
|
|
151
284
|
*
|
|
152
|
-
* @
|
|
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.
|
|
153
288
|
*/
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
})
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
static getSingleInstance (): MemoryStorageV2 {
|
|
327
|
+
if (!MemoryStorageV2.instance) MemoryStorageV2.instance = new MemoryStorageV2()
|
|
328
|
+
return MemoryStorageV2.instance
|
|
157
329
|
}
|
|
158
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
|
+
}
|
package/src/storage/storage.ts
CHANGED
|
@@ -63,7 +63,7 @@ export interface StoreItems {
|
|
|
63
63
|
export type StorageKeyFactory = (context: TurnContext) => string | Promise<string>
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Defines the interface for storage operations in the Agents platform.
|
|
66
|
+
* Defines the version 1 interface for storage operations in the Agents platform.
|
|
67
67
|
*
|
|
68
68
|
* @remarks
|
|
69
69
|
* Storage providers persist state data across conversation turns, enabling
|
|
@@ -72,7 +72,9 @@ export type StorageKeyFactory = (context: TurnContext) => string | Promise<strin
|
|
|
72
72
|
*
|
|
73
73
|
* The interface is designed to be simple with just three core operations:
|
|
74
74
|
* read, write, and delete. All operations are asynchronous to support both
|
|
75
|
-
* 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.
|
|
76
78
|
*/
|
|
77
79
|
export interface Storage {
|
|
78
80
|
/**
|
|
@@ -101,3 +103,113 @@ export interface Storage {
|
|
|
101
103
|
*/
|
|
102
104
|
delete: (keys: string[]) => Promise<void>;
|
|
103
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
|