@microsoft/agents-hosting 1.9.0-beta.3.gb2d23c1c44 → 1.9.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 (150) hide show
  1. package/README.md +182 -4
  2. package/dist/package.json +3 -3
  3. package/dist/src/app/agentApplication.d.ts +4 -3
  4. package/dist/src/app/agentApplication.js +24 -21
  5. package/dist/src/app/agentApplication.js.map +1 -1
  6. package/dist/src/app/agentApplicationBuilder.d.ts +9 -2
  7. package/dist/src/app/agentApplicationBuilder.js +9 -0
  8. package/dist/src/app/agentApplicationBuilder.js.map +1 -1
  9. package/dist/src/app/agentApplicationOptions.d.ts +16 -4
  10. package/dist/src/app/auth/authorization.d.ts +54 -0
  11. package/dist/src/app/auth/authorization.js +63 -2
  12. package/dist/src/app/auth/authorization.js.map +1 -1
  13. package/dist/src/app/auth/authorizationManager.d.ts +0 -8
  14. package/dist/src/app/auth/authorizationManager.js +104 -178
  15. package/dist/src/app/auth/authorizationManager.js.map +1 -1
  16. package/dist/src/app/auth/handlerStorage.d.ts +5 -3
  17. package/dist/src/app/auth/handlerStorage.js +36 -10
  18. package/dist/src/app/auth/handlerStorage.js.map +1 -1
  19. package/dist/src/app/auth/types.d.ts +2 -2
  20. package/dist/src/app/index.d.ts +1 -0
  21. package/dist/src/app/proactive/proactive.js +10 -6
  22. package/dist/src/app/proactive/proactive.js.map +1 -1
  23. package/dist/src/app/proactive/proactiveOptions.d.ts +2 -2
  24. package/dist/src/app/rateLimit.d.ts +315 -0
  25. package/dist/src/app/rateLimit.js +333 -0
  26. package/dist/src/app/rateLimit.js.map +1 -0
  27. package/dist/src/app/turnState.d.ts +6 -3
  28. package/dist/src/app/turnState.js +43 -4
  29. package/dist/src/app/turnState.js.map +1 -1
  30. package/dist/src/auth/authConfiguration.d.ts +10 -3
  31. package/dist/src/auth/authConfiguration.js +292 -259
  32. package/dist/src/auth/authConfiguration.js.map +1 -1
  33. package/dist/src/auth/delegatedTokenCredential.d.ts +29 -0
  34. package/dist/src/auth/delegatedTokenCredential.js +49 -0
  35. package/dist/src/auth/delegatedTokenCredential.js.map +1 -0
  36. package/dist/src/auth/msal/msalTokenCredential.js +1 -1
  37. package/dist/src/auth/msal/msalTokenCredential.js.map +1 -1
  38. package/dist/src/auth/settings.d.ts +8 -70
  39. package/dist/src/auth/settings.js +5 -67
  40. package/dist/src/auth/settings.js.map +1 -1
  41. package/dist/src/cloudAdapter.d.ts +5 -0
  42. package/dist/src/cloudAdapter.js +13 -103
  43. package/dist/src/cloudAdapter.js.map +1 -1
  44. package/dist/src/configuration/botFrameworkEnvironmentCompatibility.d.ts +23 -0
  45. package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js +183 -0
  46. package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js.map +1 -0
  47. package/dist/src/configuration/configuration.d.ts +83 -0
  48. package/dist/src/configuration/configuration.js +836 -0
  49. package/dist/src/configuration/configuration.js.map +1 -0
  50. package/dist/src/configuration/configurationSource.d.ts +57 -0
  51. package/dist/src/configuration/configurationSource.js +7 -0
  52. package/dist/src/configuration/configurationSource.js.map +1 -0
  53. package/dist/src/configuration/environmentConfiguration.d.ts +14 -0
  54. package/dist/src/configuration/environmentConfiguration.js +271 -0
  55. package/dist/src/configuration/environmentConfiguration.js.map +1 -0
  56. package/dist/src/configuration/index.d.ts +7 -0
  57. package/dist/src/configuration/index.js +14 -0
  58. package/dist/src/configuration/index.js.map +1 -0
  59. package/dist/src/configuration/jsonFileConfigurationSource.d.ts +15 -0
  60. package/dist/src/configuration/jsonFileConfigurationSource.js +58 -0
  61. package/dist/src/configuration/jsonFileConfigurationSource.js.map +1 -0
  62. package/dist/src/createCloudAdapter.d.ts +30 -5
  63. package/dist/src/createCloudAdapter.js +17 -10
  64. package/dist/src/createCloudAdapter.js.map +1 -1
  65. package/dist/src/errorHelper.js +124 -5
  66. package/dist/src/errorHelper.js.map +1 -1
  67. package/dist/src/index.d.ts +1 -0
  68. package/dist/src/index.js +1 -0
  69. package/dist/src/index.js.map +1 -1
  70. package/dist/src/observability/traces.d.ts +6 -4
  71. package/dist/src/observability/traces.js +10 -9
  72. package/dist/src/observability/traces.js.map +1 -1
  73. package/dist/src/outboundHostValidator.d.ts +4 -1
  74. package/dist/src/outboundHostValidator.js +25 -48
  75. package/dist/src/outboundHostValidator.js.map +1 -1
  76. package/dist/src/state/agentState.d.ts +8 -3
  77. package/dist/src/state/agentState.js +26 -8
  78. package/dist/src/state/agentState.js.map +1 -1
  79. package/dist/src/state/conversationState.d.ts +2 -2
  80. package/dist/src/state/conversationState.js.map +1 -1
  81. package/dist/src/state/userState.d.ts +2 -2
  82. package/dist/src/state/userState.js.map +1 -1
  83. package/dist/src/statusCodes.d.ts +5 -0
  84. package/dist/src/statusCodes.js +5 -0
  85. package/dist/src/statusCodes.js.map +1 -1
  86. package/dist/src/storage/fileStorage.d.ts +83 -68
  87. package/dist/src/storage/fileStorage.js +279 -105
  88. package/dist/src/storage/fileStorage.js.map +1 -1
  89. package/dist/src/storage/index.d.ts +1 -0
  90. package/dist/src/storage/index.js +1 -0
  91. package/dist/src/storage/index.js.map +1 -1
  92. package/dist/src/storage/memoryStorage.d.ts +85 -60
  93. package/dist/src/storage/memoryStorage.js +244 -87
  94. package/dist/src/storage/memoryStorage.js.map +1 -1
  95. package/dist/src/storage/storage.d.ts +100 -2
  96. package/dist/src/storage/storage.js +25 -0
  97. package/dist/src/storage/storage.js.map +1 -1
  98. package/dist/src/storage/storageCompatibility.d.ts +17 -0
  99. package/dist/src/storage/storageCompatibility.js +157 -0
  100. package/dist/src/storage/storageCompatibility.js.map +1 -0
  101. package/dist/src/storage/storageExpiry.d.ts +6 -0
  102. package/dist/src/storage/storageExpiry.js +20 -0
  103. package/dist/src/storage/storageExpiry.js.map +1 -0
  104. package/dist/src/utils/env.d.ts +43 -0
  105. package/dist/src/utils/env.js +42 -6
  106. package/dist/src/utils/env.js.map +1 -1
  107. package/dist/src/utils.d.ts +1 -0
  108. package/dist/src/utils.js +10 -1
  109. package/dist/src/utils.js.map +1 -1
  110. package/package.json +3 -3
  111. package/src/app/agentApplication.ts +27 -14
  112. package/src/app/agentApplicationBuilder.ts +13 -2
  113. package/src/app/agentApplicationOptions.ts +18 -4
  114. package/src/app/auth/authorization.ts +67 -2
  115. package/src/app/auth/authorizationManager.ts +126 -204
  116. package/src/app/auth/handlerStorage.ts +51 -7
  117. package/src/app/auth/types.ts +2 -2
  118. package/src/app/index.ts +7 -0
  119. package/src/app/proactive/proactive.ts +19 -9
  120. package/src/app/proactive/proactiveOptions.ts +2 -2
  121. package/src/app/rateLimit.ts +659 -0
  122. package/src/app/turnState.ts +50 -7
  123. package/src/auth/authConfiguration.ts +415 -260
  124. package/src/auth/delegatedTokenCredential.ts +49 -0
  125. package/src/auth/msal/msalTokenCredential.ts +1 -1
  126. package/src/auth/settings.ts +5 -82
  127. package/src/cloudAdapter.ts +38 -113
  128. package/src/configuration/botFrameworkEnvironmentCompatibility.ts +245 -0
  129. package/src/configuration/configuration.ts +1076 -0
  130. package/src/configuration/configurationSource.ts +76 -0
  131. package/src/configuration/environmentConfiguration.ts +377 -0
  132. package/src/configuration/index.ts +19 -0
  133. package/src/configuration/jsonFileConfigurationSource.ts +74 -0
  134. package/src/createCloudAdapter.ts +52 -9
  135. package/src/errorHelper.ts +151 -5
  136. package/src/index.ts +1 -0
  137. package/src/observability/traces.ts +8 -7
  138. package/src/outboundHostValidator.ts +37 -44
  139. package/src/state/agentState.ts +39 -13
  140. package/src/state/conversationState.ts +2 -2
  141. package/src/state/userState.ts +2 -2
  142. package/src/statusCodes.ts +6 -0
  143. package/src/storage/fileStorage.ts +299 -106
  144. package/src/storage/index.ts +1 -0
  145. package/src/storage/memoryStorage.ts +268 -85
  146. package/src/storage/storage.ts +114 -2
  147. package/src/storage/storageCompatibility.ts +185 -0
  148. package/src/storage/storageExpiry.ts +21 -0
  149. package/src/utils/env.ts +63 -0
  150. package/src/utils.ts +10 -0
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { AccessToken, GetTokenOptions, TokenCredential } from '@azure/core-auth'
7
+ import { ExceptionHelper } from '@microsoft/agents-activity'
8
+ import { Errors } from '../errorHelper'
9
+ import { TokenResponse } from '../oauth'
10
+
11
+ /**
12
+ * Default lifetime assumed for a token when the underlying provider does not report an expiration.
13
+ */
14
+ const DEFAULT_TOKEN_LIFETIME_MS = 5 * 60 * 1000
15
+
16
+ /**
17
+ * Azure Core Auth {@link TokenCredential} implementation backed by an Agents SDK token provider
18
+ *
19
+ * @remarks
20
+ * This allows Agents SDK-managed tokens (user sign-in, OBO exchange) to be consumed directly by
21
+ * Azure SDK clients that expect a `TokenCredential`.
22
+ */
23
+ export class DelegatedTokenCredential implements TokenCredential {
24
+ /**
25
+ * Creates a new instance of DelegatedTokenCredential.
26
+ * @param provider Function invoked to acquire a token for the requested scopes.
27
+ */
28
+ constructor (private provider: (scopes: string[], options?: GetTokenOptions) => Promise<TokenResponse | undefined>) {}
29
+
30
+ /**
31
+ * Retrieves an access token for the specified scopes using the configured provider.
32
+ * @param scopes Array of scopes for which to request an access token.
33
+ * @param options Optional parameters for token retrieval.
34
+ * @returns Promise that resolves to an access token.
35
+ * @throws {Error} If the provider returns a null/undefined response or a response without a token.
36
+ */
37
+ public async getToken (scopes: string | string[], options?: GetTokenOptions): Promise<AccessToken> {
38
+ const requestedScopes = Array.isArray(scopes) ? scopes : [scopes]
39
+ const response = await this.provider(requestedScopes, options)
40
+ if (!response?.token) {
41
+ throw ExceptionHelper.generateException(Error, Errors.NullTokenResponse)
42
+ }
43
+
44
+ return {
45
+ token: response.token,
46
+ expiresOnTimestamp: Date.now() + DEFAULT_TOKEN_LIFETIME_MS
47
+ }
48
+ }
49
+ }
@@ -1,7 +1,7 @@
1
1
  import { GetTokenOptions, TokenCredential } from '@azure/core-auth'
