@microsoft/agents-hosting 1.7.0-beta.1.g8bcd4f11a7 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +16 -0
  2. package/dist/package.json +3 -3
  3. package/dist/src/agent-client/agentClient.js +4 -3
  4. package/dist/src/agent-client/agentClient.js.map +1 -1
  5. package/dist/src/agent-client/agentResponseHandler.d.ts +25 -6
  6. package/dist/src/agent-client/agentResponseHandler.js +16 -55
  7. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  8. package/dist/src/agent-client/createAgentResponseHandler.d.ts +48 -0
  9. package/dist/src/agent-client/createAgentResponseHandler.js +92 -0
  10. package/dist/src/agent-client/createAgentResponseHandler.js.map +1 -0
  11. package/dist/src/agent-client/index.d.ts +1 -0
  12. package/dist/src/agent-client/index.js +1 -0
  13. package/dist/src/agent-client/index.js.map +1 -1
  14. package/dist/src/app/adaptiveCards/activityValueParsers.js +2 -1
  15. package/dist/src/app/adaptiveCards/activityValueParsers.js.map +1 -1
  16. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +13 -12
  17. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
  18. package/dist/src/app/agentApplication.js +8 -8
  19. package/dist/src/app/agentApplication.js.map +1 -1
  20. package/dist/src/app/streaming/streamingResponse.js +2 -2
  21. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  22. package/dist/src/app/turnState.d.ts +0 -1
  23. package/dist/src/app/turnState.js +15 -14
  24. package/dist/src/app/turnState.js.map +1 -1
  25. package/dist/src/app/turnStateProperty.js +4 -2
  26. package/dist/src/app/turnStateProperty.js.map +1 -1
  27. package/dist/src/auth/authConfiguration.js +5 -3
  28. package/dist/src/auth/authConfiguration.js.map +1 -1
  29. package/dist/src/auth/jwt-middleware.d.ts +2 -2
  30. package/dist/src/auth/jwt-middleware.js +84 -6
  31. package/dist/src/auth/jwt-middleware.js.map +1 -1
  32. package/dist/src/auth/msal/msalTokenProvider.js +16 -16
  33. package/dist/src/auth/msal/msalTokenProvider.js.map +1 -1
  34. package/dist/src/baseAdapter.js +3 -1
  35. package/dist/src/baseAdapter.js.map +1 -1
  36. package/dist/src/cloudAdapter.d.ts +2 -2
  37. package/dist/src/cloudAdapter.js +10 -10
  38. package/dist/src/cloudAdapter.js.map +1 -1
  39. package/dist/src/createCloudAdapter.d.ts +40 -0
  40. package/dist/src/createCloudAdapter.js +44 -0
  41. package/dist/src/createCloudAdapter.js.map +1 -0
  42. package/dist/src/errorHelper.js +206 -79
  43. package/dist/src/errorHelper.js.map +1 -1
  44. package/dist/src/headerPropagation.js +3 -1
  45. package/dist/src/headerPropagation.js.map +1 -1
  46. package/dist/src/index.d.ts +2 -0
  47. package/dist/src/index.js +2 -0
  48. package/dist/src/index.js.map +1 -1
  49. package/dist/src/interfaces/index.d.ts +5 -0
  50. package/dist/src/interfaces/index.js +22 -0
  51. package/dist/src/interfaces/index.js.map +1 -0
  52. package/dist/src/interfaces/webResponse.d.ts +41 -0
  53. package/dist/src/interfaces/webResponse.js +7 -0
  54. package/dist/src/interfaces/webResponse.js.map +1 -0
  55. package/dist/src/middlewareSet.d.ts +0 -1
  56. package/dist/src/middlewareSet.js +4 -1
  57. package/dist/src/middlewareSet.js.map +1 -1
  58. package/dist/src/oauth/userTokenClient.js +2 -1
  59. package/dist/src/oauth/userTokenClient.js.map +1 -1
  60. package/dist/src/state/conversationState.js +4 -2
  61. package/dist/src/state/conversationState.js.map +1 -1
  62. package/dist/src/state/userState.js +4 -2
  63. package/dist/src/state/userState.js.map +1 -1
  64. package/dist/src/storage/memoryStorage.js +5 -3
  65. package/dist/src/storage/memoryStorage.js.map +1 -1
  66. package/dist/src/transcript/consoleTranscriptLogger.js +3 -1
  67. package/dist/src/transcript/consoleTranscriptLogger.js.map +1 -1
  68. package/dist/src/transcript/fileTranscriptLogger.js +5 -4
  69. package/dist/src/transcript/fileTranscriptLogger.js.map +1 -1
  70. package/dist/src/transcript/transcriptLoggerMiddleware.js +2 -1
  71. package/dist/src/transcript/transcriptLoggerMiddleware.js.map +1 -1
  72. package/dist/src/turnContext.js +2 -1
  73. package/dist/src/turnContext.js.map +1 -1
  74. package/package.json +3 -3
  75. package/src/agent-client/agentClient.ts +5 -4
  76. package/src/agent-client/agentResponseHandler.ts +43 -67
  77. package/src/agent-client/createAgentResponseHandler.ts +133 -0
  78. package/src/agent-client/index.ts +1 -0
  79. package/src/app/adaptiveCards/activityValueParsers.ts +3 -2
  80. package/src/app/adaptiveCards/adaptiveCardsActions.ts +5 -5
  81. package/src/app/agentApplication.ts +8 -16
  82. package/src/app/streaming/streamingResponse.ts +2 -2
  83. package/src/app/turnState.ts +15 -14
  84. package/src/app/turnStateProperty.ts +4 -2
  85. package/src/auth/authConfiguration.ts +5 -3
  86. package/src/auth/jwt-middleware.ts +87 -8
  87. package/src/auth/msal/msalTokenProvider.ts +16 -16
  88. package/src/baseAdapter.ts +3 -2
  89. package/src/cloudAdapter.ts +12 -12
  90. package/src/createCloudAdapter.ts +56 -0
  91. package/src/errorHelper.ts +244 -89
  92. package/src/headerPropagation.ts +4 -1
  93. package/src/index.ts +2 -0
  94. package/src/interfaces/index.ts +6 -0
  95. package/src/interfaces/webResponse.ts +44 -0
  96. package/src/middlewareSet.ts +3 -1
  97. package/src/oauth/userTokenClient.ts +3 -2
  98. package/src/state/conversationState.ts +4 -3
  99. package/src/state/userState.ts +4 -3
  100. package/src/storage/memoryStorage.ts +5 -3
  101. package/src/transcript/consoleTranscriptLogger.ts +3 -2
  102. package/src/transcript/fileTranscriptLogger.ts +6 -5
  103. package/src/transcript/transcriptLoggerMiddleware.ts +3 -2
  104. package/src/turnContext.ts +3 -2
