@microsoft/agents-hosting 1.8.1 → 1.9.0-beta.18.g6b8111bba5

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 (101) hide show
  1. package/README.md +179 -9
  2. package/dist/package.json +4 -4
  3. package/dist/src/app/agentApplication.d.ts +3 -3
  4. package/dist/src/app/agentApplication.js +6 -16
  5. package/dist/src/app/agentApplication.js.map +1 -1
  6. package/dist/src/app/agentApplicationOptions.d.ts +6 -0
  7. package/dist/src/app/auth/authorization.d.ts +54 -0
  8. package/dist/src/app/auth/authorization.js +63 -2
  9. package/dist/src/app/auth/authorization.js.map +1 -1
  10. package/dist/src/app/auth/authorizationManager.d.ts +0 -8
  11. package/dist/src/app/auth/authorizationManager.js +104 -178
  12. package/dist/src/app/auth/authorizationManager.js.map +1 -1
  13. package/dist/src/auth/authConfiguration.d.ts +10 -3
  14. package/dist/src/auth/authConfiguration.js +292 -259
  15. package/dist/src/auth/authConfiguration.js.map +1 -1
  16. package/dist/src/auth/delegatedTokenCredential.d.ts +29 -0
  17. package/dist/src/auth/delegatedTokenCredential.js +49 -0
  18. package/dist/src/auth/delegatedTokenCredential.js.map +1 -0
  19. package/dist/src/auth/msal/msalTokenCredential.js +1 -1
  20. package/dist/src/auth/msal/msalTokenCredential.js.map +1 -1
  21. package/dist/src/auth/settings.d.ts +8 -70
  22. package/dist/src/auth/settings.js +5 -67
  23. package/dist/src/auth/settings.js.map +1 -1
  24. package/dist/src/cloudAdapter.d.ts +5 -0
  25. package/dist/src/cloudAdapter.js +13 -103
  26. package/dist/src/cloudAdapter.js.map +1 -1
  27. package/dist/src/configuration/botFrameworkEnvironmentCompatibility.d.ts +23 -0
  28. package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js +183 -0
  29. package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js.map +1 -0
  30. package/dist/src/configuration/configuration.d.ts +83 -0
  31. package/dist/src/configuration/configuration.js +836 -0
  32. package/dist/src/configuration/configuration.js.map +1 -0
  33. package/dist/src/configuration/configurationSource.d.ts +57 -0
  34. package/dist/src/configuration/configurationSource.js +7 -0
  35. package/dist/src/configuration/configurationSource.js.map +1 -0
  36. package/dist/src/configuration/environmentConfiguration.d.ts +14 -0
  37. package/dist/src/configuration/environmentConfiguration.js +271 -0
  38. package/dist/src/configuration/environmentConfiguration.js.map +1 -0
  39. package/dist/src/configuration/index.d.ts +7 -0
  40. package/dist/src/configuration/index.js +14 -0
  41. package/dist/src/configuration/index.js.map +1 -0
  42. package/dist/src/configuration/jsonFileConfigurationSource.d.ts +15 -0
  43. package/dist/src/configuration/jsonFileConfigurationSource.js +58 -0
  44. package/dist/src/configuration/jsonFileConfigurationSource.js.map +1 -0
  45. package/dist/src/createCloudAdapter.d.ts +30 -5
  46. package/dist/src/createCloudAdapter.js +17 -10
  47. package/dist/src/createCloudAdapter.js.map +1 -1
  48. package/dist/src/errorHelper.js +90 -2
  49. package/dist/src/errorHelper.js.map +1 -1
  50. package/dist/src/index.d.ts +1 -0
  51. package/dist/src/index.js +1 -0
  52. package/dist/src/index.js.map +1 -1
  53. package/dist/src/outboundHostValidator.d.ts +4 -1
  54. package/dist/src/outboundHostValidator.js +25 -48
  55. package/dist/src/outboundHostValidator.js.map +1 -1
  56. package/dist/src/storage/fileStorage.d.ts +5 -2
  57. package/dist/src/storage/fileStorage.js +41 -3
  58. package/dist/src/storage/fileStorage.js.map +1 -1
  59. package/dist/src/storage/index.d.ts +1 -0
  60. package/dist/src/storage/index.js +1 -0
  61. package/dist/src/storage/index.js.map +1 -1
  62. package/dist/src/storage/memoryStorage.d.ts +4 -2
  63. package/dist/src/storage/memoryStorage.js +32 -6
  64. package/dist/src/storage/memoryStorage.js.map +1 -1
  65. package/dist/src/storage/storage.d.ts +14 -1
  66. package/dist/src/storage/storageExpiry.d.ts +6 -0
  67. package/dist/src/storage/storageExpiry.js +20 -0
  68. package/dist/src/storage/storageExpiry.js.map +1 -0
  69. package/dist/src/utils/env.d.ts +43 -0
  70. package/dist/src/utils/env.js +42 -6
  71. package/dist/src/utils/env.js.map +1 -1
  72. package/dist/src/utils.d.ts +1 -0
  73. package/dist/src/utils.js +10 -1
  74. package/dist/src/utils.js.map +1 -1
  75. package/package.json +4 -4
  76. package/src/app/agentApplication.ts +11 -14
  77. package/src/app/agentApplicationOptions.ts +7 -0
  78. package/src/app/auth/authorization.ts +67 -2
  79. package/src/app/auth/authorizationManager.ts +126 -204
  80. package/src/auth/authConfiguration.ts +415 -260
  81. package/src/auth/delegatedTokenCredential.ts +49 -0
  82. package/src/auth/msal/msalTokenCredential.ts +1 -1
  83. package/src/auth/settings.ts +5 -82
  84. package/src/cloudAdapter.ts +38 -113
  85. package/src/configuration/botFrameworkEnvironmentCompatibility.ts +245 -0
  86. package/src/configuration/configuration.ts +1076 -0
  87. package/src/configuration/configurationSource.ts +76 -0
  88. package/src/configuration/environmentConfiguration.ts +377 -0
  89. package/src/configuration/index.ts +19 -0
  90. package/src/configuration/jsonFileConfigurationSource.ts +74 -0
  91. package/src/createCloudAdapter.ts +52 -9
  92. package/src/errorHelper.ts +110 -2
  93. package/src/index.ts +1 -0
  94. package/src/outboundHostValidator.ts +37 -44
  95. package/src/storage/fileStorage.ts +43 -5
  96. package/src/storage/index.ts +1 -0
  97. package/src/storage/memoryStorage.ts +34 -8
  98. package/src/storage/storage.ts +15 -1
  99. package/src/storage/storageExpiry.ts +21 -0
  100. package/src/utils/env.ts +63 -0
  101. package/src/utils.ts +10 -0
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Activity, ExceptionHelper } from '@microsoft/agents-activity'
7
- import { debug, redactScopes } from '@microsoft/agents-telemetry'
7
+ import { debug, redactScopes, redactString } from '@microsoft/agents-telemetry'
8
8
  import { AgentApplication } from '../agentApplication'