2
+ import { debug } from '@microsoft/agents-telemetry'
2
3
  import { AuthConfiguration } from '../authConfiguration'
3
4
  import { MsalTokenProvider } from './msalTokenProvider'
4
- import { debug } from '@microsoft/agents-telemetry'
5
5
 
6
6
  const logger = debug('agents:msal')
7
7
 
@@ -11,6 +11,8 @@ import type { SidecarConnectionSettings } from './sidecar/sidecarConnectionSetti
11
11
 
12
12
  export type { MsalConnectionSettings } from './msal/msalConnectionSettings'
13
13
  export type { SidecarConnectionSettings } from './sidecar/sidecarConnectionSettings'
14
+ export { envParser, envParserUtils, loadEnvSettings } from '../utils/env'
15
+ export type { LoadEnv } from '../utils/env'
14
16
 
15
17
  const logger = debug('agents:authConfiguration')
16
18
 
@@ -32,25 +34,6 @@ export const DEFAULT_CONNECTION_MAP: ConnectionMapItem = { serviceUrl: '*', conn
32
34
  export type ConnectionKeys = keyof Omit<AuthConfiguration, 'connections' | 'connectionsMap'>
33
35
  export type ConnectionMapKeys = keyof ConnectionMapItem
34
36
 
35
- export interface LoadEnv {
36
- [upperKey: string]: { key: string, value: any }
37
- }
38
-
39
- export function loadEnvSettings (callback: (key: string, value: string) => void) {
40
- const env: LoadEnv = {}
41
- for (const [envKey, envValue] of Object.entries(process.env)) {
42
- if (!envValue?.trim()) {
43
- continue
44
- }
45
-
46
- env[envKey.toUpperCase()] = { key: envKey, value: envValue }
47
-
48
- callback(envKey, envValue)
49
- }
50
-
51
- return env
52
- }
53
-
54
37
  export function applyDefaultSettings (config: AuthConfiguration) {
55
38
  const settings = { ...config }
56
39
  settings.authorityEndpoint ??= settings.authority ?? AUTHORITY_DEFAULT
@@ -91,68 +74,6 @@ export function applyDefaultSettings (config: AuthConfiguration) {
91
74
  return settings
92
75
  }
93
76
 
94
- /**
95
- * A type representing a parser settings object.
96
- */
97
- type ParserSettings<K extends string> = {
98
- [key in K]: (value: string) => { key?: string, value?: any } | undefined
99
- }
100
-
101
- /**
102
- * Creates an environment variable parser that maps the variable keys to parsing functions.
103
- * @param settings An object where each key is an environment variable name and the value is a function
104
- * that takes the variable value as input and returns an object with optional `key` and `value` properties.
105
- * @remarks
106
- * The `key` property in the returned object can be used to rename the environment variable key,
107
- * while the `value` property contains the parsed value.
108
- * @returns An object with a `parse` method that takes an environment variable key and value,
109
- * and returns the parsed result.
110
- */
111
- export function envParser<K extends string> (settings: ParserSettings<K> & ThisType<ParserSettings<K>>) {
112
- const keys = Object.keys(settings) as K[]
113
- const upperKeys = keys.reduce((acc, key) => {
114
- acc[key.toUpperCase()] = key
115
- return acc
116
- }, {} as Record<string, K>)
117
- return {
118
- keys,
119
- /**
120
- * Parses the given environment variable key and value using the provided settings.
121
- * @param key The environment variable key.
122
- * @param value The environment variable value.
123
- * @returns The parsed result with optional renamed key and parsed value.
124
- */
125
- parse (key: K, value: string) {
126
- const match = upperKeys[key.toUpperCase()]
127
- if (!match) {
128
- return {}
129
- }
130
-
131
- const result = settings[match](value)
132
- return { key: result?.key ?? match, value: result?.value }
133
- }
134
- }
135
- }
136
-
137
- /**
138
- * Utility functions for environment variable parsers.
139
- */
140
- export const envParserUtils = {
141
- /**
142
- * Bypass parser that returns the value as is.
143
- * @param value The environment variable value.
144
- * @returns An object with the original value.
145
- */
146
- bypass: (value: string) => ({ value }),
147
- /**
148
- * Redirects the parsing to another parser for a specific key.
149
- * @param parser The target parser to redirect to.
150
- * @param key The key to use in the target parser.
151
- * @returns A function that takes the environment variable value and returns the parsed result from the target parser.
152
- */
153
- redirect: <Parser extends ReturnType<typeof envParser>>(parser: Parser, key: Parameters<Parser['parse']>[0]) => (value: string) => parser.parse(key, value)
154
- }
155
-
156
77
  /**
157
78
  * Resolves the full authority URL including the tenant ID.
158
79
  * Supports both patterns:
@@ -234,7 +155,9 @@ export interface ConnectionSettingsBase {
234
155
  *
235
156
  * @remarks
236
157
  * Disabled by default for backward compatibility. Tenant-to-issuer binding is always applied
237
- * independently when both claims contain comparable tenant GUIDs.
158
+ * independently when both claims contain comparable tenant GUIDs. Environment configuration
159
+ * accepts `true`, `false`, `1`, and `0` case-insensitively with surrounding whitespace ignored.
160
+ * Any other non-empty value fails configuration loading.
238
161
  */
239
162
  validateIssuer?: boolean
240
163
 
@@ -28,12 +28,17 @@ import { HeaderPropagation, HeaderPropagationCollection, HeaderPropagationDefini
28
28
  import { JwtPayload } from 'jsonwebtoken'
29
29
  import { getTokenServiceEndpoint } from './oauth/customUserTokenAPI'
30
30
  import { Connections } from './auth/connections'
31
- import { parseBooleanEnv, suggestClosest } from './utils/env'
32
31
  import { trace } from '@microsoft/agents-telemetry'
33
32
  import { AdapterTraceDefinitions } from './observability'
34
33
  import { applyAgenticHeaders } from './getProductInfo'
35
- import { createOutboundHostValidator, OutboundUrlPolicy } from './outboundHostValidator'
34
+ import {
35
+ createOutboundHostValidator,
36
+ OutboundUrlPolicy
37
+ } from './outboundHostValidator'
36
38
  import { authorizeJWT } from './auth/jwt-middleware'
39
+ import { ConfigurationContext, getConfigurationSnapshot } from './configuration/configuration'
40
+ import { loadModernEnvironmentConfiguration } from './configuration/environmentConfiguration'
41
+ import { mergeDefined } from './utils'
37
42
 
38
43
  const logger = debug('agents:cloud-adapter')
39
44
 
@@ -62,6 +67,11 @@ const logger = debug('agents:cloud-adapter')
62
67
  * win over environment variables.
63
68
  */
64
69
  export interface CloudAdapterOptions {
70
+ /**
71
+ * Optional host-scoped external configuration.
72
+ */
73
+ configurationContext?: ConfigurationContext
74
+
65
75
  /**
66
76
  * When `true`, the default `onTurnError` handler includes `error.stack` in
67
77
  * its log output. Defaults to `false`.
@@ -89,123 +99,35 @@ export interface CloudAdapterOptions {
89
99
  validateServiceUrl?: boolean
90
100
  }
91
101
 
92
- const DEFAULT_CLOUD_ADAPTER_OPTIONS: Required<CloudAdapterOptions> = {
102
+ type ResolvedCloudAdapterOptions = Required<Pick<CloudAdapterOptions, 'emitStackTrace' | 'validateServiceUrl'>>
103
+
104
+ const DEFAULT_CLOUD_ADAPTER_OPTIONS: ResolvedCloudAdapterOptions = {
93
105
  emitStackTrace: false,
94
106
  validateServiceUrl: false
95
107
  }
96
108
 
97
- /** Env-var prefix for {@link CloudAdapterOptions} (matches .NET config section). */
98
- const CLOUD_ADAPTER_OPTIONS_ENV_PREFIX = 'CloudAdapterOptions__'
99
- const CLOUD_ADAPTER_OPTIONS_ENV_PREFIX_UPPER = CLOUD_ADAPTER_OPTIONS_ENV_PREFIX.toUpperCase()
100
-
101
- /**
102
- * Declarative env-var parser for {@link CloudAdapterOptions}.
103
- *
104
- * Shape mirrors `envParser<K>` introduced in PR #1119
105
- * (`packages/agents-hosting/src/auth/settings.ts`) so this loader can be
106
- * swapped for the shared utility once that lands. Each entry is a
107
- * `(rawValue) => parsedValue | undefined` function. Lookups are
108
- * case-insensitive via the `upperKeys` map, matching the upstream
109
- * convention and accommodating hosts that uppercase env-var names.
110
- *
111
- * Adding a new option to `CloudAdapterOptions` only requires adding an
112
- * entry here.
113
- */
114
- const cloudAdapterOptionsParser = (() => {
115
- const schema: {
116
- [K in keyof Required<CloudAdapterOptions>]: (raw: string | undefined) => CloudAdapterOptions[K]
117
- } = {
118
- emitStackTrace: parseBooleanEnv,
119
- validateServiceUrl: parseBooleanEnv
120
- }
121
- const keys = Object.keys(schema) as Array<keyof CloudAdapterOptions>
122
- const upperKeys = keys.reduce<Record<string, keyof CloudAdapterOptions>>((acc, key) => {
123
- acc[key.toUpperCase()] = key
124
- return acc
125
- }, {})
126
- return {
127
- schema,
128
- keys,
129
- /**
130
- * Resolves an env-var property name (case-insensitive) to its canonical
131
- * `CloudAdapterOptions` key, or `undefined` when the name is unknown.
132
- */
133
- resolveKey (property: string): keyof CloudAdapterOptions | undefined {
134
- return upperKeys[property.toUpperCase()]
135
- }
136
- }
137
- })()
138
-
139
- /**
140
- * Per-process dedup set for configuration warnings. Without this, every
141
- * `new CloudAdapter()` re-scans `process.env` and re-emits warnings for any
142
- * typo'd `CloudAdapterOptions__*` key (or unparseable value), which spams
143
- * stderr in multi-adapter scenarios (tests, proactive flows, DI containers).
144
- */
145
- const warnedConfigKeys = new Set<string>()
146
-
147
- function emitConfigWarning (envKey: string, message: string): void {
148
- if (warnedConfigKeys.has(envKey)) return
149
- warnedConfigKeys.add(envKey)
150
- // Visible by default (writes synchronously to stderr) so users see typos
151
- // and bad values without having to opt in via `DEBUG=agents:cloud-adapter:*`.
152
- // Hosts that want to route or suppress can intercept `console.warn` or
153
- // subscribe to the `agents:cloud-adapter:warn` debug namespace below.
154
- console.warn(`[agents:cloud-adapter] ${message}`)
155
- logger.warn(message)
156
- }
157
-
158
- /**
159
- * Scans `process.env` for keys with the `CloudAdapterOptions__` prefix
160
- * (case-insensitive) and returns the parsed partial options. Unknown keys
161
- * and values that fail to parse are warned about once per process via
162
- * `console.warn` (so they are visible by default without enabling debug)
163
- * and through the `agents:cloud-adapter:warn` debug channel for log
164
- * aggregators. Hosts that want to route or suppress these diagnostics can
165
- * intercept `console.warn` or filter the debug namespace.
166
- */
167
- function loadCloudAdapterOptionsFromEnv (): CloudAdapterOptions {
168
- const result: CloudAdapterOptions = {}
169
- for (const [envKey, rawValue] of Object.entries(process.env)) {
170
- const upper = envKey.toUpperCase()
171
- if (!upper.startsWith(CLOUD_ADAPTER_OPTIONS_ENV_PREFIX_UPPER)) continue
172
- const property = envKey.substring(CLOUD_ADAPTER_OPTIONS_ENV_PREFIX.length)
173
- const canonical = cloudAdapterOptionsParser.resolveKey(property)
174
- if (!canonical) {
175
- const suggestion = suggestClosest(property, cloudAdapterOptionsParser.keys as readonly string[], 4)
176
- const hint = suggestion ? ` Did you mean "${CLOUD_ADAPTER_OPTIONS_ENV_PREFIX}${suggestion}"?` : ''
177
- emitConfigWarning(envKey, `Unknown CloudAdapterOptions env var: ${envKey} (ignored).${hint}`)
178
- continue
179
- }
180
- const parsed = cloudAdapterOptionsParser.schema[canonical](rawValue) as any
181
- if (parsed !== undefined) {
182
- (result as any)[canonical] = parsed
183
- } else if (rawValue !== undefined && rawValue.trim() !== '') {
184
- // Known key, recognized but unparseable value (e.g. `yes`, `on`, `enabled`).
185
- // For a security-relevant flag like `validateServiceUrl`, silent
186
- // fallthrough is the dangerous failure mode — surface it.
187
- // Note: parseBooleanEnv treats whitespace-only as unset, so don't warn
188
- // on `' '`; only warn when the user actually typed something.
189
- emitConfigWarning(
190
- `${envKey}=${rawValue}`,
191
- `Ignored ${envKey}=${rawValue}; expected one of true/false/1/0.`
192
- )
193
- }
194
- }
195
- return result
196
- }
197
-
198
109
  /**
199
110
  * Resolves a `CloudAdapterOptions` instance from an explicit argument or, if
200
111
  * absent, from environment variables. Values supplied in the explicit object
201
112
  * win over env vars.
202
113
  */
203
- function resolveCloudAdapterOptions (options?: CloudAdapterOptions): Required<CloudAdapterOptions> {
204
- const fromEnv = loadCloudAdapterOptionsFromEnv()
205
- return {
206
- emitStackTrace: options?.emitStackTrace ?? fromEnv.emitStackTrace ?? DEFAULT_CLOUD_ADAPTER_OPTIONS.emitStackTrace,
207
- validateServiceUrl: options?.validateServiceUrl ?? fromEnv.validateServiceUrl ?? DEFAULT_CLOUD_ADAPTER_OPTIONS.validateServiceUrl
208
- }
114
+ function resolveCloudAdapterOptions (options?: CloudAdapterOptions): ResolvedCloudAdapterOptions {
115
+ const external = getConfigurationSnapshot(options?.configurationContext)
116
+ const fromEnv = loadModernEnvironmentConfiguration(
117
+ process.env,
118
+ { reportCloudAdapterDiagnostics: true }
119
+ ).cloudAdapterOptions
120
+ return mergeDefined<ResolvedCloudAdapterOptions>(
121
+ DEFAULT_CLOUD_ADAPTER_OPTIONS,
122
+ external.fallback.cloudAdapterOptions,
123
+ fromEnv,
124
+ external.overrideEnvironment.cloudAdapterOptions,
125
+ {
126
+ emitStackTrace: options?.emitStackTrace,
127
+ validateServiceUrl: options?.validateServiceUrl
128
+ },
129
+ external.enforce.cloudAdapterOptions
130
+ )
209
131
  }
210
132
 
211
133
  /**
@@ -251,7 +173,7 @@ export class CloudAdapter extends BaseAdapter {
251
173
  */
252
174
  connectionManager: Connections
253
175
 
254
- private readonly _options: Required<CloudAdapterOptions>
176
+ private readonly _options: ResolvedCloudAdapterOptions
255
177
  private readonly _hostValidator: OutboundUrlPolicy
256
178
 
257
179
  /**
@@ -266,11 +188,14 @@ export class CloudAdapter extends BaseAdapter {
266
188
  */
267
189
  constructor (authConfig?: AuthConfiguration, authProvider?: AuthProvider, userTokenClient?: UserTokenClient, options?: CloudAdapterOptions, outboundHostValidator?: OutboundUrlPolicy) {
268
190
  super()
269
- this.authConfig = authConfig = getAuthConfigWithDefaults(authConfig)
191
+ this.authConfig = authConfig = getAuthConfigWithDefaults(authConfig, {
192
+ configurationContext: options?.configurationContext
193
+ })
270
194
  this.jwtMiddleware = authorizeJWT(authConfig)
271
195
  this.connectionManager = new MsalConnectionManager(undefined, undefined, authConfig)
272
196
  this._options = resolveCloudAdapterOptions(options)
273
- this._hostValidator = outboundHostValidator ?? createOutboundHostValidator()
197
+ this._hostValidator = outboundHostValidator ??
198
+ createOutboundHostValidator({ configurationContext: options?.configurationContext })
274
199
 
275
200
  // Install a CloudAdapter-aware default `onTurnError` that honors
276
201
  // `emitStackTrace`. The base class default only logs the message; we
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import {
7
+ ConfigurationLayer,
8
+ createConfigurationLayer,
9
+ freezeConfigurationLayer,
10
+ isConfigurationInputError,
11
+ isInvalidConfigurationValueError,
12
+ setConfigurationValue
13
+ } from './configuration'
14
+ import { LoadEnv, loadEnvSettings } from '../utils/env'
15
+
16
+ const DEFAULT_CONNECTION_ID = 'serviceConnection'
17
+
18
+ const flatConnectionProperties = {
19
+ MicrosoftAppTenantId: 'tenantId',
20
+ MicrosoftAppId: 'clientId',
21
+ MicrosoftAppPassword: 'clientSecret',
22
+ certPemFile: 'certPemFile',
23
+ certKeyFile: 'certKeyFile',
24
+ connectionName: 'connectionName',
25
+ MicrosoftAppClientId: 'federatedClientId',
26
+ authorityEndpoint: 'authorityEndpoint',
27
+ scope: 'scopes',
28
+ altBlueprintConnectionName: 'altBlueprintConnectionName',
29
+ alternateBlueprintConnectionName: 'altBlueprintConnectionName',
30
+ WIDAssertionFile: 'WIDAssertionFile',
31
+ azureRegion: 'azureRegion',
32
+ sendX5C: 'sendX5C',
33
+ msalRetryCount: 'msalRetryCount',
34
+ authType: 'authType',
35
+ federatedTokenFile: 'federatedTokenFile',
36
+ idpmResource: 'idpmResource',
37
+ sidecarBaseUrl: 'sidecarBaseUrl',
38
+ serviceName: 'serviceName',
39
+ blueprintServiceName: 'blueprintServiceName',
40
+ bypassLocalNetworkRestriction: 'bypassLocalNetworkRestriction',
41
+ requestTimeout: 'requestTimeout',
42
+ retryCount: 'retryCount',
43
+ validateIssuer: 'validateIssuer'
44
+ } as const
45
+
46
+ const prefixedConnectionProperties = {
47
+ tenantId: 'tenantId',
48
+ clientId: 'clientId',
49
+ clientSecret: 'clientSecret',
50
+ certPemFile: 'certPemFile',
51
+ certKeyFile: 'certKeyFile',
52
+ connectionName: 'connectionName',
53
+ FICClientId: 'federatedClientId',
54
+ authorityEndpoint: 'authorityEndpoint',
55
+ scope: 'scopes',
56
+ altBlueprintConnectionName: 'altBlueprintConnectionName',
57
+ alternateBlueprintConnectionName: 'altBlueprintConnectionName',
58
+ WIDAssertionFile: 'WIDAssertionFile',
59
+ azureRegion: 'azureRegion',
60
+ sendX5C: 'sendX5C',
61
+ msalRetryCount: 'msalRetryCount',
62
+ authType: 'authType',
63
+ federatedTokenFile: 'federatedTokenFile',
64
+ idpmResource: 'idpmResource',
65
+ sidecarBaseUrl: 'sidecarBaseUrl',
66
+ serviceName: 'serviceName',
67
+ blueprintServiceName: 'blueprintServiceName',
68
+ bypassLocalNetworkRestriction: 'bypassLocalNetworkRestriction',
69
+ requestTimeout: 'requestTimeout',
70
+ retryCount: 'retryCount',
71
+ validateIssuer: 'validateIssuer'
72
+ } as const
73
+
74
+ const legacyAuthorizationProperties = {
75
+ type: 'type',
76
+ connectionName: 'azureBotOAuthConnectionName',
77
+ connectionTitle: 'title',
78
+ connectionText: 'text',
79
+ maxAttempts: 'invalidSignInRetryMax',
80
+ messages_invalidCode: 'invalidSignInRetryMessage',
81
+ messages_invalidCodeFormat: 'invalidSignInRetryMessageFormat',
82
+ messages_maxAttemptsExceeded: 'invalidSignInRetryMaxExceededMessage',
83
+ obo_connection: 'oboConnectionName',
84
+ obo_scopes: 'oboScopes',
85
+ enableSso: 'enableSso',
86
+ scopes: 'scopes',
87
+ altBlueprintConnectionName: 'altBlueprintConnectionName'
88
+ } as const
89
+
90
+ interface BotFrameworkAuthorizationCompatibility {
91
+ readonly layer: ConfigurationLayer
92
+ readonly replacements: readonly Readonly<{
93
+ legacyKey: string
94
+ modernKey: string
95
+ }>[]
96
+ }
97
+
98
+ /** Binds the flat Bot Framework single-connection environment variables. */
99
+ export function loadBotFrameworkEnvironmentConfiguration (
100
+ env: NodeJS.ProcessEnv = process.env
101
+ ): ConfigurationLayer {
102
+ return bindConnectionCompatibility(env, undefined, flatConnectionProperties)
103
+ }
104
+
105
+ /** Binds the Bot Framework `<connectionName>_<property>` environment form. */
106
+ export function loadBotFrameworkPrefixedEnvironmentConfiguration (
107
+ connectionName: string,
108
+ env: NodeJS.ProcessEnv = process.env
109
+ ): ConfigurationLayer {
110
+ return bindConnectionCompatibility(env, connectionName, prefixedConnectionProperties)
111
+ }
112
+
113
+ /**
114
+ * Binds Bot Framework-era `<handlerId>_<property>` authorization variables.
115
+ * Only known runtime/external handler IDs participate, matching existing
116
+ * compatibility behavior.
117
+ */
118
+ export function loadBotFrameworkAuthorizationEnvironmentConfiguration (
119
+ handlerIds: readonly string[],
120
+ env: NodeJS.ProcessEnv = process.env
121
+ ): BotFrameworkAuthorizationCompatibility {
122
+ const layer = createConfigurationLayer()
123
+ const replacements: Array<{ legacyKey: string, modernKey: string }> = []
124
+ const longestHandlerIdsFirst = [...handlerIds].sort((left, right) => right.length - left.length)
125
+
126
+ for (const [legacyKey, rawValue] of Object.entries(env)) {
127
+ if (!rawValue?.trim()) {
128
+ continue
129
+ }
130
+ const id = longestHandlerIdsFirst.find(candidate =>
131
+ legacyKey.toLowerCase().startsWith(`${candidate.toLowerCase()}_`)
132
+ )
133
+ if (!id) {
134
+ continue
135
+ }
136
+
137
+ const legacyProperty = legacyKey.substring(id.length + 1)
138
+ const property = findProperty(legacyAuthorizationProperties, legacyProperty)
139
+ if (!property) {
140
+ continue
141
+ }
142
+
143
+ const value = normalizeAuthorizationValue(property, rawValue)
144
+ if (!trySet(
145
+ layer,
146
+ `agentApplication.userAuthorization.handlers.${id}.settings.${property}`,
147
+ value,
148
+ legacyKey
149
+ )) {
150
+ continue
151
+ }
152
+
153
+ replacements.push({
154
+ legacyKey,
155
+ modernKey: `AgentApplication__UserAuthorization__Handlers__${id}__Settings__${property}`
156
+ })
157
+ }
158
+
159
+ return {
160
+ layer: freezeConfigurationLayer(layer),
161
+ replacements: Object.freeze(replacements.map(replacement => Object.freeze(replacement)))
162
+ }
163
+ }
164
+
165
+ function bindConnectionCompatibility (
166
+ env: NodeJS.ProcessEnv,
167
+ connectionName: string | undefined,
168
+ properties: Readonly<Record<string, string>>
169
+ ): ConfigurationLayer {
170
+ const layer = createConfigurationLayer()
171
+ const id = connectionName || DEFAULT_CONNECTION_ID
172
+ const indexedEnvironment = loadEnvSettings(() => {}, env)
173
+
174
+ for (const [legacyProperty, property] of Object.entries(properties)) {
175
+ const key = connectionName ? `${connectionName}_${legacyProperty}` : legacyProperty
176
+ const value = findEnvironmentValue(indexedEnvironment, key)
177
+ if (!value?.trim()) {
178
+ continue
179
+ }
180
+ trySet(
181
+ layer,
182
+ `connections.${id}.settings.${property}`,
183
+ normalizeConnectionValue(property, value),
184
+ key,
185
+ property === 'validateIssuer'
186
+ )
187
+ }
188
+
189
+ return freezeConfigurationLayer(layer)
190
+ }
191
+
192
+ function findEnvironmentValue (env: LoadEnv, key: string): string | undefined {
193
+ return env[key.toUpperCase()]?.value
194
+ }
195
+
196
+ function findProperty (
197
+ properties: Readonly<Record<string, string>>,
198
+ input: string
199
+ ): string | undefined {
200
+ const key = Object.keys(properties).find(candidate => candidate.toLowerCase() === input.toLowerCase())
201
+ return key ? properties[key] : undefined
202
+ }
203
+
204
+ function normalizeConnectionValue (property: string, value: string): string {
205
+ if (property === 'sendX5C' || property === 'bypassLocalNetworkRestriction') {
206
+ return String(value === 'true')
207
+ }
208
+ if (property === 'msalRetryCount' || property === 'requestTimeout' || property === 'retryCount') {
209
+ const parsed = Number.parseInt(value, 10)
210
+ return Number.isFinite(parsed) ? String(parsed) : value
211
+ }
212
+ return value
213
+ }
214
+
215
+ function normalizeAuthorizationValue (property: string, value: string): string {
216
+ if (property === 'enableSso') {
217
+ return String(value !== 'false')
218
+ }
219
+ if (property === 'invalidSignInRetryMax') {
220
+ const parsed = Number.parseInt(value)
221
+ return Number.isFinite(parsed) ? String(parsed) : value
222
+ }
223
+ return value
224
+ }
225
+
226
+ function trySet (
227
+ layer: ConfigurationLayer,
228
+ path: string,
229
+ value: string,
230
+ sourceName: string,
231
+ throwOnInvalidValue = false
232
+ ): boolean {
233
+ try {
234
+ setConfigurationValue(layer, path, value, sourceName, 'environment')
235
+ return true
236
+ } catch (error) {
237
+ if (!isConfigurationInputError(error)) {
238
+ throw error
239
+ }
240
+ if (throwOnInvalidValue && isInvalidConfigurationValueError(error)) {
241
+ throw error
242
+ }
243
+ return false
244
+ }
245
+ }