@@ -4,8 +4,10 @@
4
4
  */
5
5
 
6
6
  import { AuthConfiguration, resolveAuthority } from './authConfiguration'
7
- import { Response, NextFunction } from 'express'
8
7
  import { Request } from './request'
8
+ import { WebResponse, NextFunction } from '../interfaces/webResponse'
9
+ import { Errors } from '../errorHelper'
10
+ import { ExceptionHelper } from '@microsoft/agents-activity'
9
11
  import jwksRsa, { JwksClient, SigningKey } from 'jwks-rsa'
10
12
  import jwt, { JwtHeader, JwtPayload, SignCallback, GetPublicKeyOrSecret } from 'jsonwebtoken'
11
13
  import { debug } from '@microsoft/agents-telemetry'
@@ -64,7 +66,7 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
64
66
  logger.debug('jwt.decode ', JSON.stringify(payload))
65
67
 
66
68
  if (!payload) {
67
- throw new Error('invalid token')
69
+ throw ExceptionHelper.generateException(Error, Errors.InvalidJwtToken)
68
70
  }
69
71
  const audience = payload.aud
70
72
 
@@ -73,7 +75,7 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
73
75
  : undefined
74
76
 
75
77
  if (!matchingEntry) {
76
- const err = new Error('Audience mismatch')
78
+ const err = ExceptionHelper.generateException(Error, Errors.JwtAudienceMismatch)
77
79
  logger.error(err.message, audience)
78
80
  throw err
79
81
  }
