@microsoft/agents-hosting 1.5.0-beta.6.ga236d9a19c → 1.5.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 (157) hide show
  1. package/dist/package.json +10 -9
  2. package/dist/src/activityHandler.js +2 -2
  3. package/dist/src/activityHandler.js.map +1 -1
  4. package/dist/src/agent-client/agentClient.js +49 -40
  5. package/dist/src/agent-client/agentClient.js.map +1 -1
  6. package/dist/src/agent-client/agentResponseHandler.js +2 -2
  7. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  8. package/dist/src/app/agentApplication.d.ts +36 -10
  9. package/dist/src/app/agentApplication.js +169 -99
  10. package/dist/src/app/agentApplication.js.map +1 -1
  11. package/dist/src/app/agentApplicationBuilder.d.ts +15 -0
  12. package/dist/src/app/agentApplicationBuilder.js +22 -4
  13. package/dist/src/app/agentApplicationBuilder.js.map +1 -1
  14. package/dist/src/app/agentApplicationOptions.d.ts +38 -0
  15. package/dist/src/app/attachmentDownloader.js +2 -2
  16. package/dist/src/app/attachmentDownloader.js.map +1 -1
  17. package/dist/src/app/auth/authorization.js +12 -9
  18. package/dist/src/app/auth/authorization.js.map +1 -1
  19. package/dist/src/app/auth/authorizationManager.d.ts +18 -5
  20. package/dist/src/app/auth/authorizationManager.js +258 -45
  21. package/dist/src/app/auth/authorizationManager.js.map +1 -1
  22. package/dist/src/app/auth/handlerStorage.js +3 -1
  23. package/dist/src/app/auth/handlerStorage.js.map +1 -1
  24. package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +19 -16
  25. package/dist/src/app/auth/handlers/agenticAuthorization.js +46 -52
  26. package/dist/src/app/auth/handlers/agenticAuthorization.js.map +1 -1
  27. package/dist/src/app/auth/handlers/azureBotAuthorization.d.ts +51 -75
  28. package/dist/src/app/auth/handlers/azureBotAuthorization.js +217 -192
  29. package/dist/src/app/auth/handlers/azureBotAuthorization.js.map +1 -1
  30. package/dist/src/app/auth/types.d.ts +100 -1
  31. package/dist/src/app/auth/utils.d.ts +10 -0
  32. package/dist/src/app/auth/utils.js +21 -0
  33. package/dist/src/app/auth/utils.js.map +1 -0
  34. package/dist/src/app/index.d.ts +1 -0
  35. package/dist/src/app/index.js +1 -0
  36. package/dist/src/app/index.js.map +1 -1
  37. package/dist/src/app/proactive/conversation.d.ts +43 -0
  38. package/dist/src/app/proactive/conversation.js +67 -0
  39. package/dist/src/app/proactive/conversation.js.map +1 -0
  40. package/dist/src/app/proactive/conversationBuilder.d.ts +54 -0
  41. package/dist/src/app/proactive/conversationBuilder.js +110 -0
  42. package/dist/src/app/proactive/conversationBuilder.js.map +1 -0
  43. package/dist/src/app/proactive/conversationReferenceBuilder.d.ts +68 -0
  44. package/dist/src/app/proactive/conversationReferenceBuilder.js +125 -0
  45. package/dist/src/app/proactive/conversationReferenceBuilder.js.map +1 -0
  46. package/dist/src/app/proactive/createConversationOptions.d.ts +30 -0
  47. package/dist/src/app/proactive/createConversationOptions.js +10 -0
  48. package/dist/src/app/proactive/createConversationOptions.js.map +1 -0
  49. package/dist/src/app/proactive/createConversationOptionsBuilder.d.ts +69 -0
  50. package/dist/src/app/proactive/createConversationOptionsBuilder.js +141 -0
  51. package/dist/src/app/proactive/createConversationOptionsBuilder.js.map +1 -0
  52. package/dist/src/app/proactive/index.d.ts +7 -0
  53. package/dist/src/app/proactive/index.js +26 -0
  54. package/dist/src/app/proactive/index.js.map +1 -0
  55. package/dist/src/app/proactive/proactive.d.ts +248 -0
  56. package/dist/src/app/proactive/proactive.js +310 -0
  57. package/dist/src/app/proactive/proactive.js.map +1 -0
  58. package/dist/src/app/proactive/proactiveOptions.d.ts +19 -0
  59. package/dist/src/app/proactive/proactiveOptions.js +5 -0
  60. package/dist/src/app/proactive/proactiveOptions.js.map +1 -0
  61. package/dist/src/app/streaming/streamingResponse.js +2 -2
  62. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  63. package/dist/src/app/teamsAttachmentDownloader.js +2 -2
  64. package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
  65. package/dist/src/app/turnState.js +2 -2
  66. package/dist/src/app/turnState.js.map +1 -1
  67. package/dist/src/auth/authConfiguration.d.ts +61 -0
  68. package/dist/src/auth/authConfiguration.js +52 -3
  69. package/dist/src/auth/authConfiguration.js.map +1 -1
  70. package/dist/src/auth/jwt-middleware.js +2 -2
  71. package/dist/src/auth/jwt-middleware.js.map +1 -1
  72. package/dist/src/auth/msalConnectionManager.js +20 -0
  73. package/dist/src/auth/msalConnectionManager.js.map +1 -1
  74. package/dist/src/auth/msalTokenCredential.js +3 -0
  75. package/dist/src/auth/msalTokenCredential.js.map +1 -1
  76. package/dist/src/auth/msalTokenProvider.js +136 -110
  77. package/dist/src/auth/msalTokenProvider.js.map +1 -1
  78. package/dist/src/baseAdapter.js +2 -2
  79. package/dist/src/baseAdapter.js.map +1 -1
  80. package/dist/src/cloudAdapter.js +201 -154
  81. package/dist/src/cloudAdapter.js.map +1 -1
  82. package/dist/src/connector-client/connectorClient.js +176 -127
  83. package/dist/src/connector-client/connectorClient.js.map +1 -1
  84. package/dist/src/errorHelper.js +108 -0
  85. package/dist/src/errorHelper.js.map +1 -1
  86. package/dist/src/middlewareSet.js +2 -2
  87. package/dist/src/middlewareSet.js.map +1 -1
  88. package/dist/src/oauth/userTokenClient.js +78 -48
  89. package/dist/src/oauth/userTokenClient.js.map +1 -1
  90. package/dist/src/observability/index.d.ts +2 -0
  91. package/dist/src/observability/index.js +21 -0
  92. package/dist/src/observability/index.js.map +1 -0
  93. package/dist/src/observability/metrics.d.ts +21 -0
  94. package/dist/src/observability/metrics.js +87 -0
  95. package/dist/src/observability/metrics.js.map +1 -0
  96. package/dist/src/observability/traces.d.ts +234 -0
  97. package/dist/src/observability/traces.js +962 -0
  98. package/dist/src/observability/traces.js.map +1 -0
  99. package/dist/src/state/agentState.js +2 -2
  100. package/dist/src/state/agentState.js.map +1 -1
  101. package/dist/src/storage/fileStorage.js +38 -28
  102. package/dist/src/storage/fileStorage.js.map +1 -1
  103. package/dist/src/storage/memoryStorage.js +41 -30
  104. package/dist/src/storage/memoryStorage.js.map +1 -1
  105. package/dist/src/transcript/fileTranscriptLogger.js +2 -2
  106. package/dist/src/transcript/fileTranscriptLogger.js.map +1 -1
  107. package/dist/src/transcript/transcriptLoggerMiddleware.js +2 -2
  108. package/dist/src/transcript/transcriptLoggerMiddleware.js.map +1 -1
  109. package/dist/src/turnContext.js +48 -42
  110. package/dist/src/turnContext.js.map +1 -1
  111. package/package.json +10 -9
  112. package/src/activityHandler.ts +1 -1
  113. package/src/agent-client/agentClient.ts +53 -42
  114. package/src/agent-client/agentResponseHandler.ts +1 -1
  115. package/src/app/agentApplication.ts +212 -86
  116. package/src/app/agentApplicationBuilder.ts +26 -4
  117. package/src/app/agentApplicationOptions.ts +43 -0
  118. package/src/app/attachmentDownloader.ts +1 -1
  119. package/src/app/auth/authorization.ts +11 -8
  120. package/src/app/auth/authorizationManager.ts +297 -45
  121. package/src/app/auth/handlerStorage.ts +3 -1
  122. package/src/app/auth/handlers/agenticAuthorization.ts +68 -72
  123. package/src/app/auth/handlers/azureBotAuthorization.ts +260 -264
  124. package/src/app/auth/types.ts +102 -1
  125. package/src/app/auth/utils.ts +22 -0
  126. package/src/app/index.ts +1 -0
  127. package/src/app/proactive/conversation.ts +87 -0
  128. package/src/app/proactive/conversationBuilder.ts +139 -0
  129. package/src/app/proactive/conversationReferenceBuilder.ts +161 -0
  130. package/src/app/proactive/createConversationOptions.ts +35 -0
  131. package/src/app/proactive/createConversationOptionsBuilder.ts +181 -0
  132. package/src/app/proactive/index.ts +10 -0
  133. package/src/app/proactive/proactive.ts +524 -0
  134. package/src/app/proactive/proactiveOptions.ts +24 -0
  135. package/src/app/streaming/streamingResponse.ts +1 -1
  136. package/src/app/teamsAttachmentDownloader.ts +1 -1
  137. package/src/app/turnState.ts +1 -1
  138. package/src/auth/authConfiguration.ts +58 -1
  139. package/src/auth/jwt-middleware.ts +1 -1
  140. package/src/auth/msalConnectionManager.ts +22 -0
  141. package/src/auth/msalTokenCredential.ts +4 -0
  142. package/src/auth/msalTokenProvider.ts +138 -107
  143. package/src/baseAdapter.ts +1 -1
  144. package/src/cloudAdapter.ts +239 -184
  145. package/src/connector-client/connectorClient.ts +169 -126
  146. package/src/errorHelper.ts +124 -0
  147. package/src/middlewareSet.ts +1 -1
  148. package/src/oauth/userTokenClient.ts +70 -46
  149. package/src/observability/index.ts +5 -0
  150. package/src/observability/metrics.ts +103 -0
  151. package/src/observability/traces.ts +988 -0
  152. package/src/state/agentState.ts +1 -1
  153. package/src/storage/fileStorage.ts +36 -26
  154. package/src/storage/memoryStorage.ts +40 -29
  155. package/src/transcript/fileTranscriptLogger.ts +1 -1
  156. package/src/transcript/transcriptLoggerMiddleware.ts +1 -1
  157. package/src/turnContext.ts +47 -41
