@microsoft/agents-hosting 1.9.1 → 1.10.0-beta.14.ge6d1defc27
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 +83 -7
- package/dist/package.json +4 -4
- package/dist/src/activityHandler.js +6 -3
- package/dist/src/activityHandler.js.map +1 -1
- package/dist/src/agent-client/agentResponseHandler.d.ts +7 -0
- package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
- package/dist/src/agent-client/createAgentResponseHandler.d.ts +2 -0
- package/dist/src/agent-client/createAgentResponseHandler.js.map +1 -1
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.d.ts +1 -0
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +1 -0
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
- package/dist/src/app/attachmentDownloadHttpClient.d.ts +13 -0
- package/dist/src/app/attachmentDownloadHttpClient.js +80 -0
- package/dist/src/app/attachmentDownloadHttpClient.js.map +1 -0
- package/dist/src/app/attachmentDownloader.d.ts +2 -0
- package/dist/src/app/attachmentDownloader.js +6 -11
- package/dist/src/app/attachmentDownloader.js.map +1 -1
- package/dist/src/app/auth/authorization.d.ts +58 -0
- package/dist/src/app/auth/authorization.js.map +1 -1
- package/dist/src/app/proactive/conversation.d.ts +18 -0
- package/dist/src/app/proactive/conversation.js +6 -0
- package/dist/src/app/proactive/conversation.js.map +1 -1
- package/dist/src/app/proactive/conversationBuilder.js +2 -1
- package/dist/src/app/proactive/conversationBuilder.js.map +1 -1
- package/dist/src/app/proactive/conversationReferenceBuilder.js +8 -4
- package/dist/src/app/proactive/conversationReferenceBuilder.js.map +1 -1
- package/dist/src/app/proactive/createConversationOptionsBuilder.d.ts +1 -0
- package/dist/src/app/proactive/createConversationOptionsBuilder.js +2 -1
- package/dist/src/app/proactive/createConversationOptionsBuilder.js.map +1 -1
- package/dist/src/app/proactive/proactive.d.ts +6 -0
- package/dist/src/app/proactive/proactive.js +32 -1
- package/dist/src/app/proactive/proactive.js.map +1 -1
- package/dist/src/app/routeList.d.ts +21 -0
- package/dist/src/app/routeList.js +27 -3
- package/dist/src/app/routeList.js.map +1 -1
- package/dist/src/app/teamsAttachmentDownloader.d.ts +8 -0
- package/dist/src/app/teamsAttachmentDownloader.js +17 -12
- package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
- package/dist/src/auth/authConfiguration.d.ts +1 -0
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/authConstants.d.ts +7 -0
- package/dist/src/auth/authConstants.js +7 -0
- package/dist/src/auth/authConstants.js.map +1 -1
- package/dist/src/auth/authProvider.d.ts +36 -0
- package/dist/src/auth/connectionManager.js +2 -1
- package/dist/src/auth/connectionManager.js.map +1 -1
- package/dist/src/auth/connections.d.ts +1 -0
- package/dist/src/auth/msal/msalConnectionManager.d.ts +7 -0
- package/dist/src/auth/msal/msalConnectionManager.js +7 -0
- package/dist/src/auth/msal/msalConnectionManager.js.map +1 -1
- package/dist/src/auth/msal/msalTokenProvider.d.ts +44 -0
- package/dist/src/auth/msal/msalTokenProvider.js +43 -0
- package/dist/src/auth/msal/msalTokenProvider.js.map +1 -1
- package/dist/src/auth/settings.d.ts +28 -0
- package/dist/src/auth/settings.js +23 -0
- package/dist/src/auth/settings.js.map +1 -1
- package/dist/src/auth/sidecar/sidecarAuthProvider.d.ts +3 -0
- package/dist/src/auth/sidecar/sidecarAuthProvider.js.map +1 -1
- package/dist/src/auth/sidecar/sidecarHttpClient.d.ts +1 -9
- package/dist/src/auth/sidecar/sidecarHttpClient.js +3 -69
- package/dist/src/auth/sidecar/sidecarHttpClient.js.map +1 -1
- package/dist/src/baseAdapter.d.ts +6 -0
- package/dist/src/baseAdapter.js.map +1 -1
- package/dist/src/cards/taskModuleAction.d.ts +6 -0
- package/dist/src/cards/taskModuleAction.js +6 -0
- package/dist/src/cards/taskModuleAction.js.map +1 -1
- package/dist/src/cloudAdapter.d.ts +29 -0
- package/dist/src/cloudAdapter.js +45 -8
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/configuration/configuration.d.ts +1 -0
- package/dist/src/configuration/configuration.js +2 -1
- package/dist/src/configuration/configuration.js.map +1 -1
- package/dist/src/configuration/environmentConfiguration.js +1 -1
- package/dist/src/configuration/environmentConfiguration.js.map +1 -1
- package/dist/src/connector-client/connectorClient.d.ts +8 -0
- package/dist/src/connector-client/connectorClient.js +8 -0
- package/dist/src/connector-client/connectorClient.js.map +1 -1
- package/dist/src/createCloudAdapter.d.ts +2 -0
- package/dist/src/createCloudAdapter.js.map +1 -1
- package/dist/src/errorHelper.d.ts +1 -0
- package/dist/src/errorHelper.js +9 -0
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/headerPropagation.d.ts +38 -0
- package/dist/src/headerPropagation.js +33 -0
- package/dist/src/headerPropagation.js.map +1 -1
- package/dist/src/httpClient.d.ts +58 -0
- package/dist/src/httpClient.js +40 -0
- package/dist/src/httpClient.js.map +1 -1
- package/dist/src/interfaces/webResponse.d.ts +7 -0
- package/dist/src/middlewareSet.d.ts +6 -0
- package/dist/src/middlewareSet.js.map +1 -1
- package/dist/src/oauth/userTokenClient.d.ts +6 -0
- package/dist/src/oauth/userTokenClient.js +12 -0
- package/dist/src/oauth/userTokenClient.js.map +1 -1
- package/dist/src/outboundHostValidator.d.ts +33 -1
- package/dist/src/outboundHostValidator.js +60 -25
- package/dist/src/outboundHostValidator.js.map +1 -1
- package/dist/src/storage/fileStorage.d.ts +26 -0
- package/dist/src/storage/fileStorage.js +35 -6
- package/dist/src/storage/fileStorage.js.map +1 -1
- package/dist/src/storage/memoryStorage.d.ts +34 -0
- package/dist/src/storage/memoryStorage.js +41 -4
- package/dist/src/storage/memoryStorage.js.map +1 -1
- package/dist/src/storage/storage.d.ts +20 -0
- package/dist/src/storage/storage.js +7 -0
- package/dist/src/storage/storage.js.map +1 -1
- package/dist/src/storage/storageCompatibility.js +12 -6
- package/dist/src/storage/storageCompatibility.js.map +1 -1
- package/dist/src/storage/storageExpiry.d.ts +7 -0
- package/dist/src/storage/storageExpiry.js +7 -0
- package/dist/src/storage/storageExpiry.js.map +1 -1
- package/dist/src/turnContext.d.ts +10 -0
- package/dist/src/turnContext.js +5 -0
- package/dist/src/turnContext.js.map +1 -1
- package/dist/src/utils/env.js +22 -11
- package/dist/src/utils/env.js.map +1 -1
- package/dist/src/utils/ipAddress.d.ts +6 -0
- package/dist/src/utils/ipAddress.js +64 -0
- package/dist/src/utils/ipAddress.js.map +1 -0
- package/dist/src/utils/outboundUrl.d.ts +7 -0
- package/dist/src/utils/outboundUrl.js +52 -0
- package/dist/src/utils/outboundUrl.js.map +1 -0
- package/dist/src/utils.js +2 -1
- package/dist/src/utils.js.map +1 -1
- package/package.json +4 -4
- package/src/activityHandler.ts +9 -3
- package/src/agent-client/agentResponseHandler.ts +7 -0
- package/src/agent-client/createAgentResponseHandler.ts +2 -0
- package/src/app/adaptiveCards/adaptiveCardsActions.ts +1 -0
- package/src/app/attachmentDownloadHttpClient.ts +80 -0
- package/src/app/attachmentDownloader.ts +6 -9
- package/src/app/auth/authorization.ts +58 -0
- package/src/app/proactive/conversation.ts +24 -0
- package/src/app/proactive/conversationBuilder.ts +3 -1
- package/src/app/proactive/conversationReferenceBuilder.ts +12 -4
- package/src/app/proactive/createConversationOptionsBuilder.ts +5 -1
- package/src/app/proactive/proactive.ts +33 -1
- package/src/app/routeList.ts +30 -3
- package/src/app/teamsAttachmentDownloader.ts +17 -10
- package/src/auth/authConfiguration.ts +1 -0
- package/src/auth/authConstants.ts +7 -0
- package/src/auth/authProvider.ts +36 -0
- package/src/auth/connectionManager.ts +3 -1
- package/src/auth/connections.ts +1 -0
- package/src/auth/msal/msalConnectionManager.ts +7 -0
- package/src/auth/msal/msalTokenProvider.ts +59 -0
- package/src/auth/settings.ts +28 -0
- package/src/auth/sidecar/sidecarAuthProvider.ts +4 -0
- package/src/auth/sidecar/sidecarHttpClient.ts +3 -63
- package/src/baseAdapter.ts +6 -0
- package/src/cards/taskModuleAction.ts +6 -0
- package/src/cloudAdapter.ts +59 -10
- package/src/configuration/configuration.ts +4 -2
- package/src/configuration/environmentConfiguration.ts +7 -1
- package/src/connector-client/connectorClient.ts +8 -0
- package/src/createCloudAdapter.ts +2 -0
- package/src/errorHelper.ts +11 -0
- package/src/headerPropagation.ts +38 -0
- package/src/httpClient.ts +58 -0
- package/src/interfaces/webResponse.ts +7 -0
- package/src/middlewareSet.ts +6 -0
- package/src/oauth/userTokenClient.ts +13 -0
- package/src/outboundHostValidator.ts +84 -23
- package/src/storage/fileStorage.ts +46 -6
- package/src/storage/memoryStorage.ts +45 -4
- package/src/storage/storage.ts +20 -0
- package/src/storage/storageCompatibility.ts +18 -6
- package/src/storage/storageExpiry.ts +7 -0
- package/src/turnContext.ts +11 -0
- package/src/utils/env.ts +33 -11
- package/src/utils/ipAddress.ts +63 -0
- package/src/utils/outboundUrl.ts +50 -0
- package/src/utils.ts +3 -1
package/src/middlewareSet.ts
CHANGED
|
@@ -10,6 +10,12 @@ const logger = debug('agents:middleware')
|
|
|
10
10
|
* Interface for middleware.
|
|
11
11
|
*/
|
|
12
12
|
export interface Middleware {
|
|
13
|
+
/**
|
|
14
|
+
* Processes a turn and optionally continues the middleware chain.
|
|
15
|
+
*
|
|
16
|
+
* @param context The current turn context.
|
|
17
|
+
* @param next The callback that continues the middleware chain.
|
|
18
|
+
*/
|
|
13
19
|
onTurn: (context: TurnContext, next: () => Promise<void>) => Promise<void>
|
|
14
20
|
}
|
|
15
21
|
|
|
@@ -34,6 +34,7 @@ function formatHttpErrorMessage (error: HttpError): string {
|
|
|
34
34
|
* Client for managing user tokens.
|
|
35
35
|
*/
|
|
36
36
|
export class UserTokenClient {
|
|
37
|
+
/** The HTTP client used for token service requests. */
|
|
37
38
|
client: HttpClient
|
|
38
39
|
private msAppId: string = ''
|
|
39
40
|
private authProvider?: AuthProvider
|
|
@@ -58,6 +59,13 @@ export class UserTokenClient {
|
|
|
58
59
|
*/
|
|
59
60
|
constructor (httpClient: HttpClient, authProvider?: AuthProvider, authScope?: string)
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Initializes the client from an application ID or an HTTP client.
|
|
64
|
+
*
|
|
65
|
+
* @param param The application ID or configured HTTP client.
|
|
66
|
+
* @param authProvider The optional provider used to acquire an access token lazily.
|
|
67
|
+
* @param authScope The optional access-token scope.
|
|
68
|
+
*/
|
|
61
69
|
constructor (param: string | HttpClient, authProvider?: AuthProvider, authScope?: string) {
|
|
62
70
|
if (typeof param === 'string') {
|
|
63
71
|
const baseURL = getTokenServiceEndpoint()
|
|
@@ -260,6 +268,11 @@ export class UserTokenClient {
|
|
|
260
268
|
})
|
|
261
269
|
}
|
|
262
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Replaces the bearer token used for token service requests.
|
|
273
|
+
*
|
|
274
|
+
* @param token The access token.
|
|
275
|
+
*/
|
|
263
276
|
public updateAuthToken (token: string): void {
|
|
264
277
|
this.client.setHeader('Authorization', `Bearer ${token}`)
|
|
265
278
|
this.authInitialized = true
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from './configuration/configuration'
|
|
10
10
|
import { loadModernEnvironmentConfiguration } from './configuration/environmentConfiguration'
|
|
11
11
|
import { mergeDefined } from './utils'
|
|
12
|
+
import { getHttpsUrl, hasAllowedOutboundAddresses, isLocalOrBlockedHost } from './utils/outboundUrl'
|
|
12
13
|
|
|
13
14
|
/** Hosts used by Microsoft channel callbacks and hosted attachments. */
|
|
14
15
|
const DEFAULT_MICROSOFT_HOSTS = Object.freeze([
|
|
@@ -37,6 +38,12 @@ export interface OutboundHostValidatorOptions {
|
|
|
37
38
|
*/
|
|
38
39
|
includeDefaultMicrosoftHosts?: boolean
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Allows allowlisted HTTPS destinations to use private, shared (RFC 6598),
|
|
43
|
+
* loopback, link-local, or unspecified addresses. Defaults to `false`.
|
|
44
|
+
*/
|
|
45
|
+
allowPrivateNetworkAddresses?: boolean
|
|
46
|
+
|
|
40
47
|
/** Additional exact hosts or host suffixes to allow.
|
|
41
48
|
* An entry matches a request host when the host equals the entry or is a subdomain of it (e.g. `contoso.com` matches `contoso.com` and `files.contoso.com`).
|
|
42
49
|
* A leading `*.` is accepted and ignored (treated as a suffix). Ports and paths are ignored if provided.
|
|
@@ -46,7 +53,14 @@ export interface OutboundHostValidatorOptions {
|
|
|
46
53
|
|
|
47
54
|
/** A policy that decides whether an outbound URL is safe to request. */
|
|
48
55
|
export interface OutboundUrlPolicy {
|
|
56
|
+
/** Indicates whether URL validation is enforced. */
|
|
49
57
|
readonly enabled: boolean
|
|
58
|
+
/**
|
|
59
|
+
* Determines whether an outbound URL is allowed.
|
|
60
|
+
*
|
|
61
|
+
* @param url The URL to validate.
|
|
62
|
+
* @returns `true` when the URL is allowed.
|
|
63
|
+
*/
|
|
50
64
|
isAllowed(url: string | URL | null | undefined): boolean
|
|
51
65
|
}
|
|
52
66
|
|
|
@@ -54,43 +68,88 @@ export interface OutboundUrlPolicy {
|
|
|
54
68
|
* Shared allowlist policy for server-side outbound requests.
|
|
55
69
|
*
|
|
56
70
|
* A configured suffix matches both the exact host and its subdomains. The
|
|
57
|
-
* policy is disabled by default to preserve existing SDK behavior.
|
|
71
|
+
* policy is disabled by default to preserve existing SDK behavior. Enabled
|
|
72
|
+
* policies require HTTPS without userinfo and restrict private addresses.
|
|
73
|
+
* Attachment downloaders additionally validate DNS answers and redirect hops.
|
|
58
74
|
*/
|
|
59
75
|
export class OutboundHostValidator implements OutboundUrlPolicy {
|
|
76
|
+
/** Indicates whether allowlist enforcement is enabled. */
|
|
60
77
|
public readonly enabled: boolean
|
|
61
78
|
private readonly suffixes: ReadonlySet<string>
|
|
79
|
+
private readonly allowPrivateNetworkAddresses: boolean
|
|
62
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Creates an outbound host validator.
|
|
83
|
+
*
|
|
84
|
+
* @param options The allowlist configuration.
|
|
85
|
+
*/
|
|
63
86
|
public constructor (options: OutboundHostValidatorOptions = {}) {
|
|
64
87
|
const resolved = options.configurationContext
|
|
65
88
|
? resolveOutboundHostValidatorOptions(options)
|
|
66
89
|
: options
|
|
67
90
|
this.enabled = resolved.enabled ?? false
|
|
91
|
+
this.allowPrivateNetworkAddresses = resolved.allowPrivateNetworkAddresses ?? false
|
|
68
92
|
|
|
69
93
|
const suffixes = new Set<string>()
|
|
70
94
|
if (resolved.includeDefaultMicrosoftHosts ?? true) {
|
|
71
|
-
for (const host of DEFAULT_MICROSOFT_HOSTS)
|
|
95
|
+
for (const host of DEFAULT_MICROSOFT_HOSTS) {
|
|
96
|
+
suffixes.add(host)
|
|
97
|
+
}
|
|
72
98
|
}
|
|
73
99
|
|
|
74
100
|
for (const host of resolved.hosts ?? []) {
|
|
75
101
|
const normalized = normalizeConfiguredHost(host)
|
|
76
|
-
if (normalized)
|
|
102
|
+
if (normalized) {
|
|
103
|
+
suffixes.add(normalized)
|
|
104
|
+
}
|
|
77
105
|
}
|
|
78
106
|
|
|
79
107
|
this.suffixes = suffixes
|
|
80
108
|
}
|
|
81
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Determines whether a URL's host matches the configured allowlist.
|
|
112
|
+
*
|
|
113
|
+
* @param input The absolute URL to validate.
|
|
114
|
+
* @returns `true` when validation is disabled or the host is allowed.
|
|
115
|
+
*/
|
|
82
116
|
public isAllowed (input: string | URL | null | undefined): boolean {
|
|
83
|
-
if (!this.enabled)
|
|
117
|
+
if (!this.enabled) {
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
84
120
|
|
|
85
|
-
const
|
|
86
|
-
if (!
|
|
121
|
+
const url = getHttpsUrl(input)
|
|
122
|
+
if (!url) {
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
const host = normalizeHostname(url.hostname)
|
|
126
|
+
if (!host || (!this.allowPrivateNetworkAddresses && isLocalOrBlockedHost(host))) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
87
129
|
|
|
88
130
|
for (const suffix of this.suffixes) {
|
|
89
|
-
if (host === suffix || host.endsWith(`.${suffix}`))
|
|
131
|
+
if (host === suffix || host.endsWith(`.${suffix}`)) {
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
90
134
|
}
|
|
91
135
|
|
|
92
136
|
return false
|
|
93
137
|
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Checks the host policy and all resolved destination addresses before an attachment request.
|
|
141
|
+
* DNS prevalidation does not pin the HTTP connection to the checked addresses.
|
|
142
|
+
*/
|
|
143
|
+
public async isAllowedAsync (input: string | URL | null | undefined): Promise<boolean> {
|
|
144
|
+
if (!this.isAllowed(input)) {
|
|
145
|
+
return false
|
|
146
|
+
}
|
|
147
|
+
if (!this.enabled) {
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
150
|
+
const url = getHttpsUrl(input)
|
|
151
|
+
return url !== undefined && await hasAllowedOutboundAddresses(url, this.allowPrivateNetworkAddresses)
|
|
152
|
+
}
|
|
94
153
|
}
|
|
95
154
|
|
|
96
155
|
/**
|
|
@@ -115,11 +174,18 @@ function resolveOutboundHostValidatorOptions (
|
|
|
115
174
|
const external = getConfigurationSnapshot(options.configurationContext)
|
|
116
175
|
const fromEnv = loadOutboundHostValidatorOptionsFromEnv()
|
|
117
176
|
const direct: OutboundHostValidatorOptions = {}
|
|
118
|
-
if (options.enabled !== undefined)
|
|
177
|
+
if (options.enabled !== undefined) {
|
|
178
|
+
direct.enabled = options.enabled
|
|
179
|
+
}
|
|
119
180
|
if (options.includeDefaultMicrosoftHosts !== undefined) {
|
|
120
181
|
direct.includeDefaultMicrosoftHosts = options.includeDefaultMicrosoftHosts
|
|
121
182
|
}
|
|
122
|
-
if (options.hosts !== undefined)
|
|
183
|
+
if (options.hosts !== undefined) {
|
|
184
|
+
direct.hosts = options.hosts
|
|
185
|
+
}
|
|
186
|
+
if (options.allowPrivateNetworkAddresses !== undefined) {
|
|
187
|
+
direct.allowPrivateNetworkAddresses = options.allowPrivateNetworkAddresses
|
|
188
|
+
}
|
|
123
189
|
return mergeDefined<OutboundHostValidatorOptions>(
|
|
124
190
|
external.fallback.outboundHostValidator,
|
|
125
191
|
fromEnv,
|
|
@@ -129,25 +195,20 @@ function resolveOutboundHostValidatorOptions (
|
|
|
129
195
|
)
|
|
130
196
|
}
|
|
131
197
|
|
|
132
|
-
function getUrlHost (input: string | URL | null | undefined): string | undefined {
|
|
133
|
-
if (!input) return undefined
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
const url = input instanceof URL ? input : new URL(input)
|
|
137
|
-
return normalizeHostname(url.hostname)
|
|
138
|
-
} catch {
|
|
139
|
-
return undefined
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
198
|
function normalizeConfiguredHost (input: string): string | undefined {
|
|
144
199
|
let candidate = input?.trim()
|
|
145
|
-
if (!candidate)
|
|
146
|
-
|
|
200
|
+
if (!candidate) {
|
|
201
|
+
return undefined
|
|
202
|
+
}
|
|
203
|
+
if (candidate.startsWith('*.')) {
|
|
204
|
+
candidate = candidate.slice(2)
|
|
205
|
+
}
|
|
147
206
|
|
|
148
207
|
try {
|
|
149
208
|
const absolute = new URL(candidate)
|
|
150
|
-
if (absolute.hostname)
|
|
209
|
+
if (absolute.hostname) {
|
|
210
|
+
return normalizeHostname(absolute.hostname)
|
|
211
|
+
}
|
|
151
212
|
} catch {
|
|
152
213
|
// A bare host, host:port, or host/path is handled below.
|
|
153
214
|
}
|
|
@@ -30,16 +30,29 @@ class FileStorageInternals {
|
|
|
30
30
|
private readonly statePath: string
|
|
31
31
|
private readonly versionsPath: string
|
|
32
32
|
private readonly expirationsPath: string
|
|
33
|
+
|
|
34
|
+
/** The stored values keyed by storage key. */
|
|
33
35
|
readonly state: Record<string, unknown>
|
|
36
|
+
|
|
37
|
+
/** The current version associated with each storage key. */
|
|
34
38
|
readonly versions: Record<string, string>
|
|
39
|
+
|
|
40
|
+
/** The expiration time associated with each storage key, expressed as Unix time in milliseconds. */
|
|
35
41
|
readonly expirations: Record<string, number>
|
|
36
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Creates the shared file storage implementation.
|
|
45
|
+
*
|
|
46
|
+
* @param folder The folder containing the storage files.
|
|
47
|
+
*/
|
|
37
48
|
constructor (folder: string) {
|
|
38
49
|
fs.mkdirSync(folder, { recursive: true })
|
|
39
50
|
this.statePath = path.join(folder, 'state.json')
|
|
40
51
|
this.versionsPath = path.join(folder, 'state.versions.json')
|
|
41
52
|
this.expirationsPath = path.join(folder, 'state.expirations.json')
|
|
42
|
-
if (!fs.existsSync(this.statePath))
|
|
53
|
+
if (!fs.existsSync(this.statePath)) {
|
|
54
|
+
fs.writeFileSync(this.statePath, '{}')
|
|
55
|
+
}
|
|
43
56
|
this.state = JSON.parse(fs.readFileSync(this.statePath, 'utf8')) as Record<string, unknown>
|
|
44
57
|
this.versions = fs.existsSync(this.versionsPath)
|
|
45
58
|
? JSON.parse(fs.readFileSync(this.versionsPath, 'utf8')) as Record<string, string>
|
|
@@ -49,6 +62,7 @@ class FileStorageInternals {
|
|
|
49
62
|
: {}
|
|
50
63
|
}
|
|
51
64
|
|
|
65
|
+
/** Writes the current storage state and metadata to disk. */
|
|
52
66
|
flush (): void {
|
|
53
67
|
fs.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2))
|
|
54
68
|
if (fs.existsSync(this.versionsPath) || Object.keys(this.versions).length > 0) {
|
|
@@ -59,17 +73,34 @@ class FileStorageInternals {
|
|
|
59
73
|
}
|
|
60
74
|
}
|
|
61
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Gets the current version for a key, creating one when necessary.
|
|
78
|
+
*
|
|
79
|
+
* @param key The storage key.
|
|
80
|
+
* @returns The current version.
|
|
81
|
+
*/
|
|
62
82
|
getOrCreateVersion (key: string): string {
|
|
63
83
|
const version = this.versions[key] ?? randomUUID()
|
|
64
84
|
this.versions[key] = version
|
|
65
85
|
return version
|
|
66
86
|
}
|
|
67
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Determines whether a stored value has expired.
|
|
90
|
+
*
|
|
91
|
+
* @param key The storage key.
|
|
92
|
+
* @returns `true` when the value has expired; otherwise, `false`.
|
|
93
|
+
*/
|
|
68
94
|
isExpired (key: string): boolean {
|
|
69
95
|
const expiresAt = this.expirations[key]
|
|
70
96
|
return expiresAt !== undefined && expiresAt <= Date.now()
|
|
71
97
|
}
|
|
72
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Removes a value and its version and expiration metadata.
|
|
101
|
+
*
|
|
102
|
+
* @param key The storage key.
|
|
103
|
+
*/
|
|
73
104
|
remove (key: string): void {
|
|
74
105
|
delete this.state[key]
|
|
75
106
|
delete this.versions[key]
|
|
@@ -232,7 +263,9 @@ export class FileStorageV2 extends StorageV2 {
|
|
|
232
263
|
version: version ?? this.internals.getOrCreateVersion(key),
|
|
233
264
|
}]
|
|
234
265
|
}))
|
|
235
|
-
if (createdVersion)
|
|
266
|
+
if (createdVersion) {
|
|
267
|
+
this.internals.flush()
|
|
268
|
+
}
|
|
236
269
|
return results
|
|
237
270
|
})
|
|
238
271
|
}
|
|
@@ -277,13 +310,18 @@ export class FileStorageV2 extends StorageV2 {
|
|
|
277
310
|
const version = randomUUID()
|
|
278
311
|
this.internals.state[key] = structuredClone(value)
|
|
279
312
|
this.internals.versions[key] = version
|
|
280
|
-
if (expiresAt === undefined)
|
|
281
|
-
|
|
313
|
+
if (expiresAt === undefined) {
|
|
314
|
+
delete this.internals.expirations[key]
|
|
315
|
+
} else {
|
|
316
|
+
this.internals.expirations[key] = expiresAt
|
|
317
|
+
}
|
|
282
318
|
results[key] = { key, status: StorageOperationStatus.Succeeded, version }
|
|
283
319
|
changed = true
|
|
284
320
|
}
|
|
285
321
|
}
|
|
286
|
-
if (changed || createdVersion)
|
|
322
|
+
if (changed || createdVersion) {
|
|
323
|
+
this.internals.flush()
|
|
324
|
+
}
|
|
287
325
|
return results
|
|
288
326
|
})
|
|
289
327
|
}
|
|
@@ -325,7 +363,9 @@ export class FileStorageV2 extends StorageV2 {
|
|
|
325
363
|
changed = true
|
|
326
364
|
}
|
|
327
365
|
}
|
|
328
|
-
if (changed || createdVersion)
|
|
366
|
+
if (changed || createdVersion) {
|
|
367
|
+
this.internals.flush()
|
|
368
|
+
}
|
|
329
369
|
return results
|
|
330
370
|
})
|
|
331
371
|
}
|
|
@@ -33,6 +33,7 @@ interface MemoryStorageState {
|
|
|
33
33
|
class MemoryStorageInternals {
|
|
34
34
|
private static readonly states = new WeakMap<object, MemoryStorageState>()
|
|
35
35
|
|
|
36
|
+
/** The shared values and metadata for this storage instance. */
|
|
36
37
|
readonly state: MemoryStorageState
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -47,24 +48,53 @@ class MemoryStorageInternals {
|
|
|
47
48
|
this.state = state
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Saves a value and updates its version and expiration metadata.
|
|
53
|
+
*
|
|
54
|
+
* @param key The storage key.
|
|
55
|
+
* @param item The value to store.
|
|
56
|
+
* @param expiresAt The optional expiration time, expressed as Unix time in milliseconds.
|
|
57
|
+
* @returns The new version.
|
|
58
|
+
*/
|
|
50
59
|
save (key: string, item: unknown, expiresAt?: number): string {
|
|
51
60
|
const version = (this.state.etag++).toString()
|
|
52
61
|
this.state.memory[key] = JSON.stringify(item)
|
|
53
62
|
this.state.versions[key] = version
|
|
54
|
-
if (expiresAt === undefined)
|
|
55
|
-
|
|
63
|
+
if (expiresAt === undefined) {
|
|
64
|
+
delete this.state.expirations[key]
|
|
65
|
+
} else {
|
|
66
|
+
this.state.expirations[key] = expiresAt
|
|
67
|
+
}
|
|
56
68
|
return version
|
|
57
69
|
}
|
|
58
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Gets the version associated with a stored value.
|
|
73
|
+
*
|
|
74
|
+
* @param key The storage key.
|
|
75
|
+
* @param value The stored value.
|
|
76
|
+
* @returns The version, when available.
|
|
77
|
+
*/
|
|
59
78
|
getVersion (key: string, value: StoreItem): string | undefined {
|
|
60
79
|
return this.state.versions[key] ?? value.eTag as string | undefined
|
|
61
80
|
}
|
|
62
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Determines whether a stored value has expired.
|
|
84
|
+
*
|
|
85
|
+
* @param key The storage key.
|
|
86
|
+
* @returns `true` when the value has expired; otherwise, `false`.
|
|
87
|
+
*/
|
|
63
88
|
isExpired (key: string): boolean {
|
|
64
89
|
const expiresAt = this.state.expirations[key]
|
|
65
90
|
return expiresAt !== undefined && expiresAt <= Date.now()
|
|
66
91
|
}
|
|
67
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Removes a value and its version and expiration metadata.
|
|
95
|
+
*
|
|
96
|
+
* @param key The storage key.
|
|
97
|
+
*/
|
|
68
98
|
remove (key: string): void {
|
|
69
99
|
delete this.state.memory[key]
|
|
70
100
|
delete this.state.versions[key]
|
|
@@ -81,6 +111,11 @@ class MemoryStorageInternals {
|
|
|
81
111
|
export class MemoryStorage extends MemoryStorageInternals implements Storage {
|
|
82
112
|
private static instance: MemoryStorage
|
|
83
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Creates a legacy in-memory storage provider.
|
|
116
|
+
*
|
|
117
|
+
* @param memory Optional backing store to share with another in-memory storage instance.
|
|
118
|
+
*/
|
|
84
119
|
constructor (memory: { [key: string]: string } = {}) {
|
|
85
120
|
super(memory)
|
|
86
121
|
}
|
|
@@ -160,8 +195,11 @@ export class MemoryStorage extends MemoryStorageInternals implements Storage {
|
|
|
160
195
|
})
|
|
161
196
|
}
|
|
162
197
|
|
|
198
|
+
/** Returns the process-wide legacy memory storage instance. */
|
|
163
199
|
static getSingleInstance (): MemoryStorage {
|
|
164
|
-
if (!MemoryStorage.instance)
|
|
200
|
+
if (!MemoryStorage.instance) {
|
|
201
|
+
MemoryStorage.instance = new MemoryStorage()
|
|
202
|
+
}
|
|
165
203
|
return MemoryStorage.instance
|
|
166
204
|
}
|
|
167
205
|
}
|
|
@@ -323,8 +361,11 @@ export class MemoryStorageV2 extends StorageV2 {
|
|
|
323
361
|
})
|
|
324
362
|
}
|
|
325
363
|
|
|
364
|
+
/** Returns the process-wide V2 memory storage instance. */
|
|
326
365
|
static getSingleInstance (): MemoryStorageV2 {
|
|
327
|
-
if (!MemoryStorageV2.instance)
|
|
366
|
+
if (!MemoryStorageV2.instance) {
|
|
367
|
+
MemoryStorageV2.instance = new MemoryStorageV2()
|
|
368
|
+
}
|
|
328
369
|
return MemoryStorageV2.instance
|
|
329
370
|
}
|
|
330
371
|
}
|
package/src/storage/storage.ts
CHANGED
|
@@ -111,9 +111,13 @@ export interface Storage {
|
|
|
111
111
|
* available when `status` is {@link StorageOperationStatus.Succeeded}.
|
|
112
112
|
*/
|
|
113
113
|
export interface StorageReadResult<T extends object = Record<string, unknown>> {
|
|
114
|
+
/** The requested storage key. */
|
|
114
115
|
key: string;
|
|
116
|
+
/** The outcome of the read. */
|
|
115
117
|
status: StorageOperationStatus;
|
|
118
|
+
/** The stored value when the read succeeds. */
|
|
116
119
|
value?: T;
|
|
120
|
+
/** The stored version when available. */
|
|
117
121
|
version?: string;
|
|
118
122
|
}
|
|
119
123
|
|
|
@@ -121,8 +125,11 @@ export interface StorageReadResult<T extends object = Record<string, unknown>> {
|
|
|
121
125
|
* The result of a version 2 storage write operation.
|
|
122
126
|
*/
|
|
123
127
|
export interface StorageWriteResult {
|
|
128
|
+
/** The supplied storage key. */
|
|
124
129
|
key: string;
|
|
130
|
+
/** The outcome of the write. */
|
|
125
131
|
status: StorageOperationStatus;
|
|
132
|
+
/** The resulting or current storage version when available. */
|
|
126
133
|
version?: string;
|
|
127
134
|
}
|
|
128
135
|
|
|
@@ -130,8 +137,11 @@ export interface StorageWriteResult {
|
|
|
130
137
|
* The result of a version 2 storage delete operation.
|
|
131
138
|
*/
|
|
132
139
|
export interface StorageDeleteResult {
|
|
140
|
+
/** The supplied storage key. */
|
|
133
141
|
key: string;
|
|
142
|
+
/** The outcome of the delete. */
|
|
134
143
|
status: StorageOperationStatus;
|
|
144
|
+
/** The deleted or current storage version when available. */
|
|
135
145
|
version?: string;
|
|
136
146
|
}
|
|
137
147
|
|
|
@@ -146,16 +156,23 @@ export type StorageDeleteResults = Record<string, StorageDeleteResult>
|
|
|
146
156
|
|
|
147
157
|
/** The outcome of one version 2 storage operation. */
|
|
148
158
|
export enum StorageOperationStatus {
|
|
159
|
+
/** The operation completed successfully. */
|
|
149
160
|
Succeeded = 'succeeded',
|
|
161
|
+
/** No item exists for the key. */
|
|
150
162
|
NotFound = 'notFound',
|
|
163
|
+
/** A create-only operation found an existing item. */
|
|
151
164
|
Conflict = 'conflict',
|
|
165
|
+
/** The expected version did not match the current version. */
|
|
152
166
|
ConditionNotMet = 'conditionNotMet',
|
|
153
167
|
}
|
|
154
168
|
|
|
155
169
|
/** The write mode for a version 2 storage operation. */
|
|
156
170
|
export enum StorageWriteMode {
|
|
171
|
+
/** Creates a new item or replaces an existing item. */
|
|
157
172
|
Upsert = 'upsert',
|
|
173
|
+
/** Creates an item only when the key does not exist. */
|
|
158
174
|
CreateOnly = 'createOnly',
|
|
175
|
+
/** Replaces an item only when the key exists. */
|
|
159
176
|
Replace = 'replace',
|
|
160
177
|
}
|
|
161
178
|
|
|
@@ -168,12 +185,15 @@ export interface StorageWriteOptions {
|
|
|
168
185
|
* duration has elapsed. Values must be finite numbers greater than zero.
|
|
169
186
|
*/
|
|
170
187
|
ttl?: number;
|
|
188
|
+
/** The condition controlling whether existing items may be written. */
|
|
171
189
|
mode?: StorageWriteMode;
|
|
190
|
+
/** The storage version that must match before writing. */
|
|
172
191
|
expectedVersion?: string;
|
|
173
192
|
}
|
|
174
193
|
|
|
175
194
|
/** Options applied to every item in a version 2 delete operation. */
|
|
176
195
|
export interface StorageDeleteOptions {
|
|
196
|
+
/** The storage version that must match before deleting. */
|
|
177
197
|
expectedVersion?: string;
|
|
178
198
|
}
|
|
179
199
|
|
|
@@ -31,8 +31,12 @@ export function asStorageV2 (storage: StorageProvider): StorageV2 {
|
|
|
31
31
|
/** Returns a successful V2 read value, maps not-found to undefined, and rejects invalid results. */
|
|
32
32
|
export function getStorageReadValue<T extends object> (results: StorageReadResults<T> | null | undefined, key: string): T | undefined {
|
|
33
33
|
const result = results?.[key]
|
|
34
|
-
if (result?.status === StorageOperationStatus.NotFound)
|
|
35
|
-
|
|
34
|
+
if (result?.status === StorageOperationStatus.NotFound) {
|
|
35
|
+
return undefined
|
|
36
|
+
}
|
|
37
|
+
if (result?.status === StorageOperationStatus.Succeeded) {
|
|
38
|
+
return result.value
|
|
39
|
+
}
|
|
36
40
|
throwStorageResultError('read', key, result?.status)
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -44,7 +48,9 @@ class StorageToStorageV2Adapter extends StorageV2 {
|
|
|
44
48
|
|
|
45
49
|
async read<T extends object = Record<string, unknown>> (keys: string[]): Promise<StorageReadResults<T>> {
|
|
46
50
|
validateKeys(keys)
|
|
47
|
-
if (keys.length === 0)
|
|
51
|
+
if (keys.length === 0) {
|
|
52
|
+
return {}
|
|
53
|
+
}
|
|
48
54
|
|
|
49
55
|
const items = await this.storage.read(keys)
|
|
50
56
|
return Object.fromEntries(keys.map(key => {
|
|
@@ -64,7 +70,9 @@ class StorageToStorageV2Adapter extends StorageV2 {
|
|
|
64
70
|
throwUnsupportedOption('ttl')
|
|
65
71
|
}
|
|
66
72
|
validateExpectedVersion(options?.expectedVersion)
|
|
67
|
-
if (Object.keys(changes).length === 0)
|
|
73
|
+
if (Object.keys(changes).length === 0) {
|
|
74
|
+
return {}
|
|
75
|
+
}
|
|
68
76
|
const legacyChanges = Object.fromEntries(Object.entries(changes).map(([key, value]) => [
|
|
69
77
|
key,
|
|
70
78
|
options?.expectedVersion === undefined
|
|
@@ -81,7 +89,9 @@ class StorageToStorageV2Adapter extends StorageV2 {
|
|
|
81
89
|
if (options?.expectedVersion !== undefined) {
|
|
82
90
|
throwUnsupportedOption('expectedVersion')
|
|
83
91
|
}
|
|
84
|
-
if (keys.length === 0)
|
|
92
|
+
if (keys.length === 0) {
|
|
93
|
+
return {}
|
|
94
|
+
}
|
|
85
95
|
|
|
86
96
|
await this.storage.delete(keys)
|
|
87
97
|
return Object.fromEntries(keys.map(key => [key, { key, status: StorageOperationStatus.Succeeded }]))
|
|
@@ -116,7 +126,9 @@ function validateChanges (changes: Record<string, unknown>): void {
|
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
function removeLegacyETag<T> (value: T): T {
|
|
119
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
129
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
130
|
+
return value
|
|
131
|
+
}
|
|
120
132
|
const { eTag: _eTag, ...withoutETag } = value as Record<string, unknown>
|
|
121
133
|
return withoutETag as T
|
|
122
134
|
}
|
|
@@ -7,6 +7,13 @@ import { StorageWriteOptions } from './storage'
|
|
|
7
7
|
import { ExceptionHelper } from '@microsoft/agents-activity'
|
|
8
8
|
import { Errors } from '../errorHelper'
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Computes an absolute expiration time for a storage write.
|
|
12
|
+
*
|
|
13
|
+
* @param options The write options containing an optional TTL in seconds.
|
|
14
|
+
* @returns The expiration time in milliseconds since the Unix epoch, or `undefined` when no TTL is set.
|
|
15
|
+
* @throws A `RangeError` when the TTL is not finite or is not greater than zero.
|
|
16
|
+
*/
|
|
10
17
|
export function getStorageWriteExpiry (options?: StorageWriteOptions): number | undefined {
|
|
11
18
|
const ttl = options?.ttl
|
|
12
19
|
if (ttl === undefined) {
|
package/src/turnContext.ts
CHANGED
|
@@ -82,9 +82,16 @@ export class TurnContext {
|
|
|
82
82
|
*
|
|
83
83
|
* @param adapterOrContext The adapter that created this context, or another TurnContext to clone
|
|
84
84
|
* @param request The activity for the turn (required when first parameter is an adapter)
|
|
85
|
+
* @param identity The authenticated identity for the turn.
|
|
85
86
|
*/
|
|
86
87
|
constructor (adapterOrContext: BaseAdapter, request: Activity, identity?: JwtPayload)
|
|
88
|
+
/**
|
|
89
|
+
* Creates a context by copying another turn context.
|
|
90
|
+
*
|
|
91
|
+
* @param adapterOrContext The turn context to copy.
|
|
92
|
+
*/
|
|
87
93
|
constructor (adapterOrContext: TurnContext)
|
|
94
|
+
/** Implements the adapter and copy constructor overloads. */
|
|
88
95
|
constructor (adapterOrContext: BaseAdapter | TurnContext, request?: Activity, identity?: JwtPayload) {
|
|
89
96
|
if (adapterOrContext instanceof TurnContext) {
|
|
90
97
|
adapterOrContext.copyTo(this)
|
|
@@ -367,6 +374,7 @@ export class TurnContext {
|
|
|
367
374
|
return this._activity as Activity
|
|
368
375
|
}
|
|
369
376
|
|
|
377
|
+
/** Gets the authenticated identity for the turn. */
|
|
370
378
|
get identity (): JwtPayload {
|
|
371
379
|
return this._identity as JwtPayload
|
|
372
380
|
}
|
|
@@ -382,6 +390,7 @@ export class TurnContext {
|
|
|
382
390
|
return this._respondedRef.responded
|
|
383
391
|
}
|
|
384
392
|
|
|
393
|
+
/** Marks the turn as having sent a response. */
|
|
385
394
|
set responded (value: boolean) {
|
|
386
395
|
if (!value) {
|
|
387
396
|
throw ExceptionHelper.generateException(Error, Errors.CannotSetRespondedToFalse)
|
|
@@ -404,6 +413,7 @@ export class TurnContext {
|
|
|
404
413
|
return undefined
|
|
405
414
|
}
|
|
406
415
|
|
|
416
|
+
/** Sets the locale stored in the turn state. */
|
|
407
417
|
set locale (value: string | undefined) {
|
|
408
418
|
let turnObj = this._turnState.get(this._turn)
|
|
409
419
|
if (turnObj) {
|
|
@@ -426,6 +436,7 @@ export class TurnContext {
|
|
|
426
436
|
return this._turnState
|
|
427
437
|
}
|
|
428
438
|
|
|
439
|
+
/** Gets the streaming response associated with this context. */
|
|
429
440
|
get streamingResponse (): StreamingResponse {
|
|
430
441
|
return this._streamingResponse
|
|
431
442
|
}
|