@@ -119,13 +121,78 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
119
121
  })
120
122
  }
121
123
 
124
+ /**
125
+ * Determines whether an `Authorization` header value is present (non-empty).
126
+ *
127
+ * The {@link Request} contract allows `string | string[] | undefined` because
128
+ * different web frameworks surface headers differently. This treats an empty
129
+ * string or empty array as "absent" so callers can distinguish a missing header
130
+ * (anonymous / 401) from a present-but-malformed one (always 401).
131
+ * @param authorization The raw `Authorization` header value.
132
+ * @returns `true` when a non-empty header value is present.
133
+ */
134
+ function hasAuthorizationHeader (authorization: string | string[] | undefined): boolean {
135
+ if (Array.isArray(authorization)) {
136
+ return authorization.some((value) => typeof value === 'string' && value.trim().length > 0)
137
+ }
138
+ return typeof authorization === 'string' && authorization.trim().length > 0
139
+ }
140
+
141
+ /**
142
+ * Parses a single `Authorization` header value and returns its bearer token.
143
+ *
144
+ * Validates the `Bearer <token>` scheme, returning `undefined` for anything
145
+ * malformed (wrong scheme, missing token, or extra whitespace-delimited parts).
146
+ * @param headerValue A single `Authorization` header value.
147
+ * @returns The bearer token, or `undefined` if the value is absent or malformed.
148
+ */
149
+ function parseBearerValue (headerValue: string | undefined): string | undefined {
150
+ if (typeof headerValue !== 'string') {
151
+ return undefined
152
+ }
153
+ const parts = headerValue.trim().split(/\s+/)
154
+ if (parts.length !== 2) {
155
+ return undefined
156
+ }
157
+ const [scheme, token] = parts
158
+ if (scheme.toLowerCase() !== 'bearer' || !token) {
159
+ return undefined
160
+ }
161
+ return token
162
+ }
163
+
164
+ /**
165
+ * Extracts the bearer token from a raw `Authorization` header value.
166
+ *
167
+ * On Node's core HTTP parser (which both Express and Fastify use) `Authorization`
168
+ * is always surfaced as a single string holding the *first* header line —
169
+ * duplicate `Authorization` headers are discarded, not comma-joined. The
170
+ * `string[]` case is handled only because the {@link Request} contract permits
171
+ * it for non-Node frameworks; when an array is supplied, every entry is inspected
172
+ * and the first valid `Bearer <token>` is returned (the bearer value is not
173
+ * assumed to be first). Returns `undefined` for anything malformed so the caller
174
+ * can emit a consistent 401 instead of throwing.
175
+ * @param authorization The raw `Authorization` header value.
176
+ * @returns The bearer token, or `undefined` if the header is absent or malformed.
177
+ */
178
+ function extractBearerToken (authorization: string | string[] | undefined): string | undefined {
179
+ const values = Array.isArray(authorization) ? authorization : [authorization]
180
+ for (const value of values) {
181
+ const token = parseBearerValue(value)
182
+ if (token) {
183
+ return token
184
+ }
185
+ }
186
+ return undefined
187
+ }
188
+
122
189
  /**
123
190
  * Middleware to authorize JWT tokens.
124
191
  * @param authConfig The authentication configuration.
125
192
  * @returns An Express middleware function.
126
193
  */
