@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.
- package/README.md +182 -4
- package/dist/package.json +3 -3
- package/dist/src/app/agentApplication.d.ts +4 -3
- package/dist/src/app/agentApplication.js +24 -21
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/agentApplicationBuilder.d.ts +9 -2
- package/dist/src/app/agentApplicationBuilder.js +9 -0
- package/dist/src/app/agentApplicationBuilder.js.map +1 -1
- package/dist/src/app/agentApplicationOptions.d.ts +16 -4
- package/dist/src/app/auth/authorization.d.ts +54 -0
- package/dist/src/app/auth/authorization.js +63 -2
- package/dist/src/app/auth/authorization.js.map +1 -1
- package/dist/src/app/auth/authorizationManager.d.ts +0 -8
- package/dist/src/app/auth/authorizationManager.js +104 -178
- package/dist/src/app/auth/authorizationManager.js.map +1 -1
- package/dist/src/app/auth/handlerStorage.d.ts +5 -3
- package/dist/src/app/auth/handlerStorage.js +36 -10
- package/dist/src/app/auth/handlerStorage.js.map +1 -1
- package/dist/src/app/auth/types.d.ts +2 -2
- package/dist/src/app/index.d.ts +1 -0
- package/dist/src/app/proactive/proactive.js +10 -6
- package/dist/src/app/proactive/proactive.js.map +1 -1
- package/dist/src/app/proactive/proactiveOptions.d.ts +2 -2
- package/dist/src/app/rateLimit.d.ts +315 -0
- package/dist/src/app/rateLimit.js +333 -0
- package/dist/src/app/rateLimit.js.map +1 -0
- package/dist/src/app/turnState.d.ts +6 -3
- package/dist/src/app/turnState.js +43 -4
- package/dist/src/app/turnState.js.map +1 -1
- package/dist/src/auth/authConfiguration.d.ts +10 -3
- package/dist/src/auth/authConfiguration.js +292 -259
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/delegatedTokenCredential.d.ts +29 -0
- package/dist/src/auth/delegatedTokenCredential.js +49 -0
- package/dist/src/auth/delegatedTokenCredential.js.map +1 -0
- package/dist/src/auth/msal/msalTokenCredential.js +1 -1
- package/dist/src/auth/msal/msalTokenCredential.js.map +1 -1
- package/dist/src/auth/settings.d.ts +8 -70
- package/dist/src/auth/settings.js +5 -67
- package/dist/src/auth/settings.js.map +1 -1
- package/dist/src/cloudAdapter.d.ts +5 -0
- package/dist/src/cloudAdapter.js +13 -103
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.d.ts +23 -0
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js +183 -0
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js.map +1 -0
- package/dist/src/configuration/configuration.d.ts +83 -0
- package/dist/src/configuration/configuration.js +836 -0
- package/dist/src/configuration/configuration.js.map +1 -0
- package/dist/src/configuration/configurationSource.d.ts +57 -0
- package/dist/src/configuration/configurationSource.js +7 -0
- package/dist/src/configuration/configurationSource.js.map +1 -0
- package/dist/src/configuration/environmentConfiguration.d.ts +14 -0
- package/dist/src/configuration/environmentConfiguration.js +271 -0
- package/dist/src/configuration/environmentConfiguration.js.map +1 -0
- package/dist/src/configuration/index.d.ts +7 -0
- package/dist/src/configuration/index.js +14 -0
- package/dist/src/configuration/index.js.map +1 -0
- package/dist/src/configuration/jsonFileConfigurationSource.d.ts +15 -0
- package/dist/src/configuration/jsonFileConfigurationSource.js +58 -0
- package/dist/src/configuration/jsonFileConfigurationSource.js.map +1 -0
- package/dist/src/createCloudAdapter.d.ts +30 -5
- package/dist/src/createCloudAdapter.js +17 -10
- package/dist/src/createCloudAdapter.js.map +1 -1
- package/dist/src/errorHelper.js +124 -5
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/observability/traces.d.ts +6 -4
- package/dist/src/observability/traces.js +10 -9
- package/dist/src/observability/traces.js.map +1 -1
- package/dist/src/outboundHostValidator.d.ts +4 -1
- package/dist/src/outboundHostValidator.js +25 -48
- package/dist/src/outboundHostValidator.js.map +1 -1
- package/dist/src/state/agentState.d.ts +8 -3
- package/dist/src/state/agentState.js +26 -8
- package/dist/src/state/agentState.js.map +1 -1
- package/dist/src/state/conversationState.d.ts +2 -2
- package/dist/src/state/conversationState.js.map +1 -1
- package/dist/src/state/userState.d.ts +2 -2
- package/dist/src/state/userState.js.map +1 -1
- package/dist/src/statusCodes.d.ts +5 -0
- package/dist/src/statusCodes.js +5 -0
- package/dist/src/statusCodes.js.map +1 -1
- package/dist/src/storage/fileStorage.d.ts +83 -68
- package/dist/src/storage/fileStorage.js +279 -105
- package/dist/src/storage/fileStorage.js.map +1 -1
- package/dist/src/storage/index.d.ts +1 -0
- package/dist/src/storage/index.js +1 -0
- package/dist/src/storage/index.js.map +1 -1
- package/dist/src/storage/memoryStorage.d.ts +85 -60
- package/dist/src/storage/memoryStorage.js +244 -87
- package/dist/src/storage/memoryStorage.js.map +1 -1
- package/dist/src/storage/storage.d.ts +100 -2
- package/dist/src/storage/storage.js +25 -0
- package/dist/src/storage/storage.js.map +1 -1
- package/dist/src/storage/storageCompatibility.d.ts +17 -0
- package/dist/src/storage/storageCompatibility.js +157 -0
- package/dist/src/storage/storageCompatibility.js.map +1 -0
- package/dist/src/storage/storageExpiry.d.ts +6 -0
- package/dist/src/storage/storageExpiry.js +20 -0
- package/dist/src/storage/storageExpiry.js.map +1 -0
- package/dist/src/utils/env.d.ts +43 -0
- package/dist/src/utils/env.js +42 -6
- package/dist/src/utils/env.js.map +1 -1
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/utils.js +10 -1
- package/dist/src/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/app/agentApplication.ts +27 -14
- package/src/app/agentApplicationBuilder.ts +13 -2
- package/src/app/agentApplicationOptions.ts +18 -4
- package/src/app/auth/authorization.ts +67 -2
- package/src/app/auth/authorizationManager.ts +126 -204
- package/src/app/auth/handlerStorage.ts +51 -7
- package/src/app/auth/types.ts +2 -2
- package/src/app/index.ts +7 -0
- package/src/app/proactive/proactive.ts +19 -9
- package/src/app/proactive/proactiveOptions.ts +2 -2
- package/src/app/rateLimit.ts +659 -0
- package/src/app/turnState.ts +50 -7
- package/src/auth/authConfiguration.ts +415 -260
- package/src/auth/delegatedTokenCredential.ts +49 -0
- package/src/auth/msal/msalTokenCredential.ts +1 -1
- package/src/auth/settings.ts +5 -82
- package/src/cloudAdapter.ts +38 -113
- package/src/configuration/botFrameworkEnvironmentCompatibility.ts +245 -0
- package/src/configuration/configuration.ts +1076 -0
- package/src/configuration/configurationSource.ts +76 -0
- package/src/configuration/environmentConfiguration.ts +377 -0
- package/src/configuration/index.ts +19 -0
- package/src/configuration/jsonFileConfigurationSource.ts +74 -0
- package/src/createCloudAdapter.ts +52 -9
- package/src/errorHelper.ts +151 -5
- package/src/index.ts +1 -0
- package/src/observability/traces.ts +8 -7
- package/src/outboundHostValidator.ts +37 -44
- package/src/state/agentState.ts +39 -13
- package/src/state/conversationState.ts +2 -2
- package/src/state/userState.ts +2 -2
- package/src/statusCodes.ts +6 -0
- package/src/storage/fileStorage.ts +299 -106
- package/src/storage/index.ts +1 -0
- package/src/storage/memoryStorage.ts +268 -85
- package/src/storage/storage.ts +114 -2
- package/src/storage/storageCompatibility.ts +185 -0
- package/src/storage/storageExpiry.ts +21 -0
- package/src/utils/env.ts +63 -0
- 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
|
-
*
|
|
25
|
-
*
|
|
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
|
|
29
|
-
*
|
|
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 {
|
|
78
|
+
return {
|
|
79
|
+
adapter: new CloudAdapter(
|
|
80
|
+
authConfig,
|
|
81
|
+
undefined,
|
|
82
|
+
undefined,
|
|
83
|
+
{ configurationContext }
|
|
84
|
+
),
|
|
85
|
+
headerPropagation: undefined
|
|
86
|
+
}
|
|
49
87
|
}
|
|
50
88
|
|
|
51
|
-
//
|
|
52
|
-
//
|
|
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(
|
|
92
|
+
const adapter = agent.adapter ?? new CloudAdapter(
|
|
93
|
+
authConfig,
|
|
94
|
+
undefined,
|
|
95
|
+
undefined,
|
|
96
|
+
{ configurationContext }
|
|
97
|
+
)
|
|
55
98
|
return { adapter, headerPropagation }
|
|
56
99
|
}
|