@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.
Files changed (173) hide show
  1. package/README.md +83 -7
  2. package/dist/package.json +4 -4
  3. package/dist/src/activityHandler.js +6 -3
  4. package/dist/src/activityHandler.js.map +1 -1
  5. package/dist/src/agent-client/agentResponseHandler.d.ts +7 -0
  6. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  7. package/dist/src/agent-client/createAgentResponseHandler.d.ts +2 -0
  8. package/dist/src/agent-client/createAgentResponseHandler.js.map +1 -1
  9. package/dist/src/app/adaptiveCards/adaptiveCardsActions.d.ts +1 -0
  10. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +1 -0
  11. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
  12. package/dist/src/app/attachmentDownloadHttpClient.d.ts +13 -0
  13. package/dist/src/app/attachmentDownloadHttpClient.js +80 -0
  14. package/dist/src/app/attachmentDownloadHttpClient.js.map +1 -0
  15. package/dist/src/app/attachmentDownloader.d.ts +2 -0
  16. package/dist/src/app/attachmentDownloader.js +6 -11
  17. package/dist/src/app/attachmentDownloader.js.map +1 -1
  18. package/dist/src/app/auth/authorization.d.ts +58 -0
  19. package/dist/src/app/auth/authorization.js.map +1 -1
  20. package/dist/src/app/proactive/conversation.d.ts +18 -0
  21. package/dist/src/app/proactive/conversation.js +6 -0
  22. package/dist/src/app/proactive/conversation.js.map +1 -1
  23. package/dist/src/app/proactive/conversationBuilder.js +2 -1
  24. package/dist/src/app/proactive/conversationBuilder.js.map +1 -1
  25. package/dist/src/app/proactive/conversationReferenceBuilder.js +8 -4
  26. package/dist/src/app/proactive/conversationReferenceBuilder.js.map +1 -1
  27. package/dist/src/app/proactive/createConversationOptionsBuilder.d.ts +1 -0
  28. package/dist/src/app/proactive/createConversationOptionsBuilder.js +2 -1
  29. package/dist/src/app/proactive/createConversationOptionsBuilder.js.map +1 -1
  30. package/dist/src/app/proactive/proactive.d.ts +6 -0
  31. package/dist/src/app/proactive/proactive.js +32 -1
  32. package/dist/src/app/proactive/proactive.js.map +1 -1
  33. package/dist/src/app/routeList.d.ts +21 -0
  34. package/dist/src/app/routeList.js +27 -3
  35. package/dist/src/app/routeList.js.map +1 -1
  36. package/dist/src/app/teamsAttachmentDownloader.d.ts +8 -0
  37. package/dist/src/app/teamsAttachmentDownloader.js +17 -12
  38. package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
  39. package/dist/src/auth/authConfiguration.d.ts +1 -0
  40. package/dist/src/auth/authConfiguration.js.map +1 -1
  41. package/dist/src/auth/authConstants.d.ts +7 -0
  42. package/dist/src/auth/authConstants.js +7 -0
  43. package/dist/src/auth/authConstants.js.map +1 -1
  44. package/dist/src/auth/authProvider.d.ts +36 -0
  45. package/dist/src/auth/connectionManager.js +2 -1
  46. package/dist/src/auth/connectionManager.js.map +1 -1
  47. package/dist/src/auth/connections.d.ts +1 -0
  48. package/dist/src/auth/msal/msalConnectionManager.d.ts +7 -0
  49. package/dist/src/auth/msal/msalConnectionManager.js +7 -0
  50. package/dist/src/auth/msal/msalConnectionManager.js.map +1 -1
  51. package/dist/src/auth/msal/msalTokenProvider.d.ts +44 -0
  52. package/dist/src/auth/msal/msalTokenProvider.js +43 -0
  53. package/dist/src/auth/msal/msalTokenProvider.js.map +1 -1
  54. package/dist/src/auth/settings.d.ts +28 -0
  55. package/dist/src/auth/settings.js +23 -0
  56. package/dist/src/auth/settings.js.map +1 -1
  57. package/dist/src/auth/sidecar/sidecarAuthProvider.d.ts +3 -0
  58. package/dist/src/auth/sidecar/sidecarAuthProvider.js.map +1 -1
  59. package/dist/src/auth/sidecar/sidecarHttpClient.d.ts +1 -9
  60. package/dist/src/auth/sidecar/sidecarHttpClient.js +3 -69
  61. package/dist/src/auth/sidecar/sidecarHttpClient.js.map +1 -1
  62. package/dist/src/baseAdapter.d.ts +6 -0
  63. package/dist/src/baseAdapter.js.map +1 -1
  64. package/dist/src/cards/taskModuleAction.d.ts +6 -0
  65. package/dist/src/cards/taskModuleAction.js +6 -0
  66. package/dist/src/cards/taskModuleAction.js.map +1 -1
  67. package/dist/src/cloudAdapter.d.ts +29 -0
  68. package/dist/src/cloudAdapter.js +45 -8
  69. package/dist/src/cloudAdapter.js.map +1 -1
  70. package/dist/src/configuration/configuration.d.ts +1 -0
  71. package/dist/src/configuration/configuration.js +2 -1
  72. package/dist/src/configuration/configuration.js.map +1 -1
  73. package/dist/src/configuration/environmentConfiguration.js +1 -1
  74. package/dist/src/configuration/environmentConfiguration.js.map +1 -1
  75. package/dist/src/connector-client/connectorClient.d.ts +8 -0
  76. package/dist/src/connector-client/connectorClient.js +8 -0
  77. package/dist/src/connector-client/connectorClient.js.map +1 -1
  78. package/dist/src/createCloudAdapter.d.ts +2 -0
  79. package/dist/src/createCloudAdapter.js.map +1 -1
  80. package/dist/src/errorHelper.d.ts +1 -0
  81. package/dist/src/errorHelper.js +9 -0
  82. package/dist/src/errorHelper.js.map +1 -1
  83. package/dist/src/headerPropagation.d.ts +38 -0
  84. package/dist/src/headerPropagation.js +33 -0
  85. package/dist/src/headerPropagation.js.map +1 -1
  86. package/dist/src/httpClient.d.ts +58 -0
  87. package/dist/src/httpClient.js +40 -0
  88. package/dist/src/httpClient.js.map +1 -1
  89. package/dist/src/interfaces/webResponse.d.ts +7 -0
  90. package/dist/src/middlewareSet.d.ts +6 -0
  91. package/dist/src/middlewareSet.js.map +1 -1
  92. package/dist/src/oauth/userTokenClient.d.ts +6 -0
  93. package/dist/src/oauth/userTokenClient.js +12 -0
  94. package/dist/src/oauth/userTokenClient.js.map +1 -1
  95. package/dist/src/outboundHostValidator.d.ts +33 -1
  96. package/dist/src/outboundHostValidator.js +60 -25
  97. package/dist/src/outboundHostValidator.js.map +1 -1
  98. package/dist/src/storage/fileStorage.d.ts +26 -0
  99. package/dist/src/storage/fileStorage.js +35 -6
  100. package/dist/src/storage/fileStorage.js.map +1 -1
  101. package/dist/src/storage/memoryStorage.d.ts +34 -0
  102. package/dist/src/storage/memoryStorage.js +41 -4
  103. package/dist/src/storage/memoryStorage.js.map +1 -1
  104. package/dist/src/storage/storage.d.ts +20 -0
  105. package/dist/src/storage/storage.js +7 -0
  106. package/dist/src/storage/storage.js.map +1 -1
  107. package/dist/src/storage/storageCompatibility.js +12 -6
  108. package/dist/src/storage/storageCompatibility.js.map +1 -1
  109. package/dist/src/storage/storageExpiry.d.ts +7 -0
  110. package/dist/src/storage/storageExpiry.js +7 -0
  111. package/dist/src/storage/storageExpiry.js.map +1 -1
  112. package/dist/src/turnContext.d.ts +10 -0
  113. package/dist/src/turnContext.js +5 -0
  114. package/dist/src/turnContext.js.map +1 -1
  115. package/dist/src/utils/env.js +22 -11
  116. package/dist/src/utils/env.js.map +1 -1
  117. package/dist/src/utils/ipAddress.d.ts +6 -0
  118. package/dist/src/utils/ipAddress.js +64 -0
  119. package/dist/src/utils/ipAddress.js.map +1 -0
  120. package/dist/src/utils/outboundUrl.d.ts +7 -0
  121. package/dist/src/utils/outboundUrl.js +52 -0
  122. package/dist/src/utils/outboundUrl.js.map +1 -0
  123. package/dist/src/utils.js +2 -1
  124. package/dist/src/utils.js.map +1 -1
  125. package/package.json +4 -4
  126. package/src/activityHandler.ts +9 -3
  127. package/src/agent-client/agentResponseHandler.ts +7 -0
  128. package/src/agent-client/createAgentResponseHandler.ts +2 -0
  129. package/src/app/adaptiveCards/adaptiveCardsActions.ts +1 -0
  130. package/src/app/attachmentDownloadHttpClient.ts +80 -0
  131. package/src/app/attachmentDownloader.ts +6 -9
  132. package/src/app/auth/authorization.ts +58 -0
  133. package/src/app/proactive/conversation.ts +24 -0
  134. package/src/app/proactive/conversationBuilder.ts +3 -1
  135. package/src/app/proactive/conversationReferenceBuilder.ts +12 -4
  136. package/src/app/proactive/createConversationOptionsBuilder.ts +5 -1
  137. package/src/app/proactive/proactive.ts +33 -1
  138. package/src/app/routeList.ts +30 -3
  139. package/src/app/teamsAttachmentDownloader.ts +17 -10
  140. package/src/auth/authConfiguration.ts +1 -0
  141. package/src/auth/authConstants.ts +7 -0
  142. package/src/auth/authProvider.ts +36 -0
  143. package/src/auth/connectionManager.ts +3 -1
  144. package/src/auth/connections.ts +1 -0
  145. package/src/auth/msal/msalConnectionManager.ts +7 -0
  146. package/src/auth/msal/msalTokenProvider.ts +59 -0
  147. package/src/auth/settings.ts +28 -0
  148. package/src/auth/sidecar/sidecarAuthProvider.ts +4 -0
  149. package/src/auth/sidecar/sidecarHttpClient.ts +3 -63
  150. package/src/baseAdapter.ts +6 -0
  151. package/src/cards/taskModuleAction.ts +6 -0
  152. package/src/cloudAdapter.ts +59 -10
  153. package/src/configuration/configuration.ts +4 -2
  154. package/src/configuration/environmentConfiguration.ts +7 -1
  155. package/src/connector-client/connectorClient.ts +8 -0
  156. package/src/createCloudAdapter.ts +2 -0
  157. package/src/errorHelper.ts +11 -0
  158. package/src/headerPropagation.ts +38 -0
  159. package/src/httpClient.ts +58 -0
  160. package/src/interfaces/webResponse.ts +7 -0
  161. package/src/middlewareSet.ts +6 -0
  162. package/src/oauth/userTokenClient.ts +13 -0
  163. package/src/outboundHostValidator.ts +84 -23
  164. package/src/storage/fileStorage.ts +46 -6
  165. package/src/storage/memoryStorage.ts +45 -4
  166. package/src/storage/storage.ts +20 -0
  167. package/src/storage/storageCompatibility.ts +18 -6
  168. package/src/storage/storageExpiry.ts +7 -0
  169. package/src/turnContext.ts +11 -0
  170. package/src/utils/env.ts +33 -11
  171. package/src/utils/ipAddress.ts +63 -0
  172. package/src/utils/outboundUrl.ts +50 -0
  173. package/src/utils.ts +3 -1