127
194
  export const authorizeJWT = (authConfig: AuthConfiguration) => {
128
- return async function (req: Request, res: Response, next: NextFunction) {
195
+ return async function (req: Request, res: WebResponse, next: NextFunction) {
129
196
  let failed = false
130
197
  logger.debug('authorizing jwt')
131
198
  if (req.method !== 'POST' && req.method !== 'GET') {
@@ -133,9 +200,8 @@ export const authorizeJWT = (authConfig: AuthConfiguration) => {
133
200
  logger.warn('Method not allowed', req.method)
134
201
  res.status(405).send({ 'jwt-auth-error': 'Method not allowed' })
135
202
  } else {
136
- const authHeader = req.headers.authorization as string
137
- if (authHeader) {
138
- const token: string = authHeader.split(' ')[1] // Extract the token from the Bearer string
203
+ const token = extractBearerToken(req.headers.authorization)
204
+ if (token) {
139
205
  try {
140
206
  const user = await verifyToken(token, authConfig)
141
207
  logger.debug('token verified for ', user)
@@ -143,8 +209,21 @@ export const authorizeJWT = (authConfig: AuthConfiguration) => {
143
209
  } catch (err: Error | any) {
144
210
  failed = true
145
211
  logger.error(err)
146
- res.status(401).send({ 'jwt-auth-error': err.message })
212
+ // Emit only the human-readable description rather than the
213
+ // ExceptionHelper-formatted "[code] - description - helplink" string,
214
+ // so the wire format does not leak internal error codes or help links.
215
+ // Fall back to a stable string so a thrown non-Error (no description/
216
+ // message) never serializes to an empty `{}` and drops the detail.
217
+ const wireMessage: string = err?.description ?? err?.message ?? 'unauthorized'
218
+ res.status(401).send({ 'jwt-auth-error': wireMessage })
147
219
  }
220
+ } else if (hasAuthorizationHeader(req.headers.authorization)) {
221
+ // Header is present but not a well-formed `Bearer <token>` (e.g. wrong
222
+ // scheme, missing token, or an array value). Respond with a consistent
223
+ // 401 rather than letting malformed input throw before authorization.
224
+ failed = true
225
+ logger.warn('malformed authorization header')
226
+ res.status(401).send({ 'jwt-auth-error': 'invalid authorization header' })
148
227
  } else {
149
228
  if (!authConfig.clientId && process.env.NODE_ENV !== 'production') {
150
229
  logger.info('using anonymous auth')
@@ -284,7 +284,7 @@ export class MsalTokenProvider implements AuthProvider {
284
284
  if (typeof authConfigOrScope === 'string') {
285
285
  // Called as getAccessToken(scope)
286
286
  if (!this.connectionSettings) {
287
- throw new Error('Connection settings must be provided to constructor when calling getAccessToken(scope)')
287
+ throw ExceptionHelper.generateException(Error, Errors.ConnectionSettingsRequiredForGetAccessTokenScope)
288
288
  }
289
289
  authConfig = this.connectionSettings
290
290
  actualScope = authConfigOrScope
@@ -378,7 +378,7 @@ export class MsalTokenProvider implements AuthProvider {
378
378
  if (Array.isArray(authConfigOrScopes)) {
379
379
  // Called as acquireTokenOnBehalfOf(scopes, oboAssertion)
380
380
  if (!this.connectionSettings) {
381
- throw new Error('Connection settings must be provided to constructor when calling acquireTokenOnBehalfOf(scopes, oboAssertion)')
381
+ throw ExceptionHelper.generateException(Error, Errors.ConnectionSettingsRequiredForAcquireTokenOnBehalfOf)
382
382
  }
383
383
  authConfig = this.connectionSettings
384
384
  actualScopes = authConfigOrScopes
@@ -399,7 +399,7 @@ export class MsalTokenProvider implements AuthProvider {
399
399
  scopes: actualScopes
400
400
  })
401
401
  if (!token?.accessToken) {
402
- throw new Error('Failed to acquire token on behalf of user')
402
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenOnBehalfOf)
403
403
  }
404
404
 
405
405
  return token.accessToken
@@ -412,7 +412,7 @@ export class MsalTokenProvider implements AuthProvider {
412
412
  record({ agenticInstanceId: agentAppInstanceId })
413
413
 
414
414
  if (!this.connectionSettings) {
415
- throw new Error('Connection settings must be provided when calling getAgenticInstanceToken')
415
+ throw ExceptionHelper.generateException(Error, Errors.ConnectionSettingsRequiredForGetAgenticInstanceToken)
416
416
  }
417
417
 
418
418
  const instanceTokenCacheKey = MsalTokenProvider.cacheKey(
@@ -444,7 +444,7 @@ export class MsalTokenProvider implements AuthProvider {
444
444
  })
445
445
 
446
446
  if (!token?.accessToken) {
447
- throw new Error(`Failed to acquire instance token for agent instance: ${agentAppInstanceId}`)
447
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireInstanceTokenForAgentInstance, undefined, { agentAppInstanceId })
448
448
  }
449
449
 
450
450
  this.cacheAgenticAuthenticationResult(instanceTokenCacheKey, token)
@@ -494,7 +494,7 @@ export class MsalTokenProvider implements AuthProvider {
494
494
  */
495
495
  private async acquireTokenForAgenticScenarios (tenantId: string, clientId: string, clientAssertion: string | undefined, scopes: string[], tokenBodyParameters: { [key: string]: any }): Promise<string | null> {
496
496
  if (!this.connectionSettings) {
497
- throw new Error('Connection settings must be provided when calling getAgenticInstanceToken')
497
+ throw ExceptionHelper.generateException(Error, Errors.ConnectionSettingsRequiredForGetAgenticInstanceToken)
498
498
  }
499
499
 
500
500
  logger.debug('acquireTokenForAgenticScenarios clientId=%s tenantId=%s scopes=%o grant_type=%s', clientId, tenantId, scopes, tokenBodyParameters.grant_type)
@@ -585,7 +585,7 @@ export class MsalTokenProvider implements AuthProvider {
585
585
  })
586
586
 
587
587
  if (!token) {
588
- throw new Error(`Failed to acquire instance token for user token: ${agentAppInstanceId}`)
588
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireInstanceTokenForUserToken, undefined, { agentAppInstanceId })
589
589
  }
590
590
 
591
591
  return token
@@ -594,7 +594,7 @@ export class MsalTokenProvider implements AuthProvider {
594
594
 
595
595
  public async getAgenticApplicationToken (tenantId: string, agentAppInstanceId: string): Promise<string> {
596
596
  if (!this.connectionSettings?.clientId) {
597
- throw new Error('Connection settings must be provided when calling getAgenticApplicationToken')
597
+ throw ExceptionHelper.generateException(Error, Errors.ConnectionSettingsRequiredForGetAgenticApplicationToken)
598
598
  }
599
599
  logger.debug('getAgenticApplicationToken clientId=%s tenantId=%s agentAppInstanceId=%s', this.connectionSettings.clientId, tenantId, agentAppInstanceId)
600
600
 
@@ -605,7 +605,7 @@ export class MsalTokenProvider implements AuthProvider {
605
605
  if (!this.connectionSettings.idpmResource) {
606
606
  resource = 'api://AzureAdTokenExchange/.default'
607
607
  } else if (!URL.canParse(this.connectionSettings.idpmResource)) {
608
- throw new Error('idpmResource must be a valid absolute URI')
608
+ throw ExceptionHelper.generateException(Error, Errors.IdpmResourceAbsoluteUriRequired)
609
609
  } else {
610
610
  resource = this.connectionSettings.idpmResource
611
611
  }
@@ -617,7 +617,7 @@ export class MsalTokenProvider implements AuthProvider {
617
617
  })
618
618
  const tokenResult = await msiApp.acquireToken({ resource })
619
619
  if (!tokenResult?.accessToken) {
620
- throw new Error(`Failed to acquire token via IdentityProxyManager for agent instance: ${agentAppInstanceId}`)
620
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenViaIdentityProxyManagerForAgentInstance, undefined, { agentAppInstanceId })
621
621
  }
622
622
  logger.debug('getAgenticApplicationToken via IdentityProxyManager clientId=%s resource=%s', this.connectionSettings.clientId, resource)
623
623
  return tokenResult.accessToken
@@ -655,7 +655,7 @@ export class MsalTokenProvider implements AuthProvider {
655
655
  })
656
656
 
657
657
  if (!token) {
658
- throw new Error(`Failed to acquire token for agent instance: ${agentAppInstanceId}`)
658
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireInstanceTokenForAgentInstance, undefined, { agentAppInstanceId })
659
659
  }
660
660
 
661
661
  return token
@@ -780,7 +780,7 @@ export class MsalTokenProvider implements AuthProvider {
780
780
  azureRegion: authConfig.azureRegion
781
781
  })
782
782
  if (!token?.accessToken) {
783
- throw new Error('Failed to acquire token using certificate')
783
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenUsingCertificate)
784
784
  }
785
785
  return token.accessToken
786
786
  }
@@ -799,7 +799,7 @@ export class MsalTokenProvider implements AuthProvider {
799
799
  azureRegion: authConfig.azureRegion
800
800
  })
801
801
  if (!token?.accessToken) {
802
- throw new Error('Failed to acquire token using client secret')
802
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenUsingClientSecret)
803
803
  }
804
804
  return token.accessToken
805
805
  }
@@ -824,7 +824,7 @@ export class MsalTokenProvider implements AuthProvider {
824
824
  const token = await cca.acquireTokenByClientCredential({ scopes, azureRegion: authConfig.azureRegion })
825
825
  logger.debug('got token using FIC client assertion')
826
826
  if (!token?.accessToken) {
827
- throw new Error('Failed to acquire token using FIC client assertion')
827
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenUsingFICClientAssertion)
828
828
  }
829
829
  return token.accessToken
830
830
  }
@@ -850,7 +850,7 @@ export class MsalTokenProvider implements AuthProvider {
850
850
  const token = await cca.acquireTokenByClientCredential({ scopes, azureRegion: authConfig.azureRegion })
851
851
  logger.debug('got token using WID client assertion')
852
852
  if (!token?.accessToken) {
853
- throw new Error('Failed to acquire token using WID client assertion')
853
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenUsingWIDClientAssertion)
854
854
  }
855
855
  return token.accessToken
856
856
  }
@@ -874,7 +874,7 @@ export class MsalTokenProvider implements AuthProvider {
874
874
  })
875
875
  logger.debug('got token for FIC')
876
876
  if (!response?.accessToken) {
877
- throw new Error('Failed to acquire external token for FIC client assertion')
877
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireExternalTokenForFICClientAssertion)
878
878
  }
