@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,76 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /** A value accepted in a hierarchical configuration document. */
7
+ export type ConfigurationDocumentValue =
8
+ | string
9
+ | number
10
+ | boolean
11
+ | readonly ConfigurationDocumentValue[]
12
+ | ConfigurationDocument
13
+
14
+ /** Provider-neutral hierarchical configuration data. */
15
+ export interface ConfigurationDocument {
16
+ readonly [key: string]: ConfigurationDocumentValue
17
+ }
18
+
19
+ /** Values returned by a pluggable configuration source. */
20
+ export type ConfigurationSourceResult =
21
+ | Readonly<Record<string, string>>
22
+ | {
23
+ /** Canonical Agents SDK paths with raw string values. */
24
+ readonly format: 'canonical'
25
+ readonly values: Readonly<Record<string, string>>
26
+ }
27
+ | {
28
+ /** A hierarchical document using the shared Agents SDK schema. */
29
+ readonly format: 'document'
30
+ readonly value: Readonly<ConfigurationDocument>
31
+ }
32
+
33
+ /**
34
+ * Supplies canonical or hierarchical configuration values.
35
+ *
36
+ * @remarks
37
+ * Sources are loaded asynchronously by {@link createConfigurationContext} or
38
+ * {@link preloadConfigurationSources}. Values are cached in an immutable
39
+ * host-scoped or process-level snapshot so existing synchronous hosting APIs
40
+ * can consume them during startup.
41
+ */
42
+ export interface ConfigurationSource {
43
+ /**
44
+ * A unique name used to identify the source in diagnostics.
45
+ */
46
+ readonly name: string
47
+
48
+ /**
49
+ * Loads configuration data. A bare canonical record remains supported for
50
+ * compatibility; new sources should identify their representation explicitly.
51
+ */
52
+ load(): Promise<ConfigurationSourceResult>
53
+ }
54
+
55
+ /**
56
+ * Selects how an external source participates in configuration resolution.
57
+ */
58
+ export type ConfigurationSourceMode =
59
+ | 'fallback'
60
+ | 'overrideEnvironment'
61
+ | 'enforce'
62
+
63
+ /**
64
+ * Registers a configuration source with an explicit resolution mode.
65
+ */
66
+ export interface ConfigurationSourceRegistration {
67
+ /**
68
+ * The source to preload.
69
+ */
70
+ readonly source: ConfigurationSource
71
+
72
+ /**
73
+ * The source's resolution mode.
74
+ */
75
+ readonly mode: ConfigurationSourceMode
76
+ }
@@ -0,0 +1,377 @@
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
+ suggestConfigurationPath
14
+ } from './configuration'
15
+ import { debug } from '@microsoft/agents-telemetry'
16
+ import {
17
+ envParser,
18
+ envParserUtils,
19
+ parseBooleanEnv,
20
+ suggestClosest
21
+ } from '../utils/env'
22
+ import { ConnectionKeys, ConnectionMapKeys } from '../auth/settings'
23
+
24
+ const CONNECTIONS = 'CONNECTIONS'
25
+ const CONNECTIONS_MAP = 'CONNECTIONSMAP'
26
+ const CLOUD_ADAPTER_OPTIONS = 'CLOUDADAPTEROPTIONS'
27
+ const OUTBOUND_HOST_VALIDATOR = 'OUTBOUNDHOSTVALIDATOR'
28
+ const AGENT_APPLICATION = 'AGENTAPPLICATION'
29
+ const CLOUD_ADAPTER_OPTIONS_ENV_PREFIX = 'CloudAdapterOptions__'
30
+ const warnedCloudAdapterKeys = new Set<string>()
31
+ const cloudAdapterLogger = debug('agents:cloud-adapter')
32
+ const authLogger = debug('agents:authConfiguration')
33
+ const connectionMapParser = envParser<ConnectionMapKeys>({
34
+ serviceUrl: envParserUtils.bypass,
35
+ audience: envParserUtils.bypass,
36
+ connection: envParserUtils.bypass
37
+ })
38
+ const cloudAdapterOptionsParser = envParser<'emitStackTrace' | 'validateServiceUrl'>({
39
+ emitStackTrace: envParserUtils.bypass,
40
+ validateServiceUrl: envParserUtils.bypass
41
+ })
42
+
43
+ interface ModernEnvironmentOptions {
44
+ readonly reportCloudAdapterDiagnostics?: boolean
45
+ }
46
+
47
+ /**
48
+ * Binds the supported schema-shaped `__` environment variables to the
49
+ * canonical hierarchical configuration model.
50
+ */
51
+ export function loadModernEnvironmentConfiguration (
52
+ env: NodeJS.ProcessEnv = process.env,
53
+ options: ModernEnvironmentOptions = {}
54
+ ): ConfigurationLayer {
55
+ const layer = createConfigurationLayer()
56
+ const indexedHosts = new Map<number, string>()
57
+
58
+ for (const [key, value] of Object.entries(env)) {
59
+ if (!value?.trim()) {
60
+ continue
61
+ }
62
+
63
+ const parts = key.split('__')
64
+ const root = parts[0]?.toUpperCase()
65
+ if (root === CONNECTIONS) {
66
+ bindConnection(layer, parts, value, key, options.reportCloudAdapterDiagnostics === true)
67
+ } else if (root === CONNECTIONS_MAP) {
68
+ bindConnectionMap(layer, parts, value, key, options.reportCloudAdapterDiagnostics === true)
69
+ } else if (root === CLOUD_ADAPTER_OPTIONS) {
70
+ bindCloudAdapterOptions(layer, parts, value, key, options.reportCloudAdapterDiagnostics === true)
71
+ } else if (root === OUTBOUND_HOST_VALIDATOR) {
72
+ bindOutboundHostValidator(
73
+ layer,
74
+ parts,
75
+ value,
76
+ key,
77
+ indexedHosts,
78
+ options.reportCloudAdapterDiagnostics === true
79
+ )
80
+ } else if (root === AGENT_APPLICATION) {
81
+ bindAuthorizationHandler(layer, parts, value, key, options.reportCloudAdapterDiagnostics === true)
82
+ } else if (options.reportCloudAdapterDiagnostics === true) {
83
+ const canonicalRoot = suggestConfigurationPath(parts[0] ?? '')
84
+ const suggestedRoot = canonicalRoot
85
+ ? canonicalRoot.charAt(0).toUpperCase() + canonicalRoot.slice(1)
86
+ : undefined
87
+ if (suggestedRoot) {
88
+ emitHierarchySuggestion(key, [suggestedRoot, ...parts.slice(1)].join('__'), true)
89
+ }
90
+ }
91
+ }
92
+
93
+ if (indexedHosts.size > 0) {
94
+ const configured = [...layer.outboundHostValidator.hosts ?? []]
95
+ for (const [, host] of [...indexedHosts].sort(([left], [right]) => left - right)) {
96
+ configured.push(host)
97
+ }
98
+ ;(layer.outboundHostValidator as { hosts?: readonly string[] }).hosts = configured
99
+ }
100
+
101
+ return freezeConfigurationLayer(layer)
102
+ }
103
+
104
+ function bindConnection (
105
+ layer: ConfigurationLayer,
106
+ parts: string[],
107
+ value: string,
108
+ sourceName: string,
109
+ reportDiagnostics: boolean
110
+ ): void {
111
+ const format = 'Connections__<id>__Settings__<property>'
112
+ if (parts[2]?.toUpperCase() !== 'SETTINGS') {
113
+ if (reportDiagnostics && suggestClosest(parts[2] ?? '', ['Settings'], 4)) {
114
+ emitHierarchySuggestion(sourceName, [parts[0], parts[1], 'Settings', ...parts.slice(3)].join('__'), true)
115
+ }
116
+ return
117
+ }
118
+ if (parts.length !== 4) {
119
+ authLogger.warn(`Invalid connection environment variable: ${sourceName}. Expected format: ${format}.`)
120
+ return
121
+ }
122
+ if (!parts[1]?.trim()) {
123
+ authLogger.warn(`Invalid connection <id> in environment variable: ${sourceName}. Expected format: ${format}.`)
124
+ return
125
+ }
126
+ if (!parts[3]?.trim()) {
127
+ authLogger.warn(`Invalid connection <property> in environment variable: ${sourceName}. Expected format: ${format}.`)
128
+ return
129
+ }
130
+
131
+ const property = parts[3] as ConnectionKeys
132
+ const lookup = property.toLowerCase()
133
+ if (lookup === 'ficclientid') {
134
+ authLogger.warn('Connections__<id>__Settings__FICClientId is deprecated, please use Connections__<id>__Settings__FederatedClientId instead.')
135
+ } else if (lookup === 'authority') {
136
+ authLogger.warn('Connections__<id>__Settings__Authority is deprecated, please use Connections__<id>__Settings__AuthorityEndpoint instead.')
137
+ } else if (lookup === 'scope') {
138
+ authLogger.warn('Connections__<id>__Settings__Scope is deprecated, please use Connections__<id>__Settings__Scopes instead.')
139
+ } else if (lookup !== 'alternateblueprintconnectionname' && reportDiagnostics) {
140
+ const suggestedProperty = suggestedLeaf(
141
+ `connections.${parts[1]}.settings.${property}`
142
+ )
143
+ if (suggestedProperty) {
144
+ emitHierarchySuggestion(
145
+ sourceName,
146
+ [parts[0], parts[1], parts[2], suggestedProperty].join('__'),
147
+ false
148
+ )
149
+ }
150
+ }
151
+ trySet(
152
+ layer,
153
+ `connections.${parts[1]}.settings.${property}`,
154
+ normalizeConnectionEnvironmentValue(property, value),
155
+ sourceName,
156
+ lookup === 'validateissuer'
157
+ )
158
+ }
159
+
160
+ function bindConnectionMap (
161
+ layer: ConfigurationLayer,
162
+ parts: string[],
163
+ value: string,
164
+ sourceName: string,
165
+ reportDiagnostics: boolean
166
+ ): void {
167
+ const format = 'ConnectionsMap__<index>__<property>'
168
+ if (parts.length !== 3) {
169
+ authLogger.warn(`Invalid connection map environment variable: ${sourceName}. Expected format: ${format}.`)
170
+ return
171
+ }
172
+ if (!/^\d+$/.test(parts[1])) {
173
+ authLogger.warn(`Invalid connection map <index> in environment variable: ${sourceName}. Expected format: ${format}, where <index> is a number.`)
174
+ return
175
+ }
176
+ if (!parts[2]?.trim()) {
177
+ authLogger.warn(`Invalid connection map <property> in environment variable: ${sourceName}. Expected format: ${format}.`)
178
+ return
179
+ }
180
+ const parsed = connectionMapParser.parse(parts[2] as ConnectionMapKeys, value)
181
+ if (parsed.key) {
182
+ trySet(layer, `connectionsMap.${parts[1]}.${parsed.key}`, parsed.value, sourceName)
183
+ } else if (reportDiagnostics) {
184
+ const suggestedProperty = suggestedLeaf(`connectionsMap.${parts[1]}.${parts[2]}`)
185
+ if (suggestedProperty) {
186
+ emitHierarchySuggestion(
187
+ sourceName,
188
+ [parts[0], parts[1], suggestedProperty].join('__'),
189
+ true
190
+ )
191
+ }
192
+ }
193
+ }
194
+
195
+ function bindCloudAdapterOptions (
196
+ layer: ConfigurationLayer,
197
+ parts: string[],
198
+ value: string,
199
+ sourceName: string,
200
+ reportDiagnostics: boolean
201
+ ): void {
202
+ if (parts.length !== 2 || !parts[1]?.trim()) {
203
+ return
204
+ }
205
+ const property = parts[1]
206
+ const parsed = cloudAdapterOptionsParser.parse(
207
+ property as Parameters<typeof cloudAdapterOptionsParser.parse>[0],
208
+ value
209
+ )
210
+ if (!parsed.key) {
211
+ if (reportDiagnostics) {
212
+ const suggestion = suggestClosest(property, cloudAdapterOptionsParser.keys, 4)
213
+ const hint = suggestion ? ` Did you mean "${CLOUD_ADAPTER_OPTIONS_ENV_PREFIX}${suggestion}"?` : ''
214
+ emitCloudAdapterWarning(sourceName, `Unknown CloudAdapterOptions env var: ${sourceName} (ignored).${hint}`)
215
+ }
216
+ return
217
+ }
218
+ if (parseBooleanEnv(parsed.value) === undefined) {
219
+ if (reportDiagnostics) {
220
+ emitCloudAdapterWarning(
221
+ `${sourceName}=${value}`,
222
+ `Ignored ${sourceName}=${value}; expected one of true/false/1/0.`
223
+ )
224
+ }
225
+ return
226
+ }
227
+ trySet(layer, `cloudAdapterOptions.${parsed.key}`, parsed.value, sourceName)
228
+ }
229
+
230
+ function bindOutboundHostValidator (
231
+ layer: ConfigurationLayer,
232
+ parts: string[],
233
+ value: string,
234
+ sourceName: string,
235
+ indexedHosts: Map<number, string>,
236
+ reportDiagnostics: boolean
237
+ ): void {
238
+ if (parts.length === 3 && /^\d+$/.test(parts[2])) {
239
+ if (parts[1]?.toUpperCase() === 'HOSTS') {
240
+ indexedHosts.set(Number.parseInt(parts[2], 10), value.trim())
241
+ } else if (reportDiagnostics && suggestClosest(parts[1] ?? '', ['Hosts'], 4)) {
242
+ emitHierarchySuggestion(
243
+ sourceName,
244
+ [parts[0], 'Hosts', parts[2]].join('__'),
245
+ true
246
+ )
247
+ }
248
+ return
249
+ }
250
+ if (parts.length !== 2 || !parts[1]?.trim()) {
251
+ return
252
+ }
253
+ if (reportDiagnostics) {
254
+ const suggestedProperty = suggestedLeaf(`outboundHostValidator.${parts[1]}`)
255
+ if (suggestedProperty) {
256
+ emitHierarchySuggestion(sourceName, [parts[0], suggestedProperty].join('__'), true)
257
+ }
258
+ }
259
+ trySet(layer, `outboundHostValidator.${parts[1]}`, value, sourceName)
260
+ }
261
+
262
+ function bindAuthorizationHandler (
263
+ layer: ConfigurationLayer,
264
+ parts: string[],
265
+ value: string,
266
+ sourceName: string,
267
+ reportDiagnostics: boolean
268
+ ): void {
269
+ const settingsIndex = parts.length - 2
270
+ const userAuthorization = suggestClosest(parts[1] ?? '', ['UserAuthorization'], 4)
271
+ const handlers = suggestClosest(parts[2] ?? '', ['Handlers'], 4)
272
+ const settings = suggestClosest(parts[settingsIndex] ?? '', ['Settings'], 4)
273
+ const handlerId = parts.slice(3, settingsIndex).join('__')
274
+ const property = parts.at(-1) ?? ''
275
+ if (parts.length < 6 ||
276
+ parts[1]?.toUpperCase() !== 'USERAUTHORIZATION' ||
277
+ parts[2]?.toUpperCase() !== 'HANDLERS' ||
278
+ parts[settingsIndex]?.toUpperCase() !== 'SETTINGS' ||
279
+ !handlerId.trim() ||
280
+ !property.trim()) {
281
+ if (reportDiagnostics && parts.length >= 6 && userAuthorization && handlers && settings) {
282
+ emitHierarchySuggestion(
283
+ sourceName,
284
+ [parts[0], userAuthorization, handlers, ...parts.slice(3, settingsIndex), settings, property].join('__'),
285
+ true
286
+ )
287
+ }
288
+ return
289
+ }
290
+
291
+ if (reportDiagnostics && property.toLowerCase() !== 'alternateblueprintconnectionname') {
292
+ const suggestedProperty = suggestedLeaf(
293
+ `agentApplication.userAuthorization.handlers.${handlerId}.settings.${property}`
294
+ )
295
+ if (suggestedProperty) {
296
+ emitHierarchySuggestion(
297
+ sourceName,
298
+ [parts[0], parts[1], parts[2], ...parts.slice(3, settingsIndex), parts[settingsIndex], suggestedProperty].join('__'),
299
+ false
300
+ )
301
+ }
302
+ }
303
+ trySet(
304
+ layer,
305
+ `agentApplication.userAuthorization.handlers.${handlerId}.settings.${property}`,
306
+ normalizeAuthorizationEnvironmentValue(property, value),
307
+ sourceName
308
+ )
309
+ }
310
+
311
+ function normalizeConnectionEnvironmentValue (property: ConnectionKeys, value: string): string {
312
+ const lookup = property.toLowerCase()
313
+ if (lookup === 'sendx5c' || lookup === 'bypasslocalnetworkrestriction') {
314
+ return String(value === 'true')
315
+ }
316
+ if (lookup === 'msalretrycount' || lookup === 'requesttimeout' || lookup === 'retrycount') {
317
+ const parsed = Number.parseInt(value, 10)
318
+ return Number.isFinite(parsed) ? String(parsed) : value
319
+ }
320
+ return value
321
+ }
322
+
323
+ function normalizeAuthorizationEnvironmentValue (property: string, value: string): string {
324
+ const lookup = property.toLowerCase()
325
+ if (lookup === 'enablesso') {
326
+ return String(value !== 'false')
327
+ }
328
+ if (lookup === 'invalidsigninretrymax') {
329
+ const parsed = Number.parseInt(value)
330
+ return Number.isFinite(parsed) ? String(parsed) : value
331
+ }
332
+ return value
333
+ }
334
+
335
+ function trySet (
336
+ layer: ConfigurationLayer,
337
+ path: string,
338
+ value: string,
339
+ sourceName: string,
340
+ throwOnInvalidValue = false
341
+ ): void {
342
+ try {
343
+ setConfigurationValue(layer, path, value, sourceName, 'environment')
344
+ } catch (error) {
345
+ if (!isConfigurationInputError(error)) {
346
+ throw error
347
+ }
348
+ if (throwOnInvalidValue && isInvalidConfigurationValueError(error)) {
349
+ throw error
350
+ }
351
+ // Existing environment binders ignore unknown and invalid values.
352
+ }
353
+ }
354
+
355
+ function emitCloudAdapterWarning (key: string, message: string): void {
356
+ if (warnedCloudAdapterKeys.has(key)) {
357
+ return
358
+ }
359
+ warnedCloudAdapterKeys.add(key)
360
+ console.warn(`[agents:cloud-adapter] ${message}`)
361
+ cloudAdapterLogger.warn(message)
362
+ }
363
+
364
+ function suggestedLeaf (canonicalPath: string): string | undefined {
365
+ return suggestConfigurationPath(canonicalPath)?.split('.').at(-1)
366
+ }
367
+
368
+ function emitHierarchySuggestion (
369
+ key: string,
370
+ suggestion: string,
371
+ ignored: boolean
372
+ ): void {
373
+ emitCloudAdapterWarning(
374
+ key,
375
+ `${ignored ? 'Ignored unknown' : 'Unrecognized'} configuration environment variable: ${key}. Did you mean "${suggestion}"?`
376
+ )
377
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ export {
7
+ ConfigurationContext,
8
+ createConfigurationContext,
9
+ preloadConfigurationSources
10
+ } from './configuration'
11
+ export { createJsonFileConfigurationSource } from './jsonFileConfigurationSource'
12
+ export type {
13
+ ConfigurationDocument,
14
+ ConfigurationDocumentValue,
15
+ ConfigurationSource,
16
+ ConfigurationSourceMode,
17
+ ConfigurationSourceRegistration,
18
+ ConfigurationSourceResult
19
+ } from './configurationSource'
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ExceptionHelper } from '@microsoft/agents-activity'
7
+ import { readFile } from 'node:fs/promises'
8
+ import { Errors } from '../errorHelper'
9
+ import { ConfigurationDocument, ConfigurationSource } from './configurationSource'
10
+
11
+ const safeErrors = new WeakSet<Error>()
12
+
13
+ /**
14
+ * Creates a configuration source that loads a hierarchical Agents SDK
15
+ * configuration document from a JSON file.
16
+ *
17
+ * @param filePath Path to the JSON configuration file.
18
+ * @returns A configuration source for use with `createConfigurationContext`
19
+ * or `preloadConfigurationSources`.
20
+ */
21
+ export function createJsonFileConfigurationSource (filePath: string): ConfigurationSource {
22
+ return {
23
+ name: filePath,
24
+ async load () {
25
+ let contents: string
26
+ try {
27
+ contents = await readFile(filePath, 'utf8')
28
+ } catch {
29
+ const error = ExceptionHelper.generateException(
30
+ Error,
31
+ Errors.JsonConfigurationFileReadFailed,
32
+ undefined,
33
+ { filePath }
34
+ )
35
+ safeErrors.add(error)
36
+ throw error
37
+ }
38
+
39
+ let value: unknown
40
+ try {
41
+ value = JSON.parse(contents)
42
+ } catch {
43
+ const error = ExceptionHelper.generateException(
44
+ SyntaxError,
45
+ Errors.InvalidJsonConfigurationFile,
46
+ undefined,
47
+ { filePath }
48
+ )
49
+ safeErrors.add(error)
50
+ throw error
51
+ }
52
+
53
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
54
+ const error = ExceptionHelper.generateException(
55
+ TypeError,
56
+ Errors.JsonConfigurationDocumentRequired,
57
+ undefined,
58
+ { filePath }
59
+ )
60
+ safeErrors.add(error)
61
+ throw error
62
+ }
63
+
64
+ return {
65
+ format: 'document',
66
+ value: value as ConfigurationDocument
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ export function isSafeJsonFileConfigurationError (error: unknown): error is Error {
73
+ return error instanceof Error && safeErrors.has(error)
74
+ }
@@ -7,6 +7,7 @@ import { ActivityHandler } from './activityHandler'
7
7
  import { AgentApplication } from './app/agentApplication'
8
8
  import { AuthConfiguration } from './auth/authConfiguration'
9
9
  import { CloudAdapter } from './cloudAdapter'
10
+ import { ConfigurationContext } from './configuration/configuration'
10
11
  import { HeaderPropagationDefinition } from './headerPropagation'
11
12
  import { TurnState } from './app/turnState'
12
13
 
@@ -18,15 +19,40 @@ export interface CloudAdapterResult {
18
19
  headerPropagation: HeaderPropagationDefinition | undefined
19
20
  }
20
21
 
22
+ /**
23
+ * Options accepted by {@link createCloudAdapter} when creating a new `CloudAdapter` for an agent
24
+ * that does not already own one.
25
+ *
26
+ * @remarks
27
+ * This is the single named options type shared by the `@microsoft/agents-hosting-express` and
28
+ * `@microsoft/agents-hosting-fastify` convenience APIs (`createAgentRequestHandler`,
29
+ * `startServer`, and the Fastify plugin) so every hosting entry point resolves auth and
30
+ * constructs a `CloudAdapter` through the same path.
31
+ */
32
+ export interface CreateCloudAdapterOptions {
33
+ /**
34
+ * Optional host-scoped external configuration used to resolve the authentication
35
+ * configuration and runtime options of a newly created `CloudAdapter` (e.g. `emitStackTrace`,
36
+ * `validateServiceUrl`, outbound host validation).
37
+ *
38
+ * For an `AgentApplication`, this defaults to `agent.options.configurationContext` when
39
+ * omitted. A plain `ActivityHandler` has no built-in context, so it must be supplied here to
40
+ * participate in host-scoped configuration.
41
+ */
42
+ configurationContext?: ConfigurationContext
43
+ }
44
+
21
45
  /**
22
46
  * Creates a CloudAdapter for the given agent.
23
47
  *
24
- * If the agent is an AgentApplication with a pre-configured adapter, that adapter is reused.
25
- * Otherwise, a new CloudAdapter is created.
48
+ * An `AgentApplication`'s pre-configured adapter is always reused when available, preserving
49
+ * its identity, middleware, connection manager, and runtime policy. The auth configuration is
50
+ * used only when a new adapter must be created.
26
51
  *
27
52
  * @param agent - The AgentApplication or ActivityHandler instance.
28
- * @param authConfig - Optional auth configuration used when creating a new CloudAdapter.
29
- * If the agent already has an adapter, that adapter is reused and this value is ignored.
53
+ * @param authConfig - Optional auth configuration used when creating a new adapter. If the
54
+ * application already owns an adapter, this value does not replace or reconfigure it.
55
+ * @param options - Optional additional settings, such as a host-scoped {@link ConfigurationContext}.
30
56
  * @returns An object containing the CloudAdapter and optional header propagation configuration.
31
57
  *
32
58
  * @example
@@ -42,15 +68,32 @@ export interface CloudAdapterResult {
42
68
  */
43
69
  export const createCloudAdapter = (
44
70
  agent: AgentApplication<TurnState<any, any>> | ActivityHandler,
45
- authConfig?: AuthConfiguration
71
+ authConfig?: AuthConfiguration,
72
+ options?: CreateCloudAdapterOptions
46
73
  ): CloudAdapterResult => {
74
+ const configurationContext = options?.configurationContext ??
75
+ (agent instanceof AgentApplication ? agent.options.configurationContext : undefined)
76
+
47
77
  if (agent instanceof ActivityHandler) {
48
- return { adapter: new CloudAdapter(authConfig), headerPropagation: undefined }
78
+ return {
79
+ adapter: new CloudAdapter(
80
+ authConfig,
81
+ undefined,
82
+ undefined,
83
+ { configurationContext }
84
+ ),
85
+ headerPropagation: undefined
86
+ }
49
87
  }
50
88
 
51
- // AgentApplication: reuse its pre-configured adapter when present, otherwise create one.
52
- // headerPropagation is an application-level option, so read it regardless of adapter source.
89
+ // Preserve the application-owned processing adapter regardless of host authorization settings.
90
+ // Hosting integrations perform their independent inbound authorization stage before processing.
53
91
  const headerPropagation = agent.options?.headerPropagation
54
- const adapter = agent.adapter ?? new CloudAdapter(authConfig)
92
+ const adapter = agent.adapter ?? new CloudAdapter(
93
+ authConfig,
94
+ undefined,
95
+ undefined,
96
+ { configurationContext }
97
+ )
55
98
  return { adapter, headerPropagation }
56
99
  }