9
9
  import { AgenticAuthorization, AzureBotAuthorization } from './handlers'
10
10
  import { TurnContext } from '../../turnContext'
@@ -13,8 +13,10 @@ import { Errors } from '../../errorHelper'
13
13
  import { ActiveAuthorizationHandler, AuthorizationHandlerStatus, AuthorizationHandler, AuthorizationHandlerSettings, AuthorizationOptions } from './types'
14
14
  import { Connections } from '../../auth/connections'
15
15
  import { sendInvokeResponse } from './utils'
16
- import { envParser, envParserUtils } from '../../auth/settings'
17
- import { prune } from '../../utils'
16
+ import { mergeDefined, prune } from '../../utils'
17
+ import { getConfigurationSnapshot } from '../../configuration/configuration'
18
+ import { loadModernEnvironmentConfiguration } from '../../configuration/environmentConfiguration'
19
+ import { loadBotFrameworkAuthorizationEnvironmentConfiguration } from '../../configuration/botFrameworkEnvironmentCompatibility'
18
20
 
19
21
  const logger = debug('agents:authorization:manager')
20
22
 
@@ -22,6 +24,39 @@ const AGENTIC = 'AgenticUserAuthorization'
22
24
  const AGENTIC_LEGACY = 'agentic'
23
25
  const AZURE_BOT = 'AzureBotUserAuthorization'