@@ -12,6 +12,36 @@ import { TurnState } from './turnState'
12
12
  import { HeaderPropagationDefinition } from '../headerPropagation'
13
13
  import { AuthorizationOptions } from './auth/types'
14
14
  import { Connections } from '../auth/connections'
15
+ import { ProactiveOptions } from './proactive'
16
+
17
+ /**
18
+ * Typing timer settings for a specific channel or the global default.
19
+ */
20
+ export interface TypingTimingOptions {
21
+ /**
22
+ * Delay in milliseconds before the first typing indicator is sent.
23
+ *
24
+ * @default 0
25
+ */
26
+ initialDelayMs?: number
27
+
28
+ /**
29
+ * Delay in milliseconds between subsequent typing indicators.
30
+ *
31
+ * @default 4000
32
+ */
33
+ intervalMs?: number
34
+ }
35
+
36
+ /**
37
+ * Configuration options for automatic typing indicators.
38
+ */
39
+ export interface TypingOptions extends TypingTimingOptions {
40
+ /**
41
+ * Optional channel-specific timing overrides keyed by channel ID.
42
+ */
43
+ channelStrategies?: Record<string, TypingTimingOptions>
44
+ }
15
45
 
16
46
  /**
17
47
  * Configuration options for creating and initializing an Agent Application.
@@ -59,6 +89,13 @@ export interface AgentApplicationOptions<TState extends TurnState> {
59
89
  */