@@ -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) suffixes.add(host)
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) suffixes.add(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) return true
117
+ if (!this.enabled) {
118
+ return true
119
+ }
84
120
 
85
- const host = getUrlHost(input)
86
- if (!host) return false
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}`)) return true
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) direct.enabled = options.enabled
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) direct.hosts = options.hosts
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) return undefined
146
- if (candidate.startsWith('*.')) candidate = candidate.slice(2)
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) return normalizeHostname(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)) fs.writeFileSync(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) this.internals.flush()
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) delete this.internals.expirations[key]
281
- else this.internals.expirations[key] = expiresAt
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) this.internals.flush()
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) this.internals.flush()
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) delete this.state.expirations[key]
55
- else this.state.expirations[key] = expiresAt
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) MemoryStorage.instance = new MemoryStorage()
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) MemoryStorageV2.instance = new MemoryStorageV2()
366
+ if (!MemoryStorageV2.instance) {
367
+ MemoryStorageV2.instance = new MemoryStorageV2()
368
+ }
328
369
  return MemoryStorageV2.instance
329
370
  }
330
371
  }
@@ -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) return undefined
35
- if (result?.status === StorageOperationStatus.Succeeded) return result.value
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) return {}
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) return {}
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) return {}
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)) return 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) {
@@ -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
  }