24
26
 
27
+ function redactAuthorizationString (value: unknown): string | undefined {
28
+ return typeof value === 'string' ? redactString(value) : undefined
29
+ }
30
+
31
+ function summarizeAuthorizationConfiguration (options: AuthorizationOptions[string]) {
32
+ return prune({
33
+ type: options.type,
34
+ azureBotOAuthConnectionName: 'azureBotOAuthConnectionName' in options
35
+ ? redactAuthorizationString(options.azureBotOAuthConnectionName)
36
+ : undefined,
37
+ invalidSignInRetryMax: 'invalidSignInRetryMax' in options && typeof options.invalidSignInRetryMax === 'number'
38
+ ? options.invalidSignInRetryMax
39
+ : undefined,
40
+ oboConnectionName: 'oboConnectionName' in options
41
+ ? redactAuthorizationString(options.oboConnectionName)
42
+ : undefined,
43
+ oboScopes: 'oboScopes' in options && Array.isArray(options.oboScopes) &&
44
+ options.oboScopes.every(scope => typeof scope === 'string')
45
+ ? redactScopes(options.oboScopes)
46
+ : undefined,
47
+ enableSso: 'enableSso' in options && typeof options.enableSso === 'boolean'
48
+ ? options.enableSso
49
+ : undefined,
50
+ scopes: 'scopes' in options && Array.isArray(options.scopes) &&
51
+ options.scopes.every(scope => typeof scope === 'string')
52
+ ? redactScopes(options.scopes)
53
+ : undefined,
54
+ altBlueprintConnectionName: 'altBlueprintConnectionName' in options
55
+ ? redactAuthorizationString(options.altBlueprintConnectionName)
56
+ : undefined
57
+ })
58
+ }
59
+
25
60
  /**
26
61
  * Active handler information used by the AuthorizationManager.
27
62
  */