879
879
  return response.accessToken
880
880
  }
@@ -6,11 +6,12 @@
6
6
  import { Middleware, MiddlewareHandler, MiddlewareSet } from './middlewareSet'
7
7
  import { TurnContext } from './turnContext'
8
8
  import { debug } from '@microsoft/agents-telemetry'
9
- import { Activity, ConversationReference } from '@microsoft/agents-activity'
9
+ import { Activity, ConversationReference, ExceptionHelper } from '@microsoft/agents-activity'
10
10
  import { ResourceResponse } from './connector-client/resourceResponse'
11
11
  import { AttachmentData } from './connector-client/attachmentData'
12
12
  import { AttachmentInfo } from './connector-client/attachmentInfo'
13
13
  import { JwtPayload } from 'jsonwebtoken'
14
+ import { Errors } from './errorHelper'
14
15
 
15
16
  const logger = debug('agents:base-adapter')
16
17
 
@@ -202,7 +203,7 @@ export abstract class BaseAdapter {
202
203
  if (err instanceof Error) {
203
204
  await this.onTurnError(pContext.proxy, err)
204
205
  } else {
205
- throw new Error('Unknown error type: ' + err.message)
206
+ throw ExceptionHelper.generateException(Error, Errors.UnknownErrorType, undefined, { errorMessage: err.message })
206
207
  }
207
208
  } else {
208
209
  throw err
@@ -6,8 +6,8 @@
6
6
  import { AgentHandler, INVOKE_RESPONSE_KEY } from './activityHandler'
7
7
  import { BaseAdapter } from './baseAdapter'
8
8
  import { TurnContext } from './turnContext'
9
- import { Response } from 'express'
10
9
  import { Request } from './auth/request'
10
+ import { WebResponse } from './interfaces/webResponse'
11
11
  import { ConnectorClient } from './connector-client/connectorClient'
12
12
  import { AuthConfiguration, getAuthConfigWithDefaults } from './auth/authConfiguration'
13
13
  import { AuthProvider } from './auth/authProvider'
@@ -289,7 +289,7 @@ export class CloudAdapter extends BaseAdapter {
289
289
  */
290
290
  protected resolveIfConnectorClientIsNeeded (activity: Activity): boolean {
291
291
  if (!activity) {
292
- throw new TypeError('`activity` parameter required')
292
+ throw ExceptionHelper.generateException(TypeError, Errors.ActivityParameterRequired)
293
293
  }
294
294
 
295
295
  switch (activity.deliveryMode) {
@@ -392,7 +392,7 @@ export class CloudAdapter extends BaseAdapter {
392
392
  headers
393
393
  )
394
394
  } else {
395
- throw new Error('Could not create connector client for agentic user')
395
+ throw ExceptionHelper.generateException(Error, Errors.CannotCreateConnectorClientForAgenticUser)
396
396
  }
397
397
  } else {
398
398
  // ABS tokens will not have an azp/appid so use the botframework scope.
@@ -580,7 +580,7 @@ export class CloudAdapter extends BaseAdapter {
580
580
  */
581
581
  public async process (
582
582
  request: Request,
583
- res: Response,
583
+ res: WebResponse,
584
584
  logic: (context: TurnContext) => Promise<void>,
585
585
  headerPropagation?: HeaderPropagationDefinition): Promise<void> {
586
586
  return trace(AdapterTraceDefinitions.process, async ({ record }) => {
@@ -609,7 +609,7 @@ export class CloudAdapter extends BaseAdapter {
609
609
  res.end()
610
610
  }
611
611
  if (!request.body) {
612
- throw new TypeError('`request.body` parameter required, make sure express.json() is used as middleware')
612
+ throw ExceptionHelper.generateException(TypeError, Errors.MissingRequestBody)
613
613
  }
614
614
  const incoming = normalizeIncomingActivity(request.body!)
615
615
  const activity = Activity.fromObject(incoming)
@@ -726,11 +726,11 @@ export class CloudAdapter extends BaseAdapter {
726
726
  async updateActivity (context: TurnContext, activity: Activity): Promise<ResourceResponse | void> {
727
727
  return trace(AdapterTraceDefinitions.updateActivity, async ({ record }) => {
728
728
  if (!context) {
729
- throw new TypeError('`context` parameter required')
729
+ throw ExceptionHelper.generateException(TypeError, Errors.ContextParameterRequired)
730
730
  }
731
731
 
732
732
  if (!activity) {
733
- throw new TypeError('`activity` parameter required')
733
+ throw ExceptionHelper.generateException(TypeError, Errors.ActivityParameterRequired)
734
734
  }
735
735
 
736
736
  record({ activity })
@@ -758,7 +758,7 @@ export class CloudAdapter extends BaseAdapter {
758
758
  async deleteActivity (context: TurnContext, reference: Partial<ConversationReference>): Promise<void> {
759
759
  return trace(AdapterTraceDefinitions.deleteActivity, async ({ record }) => {
760
760
  if (!context) {
761
- throw new TypeError('`context` parameter required')
761
+ throw ExceptionHelper.generateException(TypeError, Errors.ContextParameterRequired)
762
762
  }
763
763
 
764
764
  if (!reference || !reference.serviceUrl || (reference.conversation == null) || !reference.conversation.id || !reference.activityId) {
@@ -791,7 +791,7 @@ export class CloudAdapter extends BaseAdapter {
791
791
  }
792
792
 
793
793
  if (!botAppIdOrIdentity) {
794
- throw new TypeError('continueConversation: botAppIdOrIdentity is required')
794
+ throw ExceptionHelper.generateException(TypeError, Errors.ContinueConversationBotAppIdOrIdentityRequired)
795
795
  }
796
796
  const botAppId = typeof botAppIdOrIdentity === 'string' ? botAppIdOrIdentity : botAppIdOrIdentity.aud as string
797
797
 
@@ -911,10 +911,10 @@ export class CloudAdapter extends BaseAdapter {
911
911
  logic: (context: TurnContext) => Promise<void>
912
912
  ): Promise<void> {
913
913
  if (typeof serviceUrl !== 'string' || !serviceUrl) {
914
- throw new TypeError('`serviceUrl` must be a non-empty string')
914
+ throw ExceptionHelper.generateException(TypeError, Errors.ServiceUrlRequired)
915
915
  }
916
- if (!conversationParameters) throw new TypeError('`conversationParameters` must be defined')
917
- if (!logic) throw new TypeError('`logic` must be defined')
916
+ if (!conversationParameters) throw ExceptionHelper.generateException(TypeError, Errors.ConversationParametersRequired)
917
+ if (!logic) throw ExceptionHelper.generateException(TypeError, Errors.LogicParameterRequired)
918
918
 
919
919
  const identity = CloudAdapter.createIdentity(audience)
920
920
  const restClient = await this.createConnectorClient(serviceUrl, audience, identity)
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ActivityHandler } from './activityHandler'
7
+ import { AgentApplication } from './app/agentApplication'
8
+ import { AuthConfiguration } from './auth/authConfiguration'
9
+ import { CloudAdapter } from './cloudAdapter'
10
+ import { HeaderPropagationDefinition } from './headerPropagation'
11
+ import { TurnState } from './app/turnState'
12
+
13
+ /**
14
+ * Result of creating a CloudAdapter from an agent.
15
+ */
16
+ export interface CloudAdapterResult {
17
+ adapter: CloudAdapter
18
+ headerPropagation: HeaderPropagationDefinition | undefined
19
+ }
20
+
21
+ /**
22
+ * Creates a CloudAdapter for the given agent.
23
+ *
24
+ * If the agent is an AgentApplication with a pre-configured adapter, that adapter is reused.
25
+ * Otherwise, a new CloudAdapter is created.
26
+ *
27
+ * @param agent - The AgentApplication or ActivityHandler instance.
28
+ * @param authConfig - Optional auth configuration used when creating a new CloudAdapter.
29
+ * If the agent already has an adapter, that adapter is reused and this value is ignored.
30
+ * @returns An object containing the CloudAdapter and optional header propagation configuration.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * import { AgentApplication, TurnState, createCloudAdapter } from '@microsoft/agents-hosting';
35
+ *
36
+ * const app = new AgentApplication<TurnState>();
37
+ * const { adapter, headerPropagation } = createCloudAdapter(app, { clientId: process.env.CLIENT_ID });
38
+ *
39
+ * // Use the adapter directly with request/response objects compatible with CloudAdapter.process
40
+ * adapter.process(req, res, (context) => app.run(context), headerPropagation);
41
+ * ```
42
+ */
43
+ export const createCloudAdapter = (
44
+ agent: AgentApplication<TurnState<any, any>> | ActivityHandler,
45
+ authConfig?: AuthConfiguration
46
+ ): CloudAdapterResult => {
47
+ if (agent instanceof ActivityHandler) {
48
+ return { adapter: new CloudAdapter(authConfig), headerPropagation: undefined }
49
+ }
50
+
51
+ // AgentApplication: reuse its pre-configured adapter when present, otherwise create one.
52
+ // headerPropagation is an application-level option, so read it regardless of adapter source.
53
+ const headerPropagation = agent.options?.headerPropagation
54
+ const adapter = agent.adapter ?? new CloudAdapter(authConfig)
55
+ return { adapter, headerPropagation }
56
+ }