60
90
  startTypingTimer: boolean;
61
91
 
92
+ /**
93
+ * Optional timing settings for automatic typing indicators.
94
+ *
95
+ * @default undefined
96
+ */
97
+ typing?: TypingOptions;
98
+
62
99
  /**
63
100
  * Whether to enable support for long-running message processing operations.
64
101
  * When enabled, the agent can handle operations that take longer than the typical
@@ -147,4 +184,10 @@ export interface AgentApplicationOptions<TState extends TurnState> {
147
184
  * identity providers.
148
185
  */
149
186
  connections?: Connections
187
+
188
+ /**
189
+ * Optional. Configuration for the proactive messaging subsystem.
190
+ * When provided, `app.proactive` will be available.
191
+ */
192
+ proactive?: ProactiveOptions
150
193
  }
@@ -9,7 +9,7 @@ import { TurnState } from './turnState'
9
9
  import { TurnContext } from '../turnContext'
10
10
  import { Attachment } from '@microsoft/agents-activity'
11
11
  import { AuthProvider } from '../auth/authProvider'
12
- import { debug } from '@microsoft/agents-activity/logger'
12
+ import { debug } from '@microsoft/agents-telemetry'
13
13
  import { loadAuthConfigFromEnv, MsalTokenProvider } from '../auth'
14
14
 
15
15
  const logger = debug('agents:attachmentDownloader')
@@ -3,12 +3,14 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { debug } from '@microsoft/agents-activity/logger'
6
+ import { debug } from '@microsoft/agents-telemetry'
7
+ import { ExceptionHelper } from '@microsoft/agents-activity'
7
8
  import { TokenResponse } from '../../oauth'
8
9
  import { TurnContext } from '../../turnContext'
9
10
  import { TurnState } from '../turnState'
10
11
  import { AuthorizationManager } from './authorizationManager'
11
12
  import { AuthorizationHandlerTokenOptions } from './types'
13
+ import { Errors } from '../../errorHelper'
12
14
 
13
15
  const logger = debug('agents:authorization')
14
16
 
@@ -148,7 +150,7 @@ export class UserAuthorization implements Authorization {
148
150
  return { token }
149
151
  }
150
152
 
151
- throw new Error('Invalid parameters for exchangeToken method.')
153
+ throw ExceptionHelper.generateException(Error, Errors.InvalidExchangeTokenParameters)
152
154
  }
153
155
 