@@ -60,92 +95,6 @@ type GetHandlerIds = (activity: Activity) => string[] | Promise<string[]>
60
95
  export class AuthorizationManager {
61
96
  private _handlers: Record<string, AuthorizationHandler> = {}
62
97
 
63
- /**
64
- * Environment variable configuration for the latest format.
65
- */
66
- private _envLatest = {
67
- key: {
68
- prefix: 'AgentApplication__UserAuthorization__Handlers__',
69
- separator: '__Settings__',
70
- create (id: string, prop: string) {
71
- return `${this.prefix}${id}${this.separator}${prop}`
72
- },
73
- extract (envKey: string) {
74
- // Substring: AgentApplication__UserAuthorization__Handlers__<id>__Settings__<prop>
75
- // position —————————————————————————————————————————> start^ ^end ^prop
76
- const start = this.prefix.length
77
- const end = envKey.toUpperCase().indexOf(this.separator.toUpperCase())
78
- if (end === -1) {
79
- return { id: undefined, prop: undefined }
80
- }
81
-
82
- const id = envKey.substring(start, end)
83
- const prop = envKey.substring(end + this.separator.length)
84
- return { id, prop }
85
- }
86
- },
87
- parser: envParser({
88
- // Common
89
- type: envParserUtils.bypass,
90
-
91
- // Azure Bot
92
- azureBotOAuthConnectionName: envParserUtils.bypass,
93
- title: envParserUtils.bypass,
94
- text: envParserUtils.bypass,
95
- invalidSignInRetryMessage: envParserUtils.bypass,
96
- invalidSignInRetryMessageFormat: envParserUtils.bypass,
97
- invalidSignInRetryMaxExceededMessage: envParserUtils.bypass,
98
- oboConnectionName: envParserUtils.bypass,
99
- enableSso (value) {
100
- return { value: value !== 'false' }
101
- },
102
- invalidSignInRetryMax (value) {
103
- return { value: parseInt(value) }
104
- },
105
- oboScopes (value) {
106
- return this.scopes(value)
107
- },
108
-
109
- // Agentic
110
- altBlueprintConnectionName: envParserUtils.bypass,
111
- scopes (value) {
112
- if (value.includes(',')) {
113
- return { value: value.split(',').map(s => s.trim()).filter(Boolean) }
114
- }
115
- return { value: value.split(/\s+/).filter(Boolean) }
116
- }
117
- }),
118
- }
119
-
120
- /**
121
- * Environment variable configuration for the legacy format.
122
- */
123
- private _envLegacy = {
124
- key: {
125
- separator: '_',
126
- },
127
- parser: envParser({
128
- // Common
129
- type: envParserUtils.redirect(this._envLatest.parser, 'type'),
130
-
131
- // Azure Bot
132
- connectionName: envParserUtils.redirect(this._envLatest.parser, 'azureBotOAuthConnectionName'),
133
- connectionTitle: envParserUtils.redirect(this._envLatest.parser, 'title'),
134
- connectionText: envParserUtils.redirect(this._envLatest.parser, 'text'),
135
- maxAttempts: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMax'),
136
- messages_invalidCode: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMessage'),
137
- messages_invalidCodeFormat: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMessageFormat'),
138
- messages_maxAttemptsExceeded: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMaxExceededMessage'),
139
- obo_connection: envParserUtils.redirect(this._envLatest.parser, 'oboConnectionName'),
140
- obo_scopes: envParserUtils.redirect(this._envLatest.parser, 'oboScopes'),
141
- enableSso: envParserUtils.redirect(this._envLatest.parser, 'enableSso'),
142
-
143
- // Agentic
144
- scopes: envParserUtils.redirect(this._envLatest.parser, 'scopes'),
145
- altBlueprintConnectionName: envParserUtils.redirect(this._envLatest.parser, 'altBlueprintConnectionName')
146
- })
147
- }
148
-
149
98
  /**
150
99
  * Creates an instance of the AuthorizationManager.
151
100
  * @param app The agent application instance.
@@ -318,82 +267,58 @@ export class AuthorizationManager {
318
267
  let legacyMessage = ''
319
268
  const settings: AuthorizationHandlerSettings = { storage: this.app.options.storage!, connections: this.connections }
320
269
  const runtimeOptionEntries = Object.entries(this.app.options.authorization ?? {})
321
- const result = { latest: {}, legacy: {} } as {
270
+ const result = {
271
+ latest: {},
272
+ legacy: {}
273
+ } as {
322
274
  latest: Record<string, Record<string, any> | undefined>;
323
275
  legacy: Record<string, Record<string, any> | undefined>;
324
276
  }
277
+ const external = getConfigurationSnapshot(this.app.options.configurationContext)
278
+ const externalOptionEntries = [
279
+ ...[...external.fallback.agentApplication.userAuthorization.handlers.values()].map(handler => [handler.id, handler.settings] as const),
280
+ ...[...external.overrideEnvironment.agentApplication.userAuthorization.handlers.values()].map(handler => [handler.id, handler.settings] as const),
281
+ ...[...external.enforce.agentApplication.userAuthorization.handlers.values()].map(handler => [handler.id, handler.settings] as const)
282
+ ]
283
+ const modernHandlers = loadModernEnvironmentConfiguration()
284
+ .agentApplication.userAuthorization.handlers
285
+ for (const handler of modernHandlers.values()) {
286
+ result.latest[handler.id] = { ...handler.settings }
287
+ }
325
288
 
326
- for (const [envKey, envValue] of Object.entries(process.env)) {
327
- if (!envValue?.trim()) {
328
- continue
329
- }
330
-
331
- const upperEnvKey = envKey.toUpperCase()
332
-
333
- // Legacy: extract handler ID, handler options key and its value, and assign it to the correct runtime handler ID.
334
- if (!upperEnvKey.startsWith(this._envLatest.key.prefix.toUpperCase())) {
335
- const [id] = runtimeOptionEntries.find(([id]) => upperEnvKey.startsWith(`${id.toUpperCase()}${this._envLegacy.key.separator}`)) ?? []
336
- if (!id) {
337
- continue
338
- }
339
-
340
- const prop = envKey.substring(id.length + this._envLegacy.key.separator.length)
341
- if (!prop) {
342
- continue
343
- }
344
-
345
- const { key, value } = this._envLegacy.parser.parse(prop as any, envValue)
346
- if (!key) {
347
- continue
348
- }
349
-
350
- legacyMessage += ` ${envKey}= # Use ${this._envLatest.key.create(id, key)} instead.\n`
351
-
352
- result.legacy[id] ??= {}
353
- result.legacy[id][key] = value
354
- continue
355
- }
356
-
357
- // Latest: extract handler ID, handler options key and its value, and assign it to the correct latest handler ID.
358
- const { id, prop } = this._envLatest.key.extract(envKey)
359
- if (!id || !prop) {
360
- continue
361
- }
362
-
363
- const { key, value } = this._envLatest.parser.parse(prop as any, envValue)
364
- if (!key) {
365
- continue
366
- }
367
-
368
- // Find existing handler ID case-insensitively to avoid duplicates, but keep the original casing for later processing.
369
- // 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.
370
- // 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".
371
- // 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.
372
- const existingId = id in result.latest ? id : Object.keys(result.latest).find(e => e.toLowerCase() === id.toLowerCase())
373
- const realId = existingId ?? id
374
- result.latest[realId] ??= {}
375
- result.latest[realId][key] = value
289
+ const compatibility = loadBotFrameworkAuthorizationEnvironmentConfiguration(
290
+ [...runtimeOptionEntries, ...externalOptionEntries].map(([id]) => id)
291
+ )
292
+ for (const handler of compatibility.layer.agentApplication.userAuthorization.handlers.values()) {
293
+ result.legacy[handler.id] = { ...handler.settings }
294
+ }
295
+ for (const replacement of compatibility.replacements) {
296
+ legacyMessage += ` ${replacement.legacyKey}= # Use ${replacement.modernKey} instead.\n`
376
297
  }
377
298
 
378
299
  if (legacyMessage.length > 0) {
379
300
  logger.warn('Deprecated environment variables detected, update to the latest format: (case-insensitive)', `[\n${legacyMessage}]`)
380
301
  }
381
302
 
382
- // Fix types
383
- const fixTypes = (entries: [string, any][]) => entries
384
- .map(([id, options]) => [id, { ...options, type: this.fixType(id, options?.type) }] as [string, AuthorizationOptions[string]])
385
-
386
- const runtimeEntries = fixTypes(runtimeOptionEntries)
387
- const latestEntries = fixTypes(Object.entries(result.latest))
388
- const legacyEntries = fixTypes(Object.entries(result.legacy))
389
-
390
303
  const registeredHandlers = new Set()
391
- for (const [id] of [...runtimeEntries, ...latestEntries]) {
304
+ const handlerEntries = [
305
+ ...runtimeOptionEntries,
306
+ ...Object.entries(result.latest),
307
+ ...Object.entries(result.legacy),
308
+ ...externalOptionEntries
309
+ ]
310
+ for (const [id] of handlerEntries) {
392
311
  if (registeredHandlers.has(id.toLowerCase())) {
393
312
  continue
394
313
  }
395
314
 
396
- const { options, format } = this.resolveHandlerConfiguration(id, runtimeEntries, latestEntries, legacyEntries)
315
+ const { options, format } = this.resolveHandlerConfiguration(
316
+ id,
317
+ runtimeOptionEntries,
318
+ Object.entries(result.latest),
319
+ Object.entries(result.legacy),
320
+ external
321
+ )
397
322
 
398
323
  if (options.type === AZURE_BOT) {
399
324
  // Set default values if not provided
@@ -403,17 +328,11 @@ export class AuthorizationManager {
403
328
  options.enableSso = options.enableSso !== false // default value is true if undefined.
404
329
  }
405
330
 
406
- const maskedOptions: any = {}
407
-
408
- if ('oboScopes' in options && options.oboScopes && options.oboScopes.length > 0) {
409
- maskedOptions.oboScopes = redactScopes(options.oboScopes)
410
- }
411
-
412
- if ('scopes' in options && options.scopes && options.scopes.length > 0) {
413
- maskedOptions.scopes = redactScopes(options.scopes)
414
- }
415
-
416
- logger.info(this.prefix(id, 'settings loaded from \'%s\''), format, { ...options, ...maskedOptions })
331
+ logger.info(
332
+ this.prefix(id, 'settings loaded from \'%s\''),
333
+ format,
334
+ summarizeAuthorizationConfiguration(options)
335
+ )
417
336
 
418
337
  if (!settings.storage) {
419
338
  throw ExceptionHelper.generateException(Error, Errors.StorageRequiredForAuthorization)
@@ -458,62 +377,65 @@ export class AuthorizationManager {
458
377
  */
459
378
  private resolveHandlerConfiguration (
460
379
  id: string,
461
- runtimeEntries: Array<[string, AuthorizationOptions[string]]>,
462
- latestEntries: Array<[string, AuthorizationOptions[string]]>,
463
- legacyEntries: Array<[string, AuthorizationOptions[string]]>
380
+ runtimeEntries: Array<[string, any]>,
381
+ latestEntries: Array<[string, any]>,
382
+ legacyEntries: Array<[string, any]>,
383
+ external: ReturnType<typeof getConfigurationSnapshot>
464
384
  ): {
465
385
  options: AuthorizationOptions[string];
466
386
  format: string;
467
387
  } {
468
- const matchesId = ([_id]: [string, AuthorizationOptions[string]]) => _id.toLowerCase() === id.toLowerCase()
388
+ const matchesId = ([_id]: [string, any]) => _id.toLowerCase() === id.toLowerCase()
389
+ const find = (entries: Array<[string, any]>) => entries.find(matchesId)?.[1]
390
+ const findExternal = (
391
+ entries: ReadonlyMap<string, Readonly<{ id: string; settings: Readonly<Record<string, unknown>> }>>
392
+ ) => [...entries.values()].find(handler => handler.id.toLowerCase() === id.toLowerCase())?.settings
469
393
 
470
- // Find entries case-insensitively for later processing
471
- const [, runtime] = runtimeEntries.find(matchesId) ?? []
472
- const [, latest] = latestEntries.find(matchesId) ?? []
473
- const [, legacy] = legacyEntries.find(matchesId) ?? []
394
+ const runtime = find(runtimeEntries)
395
+ const latest = find(latestEntries)
396
+ const legacy = find(legacyEntries)
474
397
 
475
398
  if (runtime !== undefined && latest !== undefined) {
476
399
  logger.warn(this.prefix(id, 'Both runtime options and latest environment variable configurations detected. Runtime configuration will take precedence over latest environment variables.'))
477
400
  }
478
401
 
402
+ const runtimeOptions = runtime === undefined ? undefined : { ...runtime }
479
403
  const runtimeLegacyKeys = ['name', 'maxAttempts', 'messages', 'obo']
480
- const isRuntimeLegacy = runtime !== undefined && Object.keys(runtime).some(key => runtimeLegacyKeys.includes(key))
481
-
482
- let runtimeLegacyFormat = ''
483
- if (isRuntimeLegacy && runtime.type === AZURE_BOT) {
484
- runtime.azureBotOAuthConnectionName ??= runtime.name
485
- runtime.invalidSignInRetryMax ??= runtime.maxAttempts
486
- runtime.invalidSignInRetryMessage ??= runtime.messages?.invalidCode
487
- runtime.invalidSignInRetryMessageFormat ??= runtime.messages?.invalidCodeFormat
488
- runtime.invalidSignInRetryMaxExceededMessage ??= runtime.messages?.maxAttemptsExceeded
489
- runtime.oboConnectionName ??= runtime.obo?.connection
490
- runtime.oboScopes ??= runtime.obo?.scopes
491
- delete runtime.name
492
- delete runtime.maxAttempts
493
- delete runtime.messages
494
- delete runtime.obo
495
- runtimeLegacyFormat = 'runtime options (legacy)'
496
- }
497
-
498
- if (runtime !== undefined && legacy !== undefined) {
499
- return { format: `${runtimeLegacyFormat || 'runtime options'} + .env variables (legacy)`, options: { ...prune(legacy), ...prune(runtime) } }
404
+ const isRuntimeLegacy = runtimeOptions !== undefined && Object.keys(runtimeOptions).some(key => runtimeLegacyKeys.includes(key))
405
+
406
+ let runtimeFormat = 'runtime options'
407
+ if (isRuntimeLegacy && this.fixType(id, runtimeOptions.type) === AZURE_BOT) {
408
+ runtimeOptions.azureBotOAuthConnectionName ??= runtimeOptions.name
409
+ runtimeOptions.invalidSignInRetryMax ??= runtimeOptions.maxAttempts
410
+ runtimeOptions.invalidSignInRetryMessage ??= runtimeOptions.messages?.invalidCode
411
+ runtimeOptions.invalidSignInRetryMessageFormat ??= runtimeOptions.messages?.invalidCodeFormat
412
+ runtimeOptions.invalidSignInRetryMaxExceededMessage ??= runtimeOptions.messages?.maxAttemptsExceeded
413
+ runtimeOptions.oboConnectionName ??= runtimeOptions.obo?.connection
414
+ runtimeOptions.oboScopes ??= runtimeOptions.obo?.scopes
415
+ delete runtimeOptions.name
416
+ delete runtimeOptions.maxAttempts
417
+ delete runtimeOptions.messages
418
+ delete runtimeOptions.obo
419
+ runtimeFormat = 'runtime options (legacy)'
500
420
  }
501
421
 
502
- if (runtime !== undefined) {
503
- return { format: runtimeLegacyFormat || 'runtime options', options: prune(runtime) }
422
+ const layers: Array<[string, Record<string, any> | undefined]> = [
423
+ ['external configuration (fallback)', findExternal(external.fallback.agentApplication.userAuthorization.handlers)],
424
+ ['.env variables (legacy)', runtimeOptions !== undefined || latest === undefined ? legacy : undefined],
425
+ ['.env variables', runtimeOptions === undefined ? latest : undefined],
426
+ ['external configuration (overrideEnvironment)', findExternal(external.overrideEnvironment.agentApplication.userAuthorization.handlers)],
427
+ [runtimeFormat, runtimeOptions],
428
+ ['external configuration (enforce)', findExternal(external.enforce.agentApplication.userAuthorization.handlers)]
429
+ ]
430
+ const activeLayers = layers.filter((layer): layer is [string, Record<string, any>] => layer[1] !== undefined)
431
+ const options = mergeDefined<AuthorizationOptions[string]>(
432
+ ...activeLayers.map(([, layer]) => layer)
433
+ )
434
+ options.type = this.fixType(id, options.type)
435
+
436
+ return {
437
+ format: activeLayers.map(([format]) => format).join(' + ') || 'empty options',
438
+ options
504
439
  }
505
-
506
- if (latest !== undefined) {
507
- return { format: '.env variables', options: prune(latest) }
508
- }
509
-
510
- if (legacy !== undefined) {
511
- // Note: this should never happen, as legacy requires runtime to be present.
512
- // However, we show a warning just in case to detect if something goes wrong.
513
- logger.warn(this.prefix(id, 'Legacy environment variable configuration detected without a corresponding runtime options configuration.'))
514
- return { format: '.env variables (legacy)', options: prune(legacy) }
515
- }
516
-
517
- return { format: 'empty options', options: {} }
518
440
  }
519
441
  }