@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
@@ -24,16 +24,28 @@ const AUTHORITY_DEFAULT = 'https://login.microsoftonline.com'
24
24
  * (matched by audience and/or serviceUrl) to a named connection.
25
25
  */
26
26
  export interface ConnectionMapItem {
27
+ /** Service URL pattern matched by this route. */
27
28
  serviceUrl: string
29
+ /** Optional token audience matched by this route. */
28
30
  audience?: string
31
+ /** Name of the connection selected by this route. */
29
32
  connection: string
30
33
  }
31
34
 
35
+ /** Default route that selects the standard service connection. */
32
36
  export const DEFAULT_CONNECTION_MAP: ConnectionMapItem = { serviceUrl: '*', connection: DEFAULT_CONNECTION }
33
37
 
38
+ /** Property names accepted as connection-level settings. */
34
39
  export type ConnectionKeys = keyof Omit<AuthConfiguration, 'connections' | 'connectionsMap'>
40
+ /** Property names accepted in a connection-map entry. */
35
41
  export type ConnectionMapKeys = keyof ConnectionMapItem
36
42
 
43
+ /**
44
+ * Applies authority, issuer, alias, and connection-map defaults to a configuration.
45
+ *
46
+ * @param config - Authentication configuration to normalize.
47
+ * @returns A normalized copy of the configuration.
48
+ */
37
49
  export function applyDefaultSettings (config: AuthConfiguration) {
38
50
  const settings = { ...config }
39
51
  settings.authorityEndpoint ??= settings.authority ?? AUTHORITY_DEFAULT
@@ -106,6 +118,13 @@ function trimTrailingSlashes (value: string): string {
106
118
  return end === value.length ? value : value.slice(0, end)
107
119
  }
108
120
 
121
+ /**
122
+ * Builds the default trusted issuer list for a tenant and authority.
123
+ *
124
+ * @param tenantId - Microsoft Entra tenant ID.
125
+ * @param authority - Authority URL used to determine the cloud and effective tenant.
126
+ * @returns Trusted Bot Framework and Microsoft Entra issuer URLs.
127
+ */
109
128
  export function getDefaultIssuers (tenantId: string, authority: string) : string[] {
110
129
  // Convert empty string to undefined so resolveAuthority applies its 'botframework.com' default
111
130
  const t = getEffectiveTenant(tenantId || undefined, authority)
@@ -268,14 +287,23 @@ export interface AuthConfiguration extends ConnectionSettings {
268
287
  * Supported authentication types for agent connections.
269
288
  */
270
289
  export enum AuthType {
290
+ /** Client authentication using an X.509 certificate. */
271
291
  Certificate = 'Certificate',
292
+ /** Client authentication using a certificate subject name. */
272
293
  CertificateSubjectName = 'CertificateSubjectName',
294
+ /** Client authentication using a client secret. */
273
295
  ClientSecret = 'ClientSecret',
296
+ /** Authentication using a user-assigned managed identity. */
274
297
  UserManagedIdentity = 'UserManagedIdentity',
298
+ /** Authentication using a system-assigned managed identity. */
275
299
  SystemManagedIdentity = 'SystemManagedIdentity',
300
+ /** Authentication using federated identity credentials. */
276
301
  FederatedCredentials = 'FederatedCredentials',
302
+ /** Authentication using a workload identity token. */
277
303
  WorkloadIdentity = 'WorkloadIdentity',
304
+ /** Authentication through Identity Proxy Manager. */
278
305
  IdentityProxyManager = 'IdentityProxyManager',
306
+ /** Authentication through the Microsoft Entra authentication sidecar. */
279
307
  EntraAuthSideCar = 'EntraAuthSideCar'
280
308
  }
281
309
 
@@ -134,6 +134,10 @@ export class SidecarAuthProvider implements AuthProvider {
134
134
  * On-behalf-of token exchange — not supported by the sidecar provider in Phase 1.
135
135
  */
136
136
  async acquireTokenOnBehalfOf (scopes: string[], oboAssertion: string): Promise<string>
137
+
138
+ /**
139
+ * On-behalf-of token exchange with explicit authentication settings — not supported by the sidecar provider in Phase 1.
140
+ */
137
141
  async acquireTokenOnBehalfOf (authConfig: AuthConfiguration, scopes: string[], oboAssertion: string): Promise<string>
138
142
  async acquireTokenOnBehalfOf (
139
143
  _authConfigOrScopes: AuthConfiguration | string[],
@@ -3,10 +3,10 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { isIP } from 'node:net'
7
6
  import { debug } from '@microsoft/agents-telemetry'
8
7
  import { ExceptionHelper } from '@microsoft/agents-activity'
9
8
  import { Errors } from '../../errorHelper'
9
+ import { isPrivateOrLoopbackAddress } from '../../utils/ipAddress'
10
10
  import {
11
11
  DEFAULT_REQUEST_TIMEOUT_MS,
12
12
  DEFAULT_RETRY_COUNT,
@@ -23,7 +23,7 @@ const DEFAULT_RETRY_BACKOFF_BASE_MS = 2000
23
23
  /**
24
24
  * Reusable HTTP client for communicating with the Microsoft Entra Agent ID sidecar (agent container).
25
25
  * Handles URL construction, query parameter building, response parsing, retry, and SSRF-safe base
26
- * URL validation. Uses the native `fetch` API (Node.js 20+).
26
+ * URL validation.
27
27
  */
28
28
  export class SidecarHttpClient {
29
29
  /** Default per-attempt request timeout (milliseconds). */
@@ -139,68 +139,8 @@ export class SidecarHttpClient {
139
139
  }
140
140
 
141
141
  private static isLoopbackOrPrivateHost (host: string): boolean {
142
- // URL.hostname wraps IPv6 literals in brackets (e.g. "[::1]"); strip them before classification.
143
- const normalized = host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host
144
- const ipVersion = isIP(normalized)
145
- if (ipVersion === 0) {
146
- // DNS host: only "localhost" (and subdomains) is treated as safe.
147
- const lower = normalized.toLowerCase()
148
- return lower === 'localhost' || lower.endsWith('.localhost')
149
- }
150
- if (ipVersion === 4) {
151
- return SidecarHttpClient.isPrivateOrLoopbackIPv4(normalized)
152
- }
153
- return SidecarHttpClient.isPrivateOrLoopbackIPv6(normalized)
154
- }
155
-
156
- private static isPrivateOrLoopbackIPv4 (host: string): boolean {
157
- const b = host.split('.').map((p) => parseInt(p, 10))
158
- if (b.length !== 4 || b.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
159
- return false
160
- }
161
- if (b[0] === 127) return true // 127.0.0.0/8 loopback
162
- if (b[0] === 10) return true // 10.0.0.0/8
163
- if (b[0] === 172 && b[1] >= 16 && b[1] <= 31) return true // 172.16.0.0/12
164
- if (b[0] === 192 && b[1] === 168) return true // 192.168.0.0/16
165
- if (b[0] === 169 && b[1] === 254) return true // 169.254.0.0/16 link-local
166
- return false
167
- }
168
-
169
- private static isPrivateOrLoopbackIPv6 (host: string): boolean {
170
142
  const lower = host.toLowerCase()
171
- if (lower === '::1' || lower === '0:0:0:0:0:0:0:1') return true // loopback (`::` is the unspecified address, not loopback)
172
- // IPv4-mapped/-translated addresses in either dotted (::ffff:127.0.0.1)
173
- // or hex-compressed (::ffff:7f00:1) form.
174
- const mappedIPv4 = SidecarHttpClient.extractMappedIPv4(lower)
175
- if (mappedIPv4) {
176
- return SidecarHttpClient.isPrivateOrLoopbackIPv4(mappedIPv4)
177
- }
178
- const firstHextet = parseInt(lower.split(':')[0], 16)
179
- if (!Number.isNaN(firstHextet)) {
180
- if ((firstHextet & 0xfe00) === 0xfc00) return true // fc00::/7 unique local
181
- if ((firstHextet & 0xffc0) === 0xfe80) return true // fe80::/10 link-local
182
- if ((firstHextet & 0xffc0) === 0xfec0) return true // fec0::/10 site-local (deprecated)
183
- }
184
- return false
185
- }
186
-
187
- /**
188
- * Extracts the embedded IPv4 address from an IPv4-mapped/-translated IPv6 literal, returning it
189
- * in dotted-quad form, or `undefined` if `host` is not such an address. Handles both the dotted
190
- * form (`::ffff:127.0.0.1`) and the hex-compressed form Node emits (`::ffff:7f00:1`).
191
- */
192
- private static extractMappedIPv4 (host: string): string | undefined {
193
- const dotted = host.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/)
194
- if (dotted) {
195
- return dotted[1]
196
- }
197
- const hex = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/)
198
- if (hex) {
199
- const high = parseInt(hex[1], 16)
200
- const low = parseInt(hex[2], 16)
201
- return `${high >> 8}.${high & 0xff}.${low >> 8}.${low & 0xff}`
202
- }
203
- return undefined
143
+ return lower === 'localhost' || lower.endsWith('.localhost') || isPrivateOrLoopbackAddress(host)
204
144
  }
205
145
 
206
146
  /**
@@ -88,6 +88,12 @@ export abstract class BaseAdapter {
88
88
 
89
89
  /**
90
90
  * Continues a conversation.
91
+ *
92
+ * @remarks
93
+ * Errors thrown by `logic` are passed to {@link onTurnError}. If the error handler completes
94
+ * normally, this method resolves. To observe the original error in the caller, catch it inside
95
+ * `logic` and throw it after this method completes.
96
+ *
91
97
  * @param reference - The conversation reference to continue.
92
98
  * @param logic - The logic to execute.
93
99
  * @returns A promise representing the completion of the continue operation.
@@ -9,6 +9,12 @@ import { CardAction } from '@microsoft/agents-activity'
9
9
  * Represents a task module action.
10
10
  */
11
11
  export class TaskModuleAction implements CardAction {
12
+ /**
13
+ * Creates an action that invokes a `task/fetch` task module.
14
+ *
15
+ * @param title The action title.
16
+ * @param value The action data as an object or JSON string.
17
+ */
12
18
  constructor (title: string, value: any) {
13
19
  this.type = 'invoke'
14
20
  this.title = title
@@ -152,7 +152,9 @@ function sanitizeForLog (value: string, max = 256): string {
152
152
  function truncateActivityForLog (activity: unknown, max = 1024): string {
153
153
  try {
154
154
  const json = JSON.stringify(activity)
155
- if (!json) return '<unserializable>'
155
+ if (!json) {
156
+ return '<unserializable>'
157
+ }
156
158
  // Strip C0/DEL/C1 controls + U+2028/U+2029 from the serialized form so
157
159
  // attacker-controlled fields (e.g. activity.text) can't forge log lines.
158
160
  // eslint-disable-next-line no-control-regex
@@ -651,18 +653,30 @@ export class CloudAdapter extends BaseAdapter {
651
653
  * should be rejected with a 400.
652
654
  */
653
655
  private validateServiceUrl (identity: JwtPayload | undefined, activity: Activity): boolean {
654
- if (!this.isOutboundServiceUrlAllowed(activity.serviceUrl)) return false
656
+ if (!this.isOutboundServiceUrlAllowed(activity.serviceUrl)) {
657
+ return false
658
+ }
655
659
 
656
- if (!identity) return true
657
- if (!activity.serviceUrl) return true
660
+ if (!identity) {
661
+ return true
662
+ }
663
+ if (!activity.serviceUrl) {
664
+ return true
665
+ }
658
666
 
659
667
  const claimValue = identity.serviceurl
660
- if (typeof claimValue !== 'string') return true
668
+ if (typeof claimValue !== 'string') {
669
+ return true
670
+ }
661
671
 
662
672
  let claimHost: string | undefined
663
673
  let activityHost: string | undefined
664
- try { claimHost = new URL(claimValue).hostname.toLowerCase() } catch { /* invalid */ }
665
- try { activityHost = new URL(activity.serviceUrl).hostname.toLowerCase() } catch { /* invalid */ }
674
+ try {
675
+ claimHost = new URL(claimValue).hostname.toLowerCase()
676
+ } catch { /* invalid */ }
677
+ try {
678
+ activityHost = new URL(activity.serviceUrl).hostname.toLowerCase()
679
+ } catch { /* invalid */ }
666
680
 
667
681
  if (claimHost && activityHost && claimHost === activityHost) {
668
682
  return true
@@ -682,7 +696,9 @@ export class CloudAdapter extends BaseAdapter {
682
696
  * Applies the shared outbound host policy.
683
697
  */
684
698
  private isOutboundServiceUrlAllowed (serviceUrl: string | undefined): boolean {
685
- if (!this._hostValidator.enabled || !serviceUrl || this._hostValidator.isAllowed(serviceUrl)) return true
699
+ if (!this._hostValidator.enabled || !serviceUrl || this._hostValidator.isAllowed(serviceUrl)) {
700
+ return true
701
+ }
686
702
 
687
703
  logger.warn(`ServiceUrl host is not in the configured allowed hosts. ServiceUrl='${sanitizeForLog(serviceUrl)}'`)
688
704
  return false
@@ -751,6 +767,35 @@ export class CloudAdapter extends BaseAdapter {
751
767
 
752
768
  /**
753
769
  * Continues a conversation.
770
+ *
771
+ * @remarks
772
+ * Errors thrown by `logic` are passed to {@link BaseAdapter.onTurnError}. If the error handler
773
+ * completes normally, this method resolves. A `try`/`catch` around `continueConversation` will
774
+ * therefore not observe the original error.
775
+ *
776
+ * To observe an error from a proactive operation, catch it inside `logic` and throw it after
777
+ * `continueConversation` completes:
778
+ *
779
+ * ```typescript
780
+ * let operationFailed = false
781
+ * let operationError: unknown
782
+ *
783
+ * await adapter.continueConversation(botAppId, reference, async (context) => {
784
+ * try {
785
+ * await context.sendActivity('Proactive message')
786
+ * } catch (error) {
787
+ * operationFailed = true
788
+ * operationError = error
789
+ * }
790
+ * })
791
+ *
792
+ * if (operationFailed) {
793
+ * throw operationError
794
+ * }
795
+ * ```
796
+ *
797
+ * Catching the error inside `logic` prevents `onTurnError` from receiving that error.
798
+ *
754
799
  * @param botAppIdOrIdentity - The bot identity to use when continuing the conversation. This can be either:
755
800
  * a string containing the bot's App ID (botId) or a JwtPayload object containing identity claims (must include aud).
756
801
  * @param reference - The conversation reference to continue.
@@ -891,8 +936,12 @@ export class CloudAdapter extends BaseAdapter {
891
936
  if (typeof serviceUrl !== 'string' || !serviceUrl) {
892
937
  throw ExceptionHelper.generateException(TypeError, Errors.ServiceUrlRequired)
893
938
  }
894
- if (!conversationParameters) throw ExceptionHelper.generateException(TypeError, Errors.ConversationParametersRequired)
895
- if (!logic) throw ExceptionHelper.generateException(TypeError, Errors.LogicParameterRequired)
939
+ if (!conversationParameters) {
940
+ throw ExceptionHelper.generateException(TypeError, Errors.ConversationParametersRequired)
941
+ }
942
+ if (!logic) {
943
+ throw ExceptionHelper.generateException(TypeError, Errors.LogicParameterRequired)
944
+ }
896
945
 
897
946
  const identity = CloudAdapter.createIdentity(audience)
898
947
  const restClient = await this.createConnectorClient(serviceUrl, audience, identity)
@@ -27,6 +27,7 @@ export type CloudAdapterConfigurationPatch = Partial<{
27
27
  export type OutboundHostValidatorConfigurationPatch = Partial<{
28
28
  enabled: boolean
29
29
  includeDefaultMicrosoftHosts: boolean
30
+ allowPrivateNetworkAddresses: boolean
30
31
  hosts: readonly string[]
31
32
  }>
32
33
  export type AuthorizationHandlerPatch = Record<string, unknown>
@@ -105,6 +106,7 @@ const connectionMapParsers = {
105
106
  const outboundHostValidatorParsers = {
106
107
  enabled: asBoolean,
107
108
  includeDefaultMicrosoftHosts: asBoolean,
109
+ allowPrivateNetworkAddresses: asBoolean,
108
110
  hosts: asList
109
111
  } satisfies Record<string, (value: string) => unknown>
110
112
 
@@ -489,7 +491,7 @@ function parseValue (
489
491
  ? parseDocumentValue(parser, value)
490
492
  : typeof value === 'string' ? parser(value) : undefined
491
493
  if (parsed === undefined) {
492
- if (key === 'validateIssuer') {
494
+ if (key === 'validateIssuer' || key === 'allowPrivateNetworkAddresses') {
493
495
  throw ExceptionHelper.generateException(
494
496
  TypeError,
495
497
  Errors.InvalidBooleanConfigurationValue,
@@ -598,7 +600,7 @@ export function setConfigurationValue (
598
600
  if (parsed.key === 'hosts') {
599
601
  ;(target.outboundHostValidator as OutboundHostValidatorConfigurationPatch).hosts = parsed.value as string[]
600
602
  } else {
601
- ;(target.outboundHostValidator as OutboundHostValidatorConfigurationPatch)[parsed.key as 'enabled' | 'includeDefaultMicrosoftHosts'] = parsed.value as boolean
603
+ ;(target.outboundHostValidator as OutboundHostValidatorConfigurationPatch)[parsed.key as 'enabled' | 'includeDefaultMicrosoftHosts' | 'allowPrivateNetworkAddresses'] = parsed.value as boolean
602
604
  }
603
605
  return
604
606
  }
@@ -256,7 +256,13 @@ function bindOutboundHostValidator (
256
256
  emitHierarchySuggestion(sourceName, [parts[0], suggestedProperty].join('__'), true)
257
257
  }
258
258
  }
259
- trySet(layer, `outboundHostValidator.${parts[1]}`, value, sourceName)
259
+ trySet(
260
+ layer,
261
+ `outboundHostValidator.${parts[1]}`,
262
+ value,
263
+ sourceName,
264
+ parts[1].toLowerCase() === 'allowprivatenetworkaddresses'
265
+ )
260
266
  }
261
267
 
262
268
  function bindAuthorizationHandler (
@@ -53,6 +53,7 @@ export class ConnectorClient {
53
53
  this._httpClient = httpClient
54
54
  }
55
55
 
56
+ /** Gets the HTTP client used for Connector API requests. */
56
57
  public get httpClient (): HttpClient {
57
58
  return this._httpClient
58
59
  }
@@ -127,6 +128,13 @@ export class ConnectorClient {
127
128
  })
128
129
  }
129
130
 
131
+ /**
132
+ * Retrieves a member of a conversation.
133
+ *
134
+ * @param userId The member's channel user ID.
135
+ * @param conversationId The conversation ID.
136
+ * @returns The member's channel account.
137
+ */
130
138
  public async getConversationMember (userId: string, conversationId: string): Promise<ChannelAccount> {
131
139
  return trace(ConnectorClientTraceDefinitions.getConversationMember, async ({ record }) => {
132
140
  if (!userId || !conversationId) {
@@ -15,7 +15,9 @@ import { TurnState } from './app/turnState'
15
15
  * Result of creating a CloudAdapter from an agent.
16
16
  */
17
17
  export interface CloudAdapterResult {
18
+ /** The adapter to use for processing requests. */
18
19
  adapter: CloudAdapter
20
+ /** The application's outbound header propagation callback, when configured. */
19
21
  headerPropagation: HeaderPropagationDefinition | undefined
20
22
  }
21
23
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { AgentErrorDefinition } from '@microsoft/agents-activity'
5
5
 
6
+ /** Error definitions used by the hosting package. */
6
7
  export const Errors: { [key: string]: AgentErrorDefinition } = {
7
8
  // ============================================================================
8
9
  // TurnContext and Activity Errors (-120000 to -120090)
@@ -295,6 +296,16 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
295
296
  description: 'HttpClient requires baseURL when using a relative URL: {url}'
296
297
  },
297
298
 
299
+ AttachmentDownloadRedirectLimit: {
300
+ code: -120292,
301
+ description: 'Attachment download exceeded the maximum of 50 redirects.'
302
+ },
303
+
304
+ AttachmentDownloadInvalidRedirect: {
305
+ code: -120293,
306
+ description: 'Attachment download returned an invalid redirect URL.'
307
+ },
308
+
298
309
  // ============================================================================
299
310
  // Connection and Authentication Errors (-120300 to -120599)
300
311
  // ============================================================================
@@ -17,14 +17,21 @@ export class HeaderPropagation implements HeaderPropagationCollection {
17
17
 
18
18
  private _protectedHeaders = ['x-ms-correlation-id']
19
19
 
20
+ /** Gets the normalized headers received on the incoming request. */
20
21
  public get incoming (): Record<string, string> {
21
22
  return this._incomingRequests
22
23
  }
23
24
 
25
+ /** Gets the headers selected for propagation to outgoing requests. */
24
26
  public get outgoing (): Record<string, string> {
25
27
  return this._outgoingHeaders
26
28
  }
27
29
 
30
+ /**
31
+ * Creates a header propagation collection from incoming request headers.
32
+ *
33
+ * @param headers The incoming headers, including optional multi-value headers.
34
+ */
28
35
  constructor (headers: Record<string, string | string[] | undefined>) {
29
36
  if (!headers) {
30
37
  throw ExceptionHelper.generateException(Error, Errors.HeadersRequired)
@@ -39,6 +46,11 @@ export class HeaderPropagation implements HeaderPropagationCollection {
39
46
  this._protectedHeaders = protectedHeaders // Restore protected headers list after propagation
40
47
  }
41
48
 
49
+ /**
50
+ * Copies existing incoming header values to the outgoing collection.
51
+ *
52
+ * @param headers The header names to propagate.
53
+ */
42
54
  propagate (headers: string[]) {
43
55
  this.process(headers.map(h => [h, '']), key => {
44
56
  if (this._incomingRequests[key] && !this._outgoingHeaders[key]) {
@@ -47,6 +59,11 @@ export class HeaderPropagation implements HeaderPropagationCollection {
47
59
  })
48
60
  }
49
61
 
62
+ /**
63
+ * Adds headers that do not already exist in either collection.
64
+ *
65
+ * @param headers The headers to add.
66
+ */
50
67
  add (headers: Record<string, string>) {
51
68
  this.process(Object.entries(headers ?? {}), (key, value) => {
52
69
  if (!this._incomingRequests[key] && !this._outgoingHeaders[key]) {
@@ -55,6 +72,11 @@ export class HeaderPropagation implements HeaderPropagationCollection {
55
72
  })
56
73
  }
57
74
 
75
+ /**
76
+ * Appends values to headers that already exist in either collection.
77
+ *
78
+ * @param headers The values to append.
79
+ */
58
80
  concat (headers: Record<string, string>) {
59
81
  this.process(Object.entries(headers ?? {}), (key, value) => {
60
82
  if (this._incomingRequests[key] || this._outgoingHeaders[key]) {
@@ -63,12 +85,23 @@ export class HeaderPropagation implements HeaderPropagationCollection {
63
85
  })
64
86
  }
65
87
 
88
+ /**
89
+ * Sets outgoing header values unless the headers are protected.
90
+ *
91
+ * @param headers The headers to set.
92
+ */
66
93
  override (headers: Record<string, string>) {
67
94
  this.process(Object.entries(headers ?? {}), (key, value) => {
68
95
  this._outgoingHeaders[key] = value
69
96
  })
70
97
  }
71
98
 
99
+ /**
100
+ * Finds a known header name without regard to casing.
101
+ *
102
+ * @param key The header name to find.
103
+ * @returns The original-cased header name, or `undefined` when it is unknown.
104
+ */
72
105
  key (key: string) {
73
106
  return this._keys.find(k => k.toLowerCase() === key.toLowerCase())
74
107
  }
@@ -113,6 +146,11 @@ export class HeaderPropagation implements HeaderPropagationCollection {
113
146
  * A function type that defines how headers should be propagated.
114
147
  */
115
148
  export interface HeaderPropagationDefinition {
149
+ /**
150
+ * Configures outgoing headers for a request.
151
+ *
152
+ * @param headers The collection to configure.
153
+ */
116
154
  (headers: HeaderPropagationCollection): void
117
155
  }
118
156
 
package/src/httpClient.ts CHANGED
@@ -11,13 +11,21 @@ import { Errors } from './errorHelper'
11
11
  * Configuration for an HTTP request.
12
12
  */
13
13
  export interface HttpRequestConfig {
14
+ /** The HTTP method. */
14
15
  method: string
16
+ /** The absolute URL or path relative to the client's base URL. */
15
17
  url: string
18
+ /** Headers to merge with the client's default headers. */
16
19
  headers?: Record<string, string>
20
+ /** The request body. */
17
21
  data?: unknown
22
+ /** Query parameters to append to the URL. */
18
23
  params?: Record<string, string | undefined>
24
+ /** The format used to read the response body. */
19
25
  responseType?: 'json' | 'arraybuffer' | 'stream'
26
+ /** The request timeout in milliseconds. */
20
27
  timeout?: number
28
+ /** A signal used to cancel the request. */
21
29
  signal?: AbortSignal
22
30
  }
23
31
 
@@ -25,10 +33,15 @@ export interface HttpRequestConfig {
25
33
  * Represents an HTTP response.
26
34
  */
27
35
  export interface HttpResponse<T = unknown> {
36
+ /** The parsed response body. */
28
37
  data: T
38
+ /** The HTTP status code. */
29
39
  status: number
40
+ /** The HTTP status text. */
30
41
  statusText: string
42
+ /** The response headers. */
31
43
  headers: Headers
44
+ /** The configuration used for the request. */
32
45
  config: HttpRequestConfig
33
46
  }
34
47
 
@@ -36,7 +49,9 @@ export interface HttpResponse<T = unknown> {
36
49
  * Options for creating an HttpClient instance.
37
50
  */
38
51
  export interface HttpClientOptions {
52
+ /** The base URL used for relative request URLs. */
39
53
  baseURL?: string
54
+ /** Default headers included with every request. */
40
55
  headers?: Record<string, string>
41
56
  }
42
57
 
@@ -47,27 +62,48 @@ export class HttpClient {
47
62
  private _baseURL: string
48
63
  private _defaultHeaders: Record<string, string>
49
64
 
65
+ /**
66
+ * Creates an HTTP client.
67
+ *
68
+ * @param options The base URL and default headers.
69
+ */
50
70
  constructor (options: HttpClientOptions = {}) {
51
71
  this._baseURL = options.baseURL ?? ''
52
72
  this._defaultHeaders = this.normalizeHeaders(options.headers)
53
73
  }
54
74
 
75
+ /** Gets the base URL used for relative requests. */
55
76
  get baseURL (): string {
56
77
  return this._baseURL
57
78
  }
58
79
 
80
+ /** Gets the normalized default request headers. */
59
81
  get defaultHeaders (): Record<string, string> {
60
82
  return this._defaultHeaders
61
83
  }
62
84
 
85
+ /** Replaces the default request headers. */
63
86
  set defaultHeaders (headers: Record<string, string>) {
64
87
  this._defaultHeaders = this.normalizeHeaders(headers)
65
88
  }
66
89
 
90
+ /**
91
+ * Sets a default request header.
92
+ *
93
+ * @param name The header name.
94
+ * @param value The header value.
95
+ */
67
96
  setHeader (name: string, value: string): void {
68
97
  this._defaultHeaders[name.toLowerCase()] = value
69
98
  }
70
99
 
100
+ /**
101
+ * Sends an HTTP request.
102
+ *
103
+ * @param config The request configuration.
104
+ * @returns The response with its parsed body.
105
+ * @throws An {@link HttpError} when the response status is not successful.
106
+ */
71
107
  async request<T = unknown> (config: HttpRequestConfig): Promise<HttpResponse<T>> {
72
108
  const url = this.buildUrl(config.url, config.params)
73
109
  const headers = this.normalizeHeaders({ ...this._defaultHeaders, ...config.headers })
@@ -142,6 +178,13 @@ export class HttpClient {
142
178
  return httpResponse
143
179
  }
144
180
 
181
+ /**
182
+ * Sends an HTTP request, defaulting to GET.
183
+ *
184
+ * @param url The absolute URL or path relative to the client's base URL.
185
+ * @param options Additional request options. The `method` and `url` options override the defaults.
186
+ * @returns The response with its parsed body.
187
+ */
145
188
  async get<T = unknown> (url: string, options?: Partial<HttpRequestConfig>): Promise<HttpResponse<T>> {
146
189
  return this.request<T>({ method: 'get', url, ...options })
147
190
  }
@@ -192,10 +235,20 @@ export class HttpClient {
192
235
  * Error thrown when an HTTP request fails.
193
236
  */
194
237
  export class HttpError extends Error {
238
+ /** The unsuccessful HTTP response. */
195
239
  public readonly response: HttpResponse
240
+ /** The configuration used for the request. */
196
241
  public readonly config: HttpRequestConfig
242
+ /** The HTTP status code. */
197
243
  public readonly status: number
198
244
 
245
+ /**
246
+ * Creates an HTTP request error.
247
+ *
248
+ * @param message The error message.
249
+ * @param response The unsuccessful response.
250
+ * @param config The request configuration.
251
+ */
199
252
  constructor (message: string, response: HttpResponse, config: HttpRequestConfig) {
200
253
  super(message)
201
254
  this.name = 'HttpError'
@@ -204,6 +257,11 @@ export class HttpError extends Error {
204
257
  this.status = response.status
205
258
  }
206
259
 
260
+ /**
261
+ * Returns a serializable representation of the error.
262
+ *
263
+ * @returns The error, request, and response details.
264
+ */
207
265
  toJSON (): Record<string, unknown> {
208
266
  return {
209
267
  message: this.message,
@@ -17,11 +17,17 @@
17
17
  * longer needs to import types from `express` at compile time.
18
18
  */
19
19
  export interface WebResponse {
20
+ /** Sets the HTTP status code. */
20
21
  status (code: number): this
22
+ /** Sets a response header. */
21
23
  setHeader (name: string, value: string): this
24
+ /** Sends an optional response body. */
22
25
  send (body?: unknown): this
26
+ /** Ends the response. */
23
27
  end (): this
28
+ /** Indicates whether response headers have been sent. */
24
29
  headersSent: boolean
30
+ /** Indicates whether the response stream has ended. */
25
31
  writableEnded: boolean
26
32
  }
27
33
 
@@ -40,5 +46,6 @@ export type NextFunction = (err?: any) => void
40
46
  * (`req.params.conversationId`) without coupling to Express's `Request` type.
41
47
  */
42
48
  export interface WebRequestParamsCarrier {
49
+ /** Route parameters parsed from the request URL. */
43
50
  params?: Record<string, string | undefined>
44
51
  }