154
156
  /**
@@ -180,7 +182,7 @@ export class UserAuthorization implements Authorization {
180
182
  if (authHandlerId) {
181
183
  await this.getHandler(authHandlerId).signout(context)
182
184
  } else {
183
- for (const handler of Object.values(this.manager.handlers)) {
185
+ for (const handler of this.manager.handlers) {
184
186
  await handler.signout(context)
185
187
  }
186
188
  }
@@ -207,7 +209,7 @@ export class UserAuthorization implements Authorization {
207
209
  * @public
208
210
  */
209
211
  public onSignInSuccess (handler: (context: TurnContext, state: TurnState, authHandlerId?: string) => Promise<void>) {
210
- for (const authHandler of Object.values(this.manager.handlers)) {
212
+ for (const authHandler of this.manager.handlers) {
211
213
  authHandler.onSuccess((context) => handler(context, new TurnState(), authHandler.id))
212
214
  }
213
215
  }
@@ -239,7 +241,7 @@ export class UserAuthorization implements Authorization {
239
241
  * @public
240
242
  */
241
243
  public onSignInFailure (handler: (context: TurnContext, state: TurnState, authHandlerId?: string, errorMessage?: string) => Promise<void>) {
242
- for (const authHandler of Object.values(this.manager.handlers)) {
244
+ for (const authHandler of this.manager.handlers) {
243
245
  authHandler.onFailure((context, reason) => handler(context, new TurnState(), authHandler.id, reason))
244
246
  }
245
247
  }
@@ -253,9 +255,10 @@ export class UserAuthorization implements Authorization {
253
255
  * @private
254
256
  */
255
257
  private getHandler (id: string) {
256
- if (!Object.prototype.hasOwnProperty.call(this.manager.handlers, id)) {
257
- throw new Error(`Cannot find auth handler with ID '${id}'. Ensure it is configured in the agent application options.`)
258
+ const handler = this.manager.handlers.find(e => e.id.toLowerCase() === id.toLowerCase())
259
+ if (!handler) {
260
+ throw ExceptionHelper.generateException(Error, Errors.AuthHandlerNotFound, undefined, { handlerId: id })
258
261
  }
259
- return this.manager.handlers[id]
262
+ return handler
260
263
  }
261
264
  }
@@ -3,16 +3,24 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { Activity, debug } from '@microsoft/agents-activity'
6
+ import { Activity, ExceptionHelper } from '@microsoft/agents-activity'
7
+ import { debug } from '@microsoft/agents-telemetry'
7
8
  import { AgentApplication } from '../agentApplication'
8
9
  import { AgenticAuthorization, AzureBotAuthorization } from './handlers'
9
10
  import { TurnContext } from '../../turnContext'
10
11
  import { HandlerStorage } from './handlerStorage'
12
+ import { Errors } from '../../errorHelper'
11
13
  import { ActiveAuthorizationHandler, AuthorizationHandlerStatus, AuthorizationHandler, AuthorizationHandlerSettings, AuthorizationOptions } from './types'
12
14
  import { Connections } from '../../auth/connections'
15
+ import { sendInvokeResponse } from './utils'
16
+ import { envParser, envParserUtils } from '../../auth'
13
17
 
14
18
  const logger = debug('agents:authorization:manager')
15
19
 
20
+ const AGENTIC = 'AgenticUserAuthorization'
21
+ const AGENTIC_LEGACY = 'agentic'
22
+ const AZURE_BOT = 'AzureBotUserAuthorization'
23
+
16
24
  /**
17
25
  * Active handler information used by the AuthorizationManager.
18
26
  */
@@ -52,53 +60,113 @@ export class AuthorizationManager {
52
60
  private _handlers: Record<string, AuthorizationHandler> = {}
53
61
 
54
62
  /**
55
- * Creates an instance of the AuthorizationManager.
56
- * @param app The agent application instance.
63
+ * Environment variable configuration for the latest format.
57
64
  */
58
- constructor (private app: AgentApplication<any>, connections: Connections) {
59
- if (!app.options.storage) {
60
- throw new Error('Storage is required for Authorization. Ensure that a storage provider is configured in the AgentApplication options.')
61
- }
62
-
63
- if (app.options.authorization === undefined || Object.keys(app.options.authorization).length === 0) {
64
- throw new Error('The AgentApplication.authorization does not have any auth handlers')
65
- }
66
-
67
- const settings: AuthorizationHandlerSettings = { storage: app.options.storage, connections }
68
- for (const [id, handler] of Object.entries(app.options.authorization)) {
69
- const options = this.loadOptions(id, handler)
70
- if (options.type === 'agentic') {
71
- this._handlers[id] = new AgenticAuthorization(id, options, settings)
72
- } else {
73
- this._handlers[id] = new AzureBotAuthorization(id, options, settings)
65
+ private _envLatest = {
66
+ key: {
67
+ prefix: 'AgentApplication__UserAuthorization__Handlers__',
68
+ separator: '__Settings__',
69
+ create (id: string, prop: string) {
70
+ return `${this.prefix}${id}${this.separator}${prop}`
71
+ },
72
+ extract (envKey: string) {
73
+ // Substring: AgentApplication__UserAuthorization__Handlers__<id>__Settings__<prop>
74
+ // position —————————————————————————————————————————> start^ ^end ^prop
75
+ const start = this.prefix.length
76
+ const end = envKey.toUpperCase().indexOf(this.separator.toUpperCase())
77
+ if (end === -1) {
78
+ return { id: undefined, prop: undefined }
79
+ }
80
+
81
+ const id = envKey.substring(start, end)
82
+ const prop = envKey.substring(end + this.separator.length)
83
+ return { id, prop }
74
84
  }
75
- }
85
+ },
86
+ parser: envParser({
87
+ // Common
88
+ type: envParserUtils.bypass,
89
+
90
+ // Azure Bot
91
+ azureBotOAuthConnectionName: envParserUtils.bypass,
92
+ title: envParserUtils.bypass,
93
+ text: envParserUtils.bypass,
94
+ invalidSignInRetryMessage: envParserUtils.bypass,
95
+ invalidSignInRetryMessageFormat: envParserUtils.bypass,
96
+ invalidSignInRetryMaxExceededMessage: envParserUtils.bypass,
97
+ oboConnectionName: envParserUtils.bypass,
98
+ enableSso (value) {
99
+ return { value: value !== 'false' }
100
+ },
101
+ invalidSignInRetryMax (value) {
102
+ return { value: parseInt(value) }
103
+ },
104
+ oboScopes (value) {
105
+ return this.scopes(value)
106
+ },
107
+
108
+ // Agentic
109
+ altBlueprintConnectionName: envParserUtils.bypass,
110
+ scopes (value) {
111
+ if (value.includes(',')) {
112
+ return { value: value.split(',').map(s => s.trim()).filter(Boolean) }
113
+ }
114
+ return { value: value.split(/\s+/).filter(Boolean) }
115
+ }
116
+ }),
76
117
  }
77
118
 
78
119
  /**
79
- * Loads and validates the authorization handler options.
120
+ * Environment variable configuration for the legacy format.
80
121
  */
81
- private loadOptions (id: string, options: AuthorizationOptions[string]) {
82
- const result: AuthorizationOptions[string] = {
83
- ...options,
84
- type: (options.type ?? process.env[`${id}_type`])?.toLowerCase() as typeof options.type,
85
- }
122
+ private _envLegacy = {
123
+ key: {
124
+ separator: '_',
125
+ },
126
+ parser: envParser({
127
+ // Common
128
+ type: envParserUtils.redirect(this._envLatest.parser, 'type'),
129
+
130
+ // Azure Bot
131
+ connectionName: envParserUtils.redirect(this._envLatest.parser, 'azureBotOAuthConnectionName'),
132
+ connectionTitle: envParserUtils.redirect(this._envLatest.parser, 'title'),
133
+ connectionText: envParserUtils.redirect(this._envLatest.parser, 'text'),
134
+ maxAttempts: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMax'),
135
+ messages_invalidCode: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMessage'),
136
+ messages_invalidCodeFormat: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMessageFormat'),
137
+ messages_maxAttemptsExceeded: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMaxExceededMessage'),
138
+ obo_connection: envParserUtils.redirect(this._envLatest.parser, 'oboConnectionName'),
139
+ obo_scopes: envParserUtils.redirect(this._envLatest.parser, 'oboScopes'),
140
+ enableSso: envParserUtils.redirect(this._envLatest.parser, 'enableSso'),
141
+
142
+ // Agentic
143
+ scopes: envParserUtils.redirect(this._envLatest.parser, 'scopes'),
144
+ altBlueprintConnectionName: envParserUtils.redirect(this._envLatest.parser, 'altBlueprintConnectionName')
145
+ })
146
+ }
86
147
 
87
- // Validate supported types, agentic, and default (Azure Bot - undefined)
88
- const supportedTypes = ['agentic', undefined]
89
- if (!supportedTypes.includes(result.type)) {
90
- throw new Error(`Unsupported authorization handler type: '${result.type}' for auth handler: '${id}'. Supported types are: '${supportedTypes.filter(Boolean).join('\', \'')}'.`)
148
+ /**
149
+ * Creates an instance of the AuthorizationManager.
150
+ * @param app The agent application instance.
151
+ */
152
+ constructor (private app: AgentApplication<any>, private connections: Connections) {
153
+ this.createHandlers()
154
+
155
+ if (this.handlers.length === 0 && app.options.authorization !== undefined) {
156
+ throw ExceptionHelper.generateException(Error, Errors.NoAuthHandlersConfigured)
91
157
  }
92
158
 
93
- return result
159
+ for (const [id, handler] of Object.entries(this._handlers)) {
160
+ logger.debug('auth handler "%s" type=%s scopes=%o', id, handler.type, handler.scopes)
161
+ }
94
162
  }
95
163
 
96
164
  /**
97
165
  * Gets the registered authorization handlers.
98
166
  * @returns A record of authorization handlers by their IDs.
99
167
  */
100
- public get handlers (): Record<string, AuthorizationHandler> {
101
- return this._handlers
168
+ public get handlers (): AuthorizationHandler[] {
169
+ return Object.values(this._handlers)
102
170
  }
103
171
 
104
172
  /**
@@ -108,24 +176,45 @@ export class AuthorizationManager {
108
176
  * @returns The result of the authorization process.
109
177
  */
110
178
  public async process (context: TurnContext, getHandlerIds: GetHandlerIds): Promise<AuthorizationManagerProcessResult> {
179
+ const activity = context.activity
180
+
181
+ if (this.handlers.length === 0) {
182
+ return { authorized: true, context }
183
+ }
184
+
111
185
  const storage = new HandlerStorage(this.app.options.storage!, context)
112
186
 
113
187
  let active = await this.active(storage, getHandlerIds)
114
188
 
115
- if (active !== undefined && active?.data.activity.conversation?.id !== context.activity.conversation?.id) {
189
+ if (!active && activity.name?.startsWith('signin/')) {
190
+ const reason = `Received '${activity.name}' but no active sign-in flow exists for user '${activity.from?.id}'.`
191
+ logger.warn(reason, activity)
192
+ await sendInvokeResponse(context, {
193
+ status: 400,
194
+ body: { failureDetail: reason }
195
+ })
196
+ return { authorized: false, context }
197
+ }
198
+
199
+ if (active !== undefined && active?.data.activity.conversation?.id !== activity.conversation?.id) {
116
200
  logger.warn('Discarding the active session due to the conversation has changed during an active sign-in process', active?.data.activity)
117
201
  await storage.delete()
118
202
  return { authorized: true, context }
119
203
  }
120
204
 
121
- const handlers = active?.handlers ?? this.mapHandlers(await getHandlerIds(context.activity) ?? []) ?? []
205
+ const handlers = active?.handlers ?? this.mapHandlers(await getHandlerIds(activity) ?? []) ?? []
122
206
 
123
207
  // Create a shallow copy to modify the activity, since the signin process depends on it and we want to ensure the next handler depends on the initial activity, not the modified one.
124
208
  const sharedContext = new TurnContext(context)
125
209
 
126
210
  for (const handler of handlers) {
211
+ if (handler.scopes?.length) {
212
+ logger.debug('invoking auth handler "%s" scopes=[%s]', handler.id, handler.scopes.join(','))
213
+ } else {
214
+ logger.debug('invoking auth handler "%s"', handler.id)
215
+ }
127
216
  const status = await this.signin(storage, handler, sharedContext, active?.data)
128
- logger.debug(this.prefix(handler.id, `Sign-in status: ${status}`))
217
+ logger.debug('auth handler "%s" sign-in status=%s', handler.id, status)
129
218
 
130
219
  if (status === AuthorizationHandlerStatus.IGNORED) {
131
220
  await storage.delete()
@@ -147,7 +236,7 @@ export class AuthorizationManager {
147
236
  }
148
237
 
149
238
  if (status !== AuthorizationHandlerStatus.APPROVED) {
150
- throw new Error(this.prefix(handler.id, `Unexpected registration status: ${status}`))
239
+ throw ExceptionHelper.generateException(Error, Errors.UnexpectedRegistrationStatus, undefined, { status })
151
240
  }
152
241
 
153
242
  await storage.delete()
@@ -194,7 +283,7 @@ export class AuthorizationManager {
194
283
  return await handler.signin(context, active)
195
284
  } catch (cause) {
196
285
  await storage.delete()
197
- throw new Error(this.prefix(handler.id, 'Failed to sign in'), { cause })
286
+ throw ExceptionHelper.generateException(Error, Errors.FailedToSignIn, cause as Error)
198
287
  }
199
288
  }
200
289
 
@@ -202,16 +291,19 @@ export class AuthorizationManager {
202
291
  * Maps an array of handler IDs to their corresponding handler instances.
203
292
  */
204
293
  private mapHandlers (ids: string[]): AuthorizationHandler[] {
205
- let unknownHandlers = ''
294
+ const unknownHandlers: string[] = []
206
295
  const handlers = ids.map(id => {
207
- if (!this._handlers[id]) {
208
- unknownHandlers += ` ${id}`
296
+ const handler = this.handlers.find(e => e.id.toLowerCase() === id.toLowerCase())
297
+ if (!handler) {
298
+ unknownHandlers.push(id)
209
299
  }
210
- return this._handlers[id]
211
- })
212
- if (unknownHandlers) {
213
- throw new Error(`Cannot find auth handlers with ID(s): ${unknownHandlers}`)
300
+ return handler
301
+ }).filter((handler) => handler !== undefined)
302
+
303
+ if (unknownHandlers.length > 0) {
304
+ throw ExceptionHelper.generateException(Error, Errors.AuthHandlersNotFound, undefined, { handlerIds: unknownHandlers.join(', ') })
214
305
  }
306
+
215
307
  return handlers
216
308
  }
217
309
 
@@ -221,4 +313,164 @@ export class AuthorizationManager {
221
313
  private prefix (id: string, message: string) {
222
314
  return `[handler:${id}] ${message}`
223
315
  }
316
+
317
+ /**
318
+ * Creates authorization handlers based on the application configuration and environment variables.
319
+ */
320
+ private createHandlers () {
321
+ let legacyMessage = ''
322
+ const settings: AuthorizationHandlerSettings = { storage: this.app.options.storage!, connections: this.connections }
323
+ let runtimeEntries = Object.entries(this.app.options.authorization ?? {})
324
+ const result = { latest: {}, legacy: {} } as {
325
+ latest: Record<string, Record<string, any> | undefined>;
326
+ legacy: Record<string, Record<string, any> | undefined>;
327
+ }
328
+
329
+ for (const [envKey, envValue] of Object.entries(process.env)) {
330
+ if (!envValue?.trim()) {
331
+ continue
332
+ }
333
+
334
+ const upperEnvKey = envKey.toUpperCase()
335
+
336
+ // Legacy: extract handler ID, handler options key and its value, and assign it to the correct runtime handler ID.
337
+ if (!upperEnvKey.startsWith(this._envLatest.key.prefix.toUpperCase())) {
338
+ const [id] = runtimeEntries.find(([id]) => upperEnvKey.startsWith(`${id.toUpperCase()}${this._envLegacy.key.separator}`)) ?? []
339
+ if (!id) {
340
+ continue
341
+ }
342
+
343
+ const prop = envKey.substring(id.length + this._envLegacy.key.separator.length)
344
+ if (!prop) {
345
+ continue
346
+ }
347
+
348
+ const { key, value } = this._envLegacy.parser.parse(prop as any, envValue)
349
+ if (!key) {
350
+ continue
351
+ }
352
+
353
+ legacyMessage += ` ${envKey}= # Use ${this._envLatest.key.create(id, key)} instead.\n`
354
+
355
+ result.legacy[id] ??= {}
356
+ result.legacy[id][key] = value
357
+ continue
358
+ }
359
+
360
+ // Latest: extract handler ID, handler options key and its value, and assign it to the correct latest handler ID.
361
+ const { id, prop } = this._envLatest.key.extract(envKey)
362
+ if (!id || !prop) {
363
+ continue
364
+ }
365
+
366
+ const { key, value } = this._envLatest.parser.parse(prop as any, envValue)
367
+ if (!key) {
368
+ continue
369
+ }
370
+
371
+ // Find existing handler ID case-insensitively to avoid duplicates, but keep the original casing for later processing.
372
+ // This allows users to specify environment variables in a case-insensitive way, while ensuring that the handler IDs are treated in a case-sensitive way internally, to avoid issues when referencing them later.
373
+ // For example, if the user specifies AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__GRAPH__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME and agentapplication__userauthorization__handlers__graph__settings__azurebotoauthconnectionname, we want to treat them as the same handler ID "graph", and not create two separate handlers "GRAPH" and "graph".
374
+ // Note: the first environment variable that is processed will determine the casing of the handler ID, and any subsequent environment variable that matches the same handler ID case-insensitively will be treated as referring to the same handler, regardless of its casing.
375
+ const existingId = id in result.latest ? id : Object.keys(result.latest).find(e => e.toLowerCase() === id.toLowerCase())
376
+ const realId = existingId ?? id
377
+ result.latest[realId] ??= {}
378
+ result.latest[realId][key] = value
379
+ }
380
+
381
+ if (legacyMessage.length > 0) {
382
+ logger.warn('Deprecated environment variables detected, update to the latest format: (case-insensitive)', `[\n${legacyMessage}]`)
383
+ }
384
+
385
+ // Fix types
386
+ const fixTypes = (entries: [string, any][]) => entries
387
+ .map(([id, options]) => [id, { ...options, type: this.fixType(id, options?.type) }] as [string, AuthorizationOptions[string]])
388
+
389
+ runtimeEntries = fixTypes(runtimeEntries)
390
+ const latestEntries = fixTypes(Object.entries(result.latest))
391
+ const legacyEntries = fixTypes(Object.entries(result.legacy))
392
+
393
+ const registeredHandlers = new Set()
394
+ for (const [id] of [...runtimeEntries, ...latestEntries]) {
395
+ if (registeredHandlers.has(id.toLowerCase())) {
396
+ continue
397
+ }
398
+
399
+ // Find entries case-insensitively for later processing
400
+ const [, runtime] = runtimeEntries.find(([key]) => key.toLowerCase() === id.toLowerCase()) ?? []
401
+ const [, latest] = latestEntries.find(([key]) => key.toLowerCase() === id.toLowerCase()) ?? []
402
+ const [, legacy] = legacyEntries.find(([key]) => key.toLowerCase() === id.toLowerCase()) ?? []
403
+
404
+ if (runtime !== undefined && latest !== undefined) {
405
+ logger.warn(this.prefix(id, 'Both runtime and latest environment variable configurations detected. Runtime configuration will take precedence over latest environment variables.'))
406
+ }
407
+
408
+ // Normalize runtime legacy options to latest format
409
+ if (runtime?.type === AZURE_BOT) {
410
+ runtime.azureBotOAuthConnectionName ??= runtime.name
411
+ runtime.invalidSignInRetryMax ??= runtime.maxAttempts
412
+ runtime.invalidSignInRetryMessage ??= runtime.messages?.invalidCode
413
+ runtime.invalidSignInRetryMessageFormat ??= runtime.messages?.invalidCodeFormat
414
+ runtime.invalidSignInRetryMaxExceededMessage ??= runtime.messages?.maxAttemptsExceeded
415
+ runtime.oboConnectionName ??= runtime.obo?.connection
416
+ runtime.oboScopes ??= runtime.obo?.scopes
417
+ delete runtime.name
418
+ delete runtime.maxAttempts
419
+ delete runtime.messages
420
+ delete runtime.obo
421
+ }
422
+
423
+ // Helper to remove undefined options
424
+ const prune = <T extends Record<string, any>>(obj: T) => {
425
+ const entries = Object.entries(obj).filter(([, e]) => e !== undefined)
426
+ return Object.fromEntries(entries) as T
427
+ }
428
+
429
+ // Priority: runtime > latest > legacy
430
+ // Pruning done individually to avoid overwriting with undefined when merging.
431
+ const options: AuthorizationOptions[string] = runtime ? { ...prune(legacy || {}), ...prune(runtime) } : prune(latest || {})
432
+
433
+ if (!settings.storage) {
434
+ throw ExceptionHelper.generateException(Error, Errors.StorageRequiredForAuthorization)
435
+ }
436
+
437
+ if (options.type === AGENTIC) {
438
+ this._handlers[id] = new AgenticAuthorization(id, options, settings)
439
+ } else if (options.type === AZURE_BOT) {
440
+ // Set default values if not provided
441
+ options.title ||= 'Sign-in'
442
+ options.text ||= 'Please sign-in to continue'
443
+ options.oboScopes ??= []
444
+ options.enableSso = options.enableSso !== false // default value is true if undefined.
445
+
446
+ this._handlers[id] = new AzureBotAuthorization(id, options, settings)
447
+ }
448
+
449
+ registeredHandlers.add(id.toLowerCase())
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Fixes the handler type based on the provided type string, supporting both latest and legacy formats.
455
+ */
456
+ private fixType (handlerId: string, type: AuthorizationOptions[string]['type'] | string) {
457
+ if (!type) {
458
+ return AZURE_BOT
459
+ }
460
+
461
+ if (type.toLowerCase() === AGENTIC_LEGACY.toLowerCase()) {
462
+ logger.warn(this.prefix(handlerId, 'The \'agentic\' type is deprecated. Please use \'AgenticUserAuthorization\' instead.'))
463
+ return AGENTIC
464
+ }
465
+
466
+ if (type.toLowerCase() === AGENTIC.toLowerCase()) {
467
+ return AGENTIC
468
+ }
469
+
470
+ if (type.toLowerCase() === AZURE_BOT.toLowerCase()) {
471
+ return AZURE_BOT
472
+ }
473
+
474
+ throw ExceptionHelper.generateException(Error, Errors.UnsupportedAuthHandlerType, undefined, { handlerType: type })
475
+ }
224
476
  }
@@ -6,6 +6,8 @@
6
6
  import { ActiveAuthorizationHandler } from './types'
7
7
  import { TurnContext } from '../../turnContext'
8
8
  import { Storage } from '../../storage'
9
+ import { ExceptionHelper } from '@microsoft/agents-activity'
10
+ import { Errors } from '../../errorHelper'
9
11
 
10
12
  /**
11
13
  * Storage manager for handler state.
@@ -25,7 +27,7 @@ export class HandlerStorage<TActiveHandler extends ActiveAuthorizationHandler =
25
27
  const channelId = this.context.activity.channelId?.trim()
26
28
  const userId = this.context.activity.from?.id?.trim()
27
29
  if (!channelId || !userId) {
28
- throw new Error(`Both 'activity.channelId' and 'activity.from.id' are required to generate the ${HandlerStorage.name} key.`)
30
+ throw ExceptionHelper.generateException(Error, Errors.ChannelIdAndFromIdRequired)
29
31
  }
30
32
  return `auth/${channelId}/${userId}`
31
33
  }