@microsoft/agents-hosting 1.6.0-beta.9.gce9d8facd2 → 1.7.0-beta.1.g8bcd4f11a7

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 (149) hide show
  1. package/dist/package.json +4 -9
  2. package/dist/src/agent-client/agentClient.js +2 -2
  3. package/dist/src/agent-client/agentClient.js.map +1 -1
  4. package/dist/src/agent-client/agentResponseHandler.js +2 -2
  5. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  6. package/dist/src/app/agentApplication.js +28 -2
  7. package/dist/src/app/agentApplication.js.map +1 -1
  8. package/dist/src/app/agentApplicationOptions.d.ts +10 -0
  9. package/dist/src/app/attachmentDownloader.js +5 -10
  10. package/dist/src/app/attachmentDownloader.js.map +1 -1
  11. package/dist/src/app/auth/authorizationManager.d.ts +4 -0
  12. package/dist/src/app/auth/authorizationManager.js +94 -67
  13. package/dist/src/app/auth/authorizationManager.js.map +1 -1
  14. package/dist/src/app/auth/handlers/azureBotAuthorization.js +2 -2
  15. package/dist/src/app/auth/handlers/azureBotAuthorization.js.map +1 -1
  16. package/dist/src/app/proactive/createConversationOptionsBuilder.js +10 -6
  17. package/dist/src/app/proactive/createConversationOptionsBuilder.js.map +1 -1
  18. package/dist/src/app/proactive/proactive.d.ts +1 -0
  19. package/dist/src/app/proactive/proactive.js +30 -15
  20. package/dist/src/app/proactive/proactive.js.map +1 -1
  21. package/dist/src/app/streaming/streamingResponse.d.ts +11 -0
  22. package/dist/src/app/streaming/streamingResponse.js +21 -0
  23. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  24. package/dist/src/app/teamsAttachmentDownloader.js +6 -14
  25. package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
  26. package/dist/src/auth/MemoryCache.d.ts +2 -1
  27. package/dist/src/auth/MemoryCache.js +7 -1
  28. package/dist/src/auth/MemoryCache.js.map +1 -1
  29. package/dist/src/auth/authConfiguration.d.ts +2 -143
  30. package/dist/src/auth/authConfiguration.js +336 -257
  31. package/dist/src/auth/authConfiguration.js.map +1 -1
  32. package/dist/src/auth/authConstants.d.ts +7 -7
  33. package/dist/src/auth/authConstants.js.map +1 -1
  34. package/dist/src/auth/connectionManager.d.ts +92 -0
  35. package/dist/src/auth/{msalConnectionManager.js → connectionManager.js} +48 -33
  36. package/dist/src/auth/connectionManager.js.map +1 -0
  37. package/dist/src/auth/index.d.ts +5 -3
  38. package/dist/src/auth/index.js +5 -3
  39. package/dist/src/auth/index.js.map +1 -1
  40. package/dist/src/auth/jwt-middleware.d.ts +6 -0
  41. package/dist/src/auth/jwt-middleware.js +34 -2
  42. package/dist/src/auth/jwt-middleware.js.map +1 -1
  43. package/dist/src/auth/msal/msalConnectionManager.d.ts +17 -0
  44. package/dist/src/auth/msal/msalConnectionManager.js +50 -0
  45. package/dist/src/auth/msal/msalConnectionManager.js.map +1 -0
  46. package/dist/src/auth/msal/msalConnectionSettings.d.ts +68 -0
  47. package/dist/src/auth/msal/msalConnectionSettings.js +7 -0
  48. package/dist/src/auth/msal/msalConnectionSettings.js.map +1 -0
  49. package/dist/src/auth/{msalTokenCredential.d.ts → msal/msalTokenCredential.d.ts} +1 -1
  50. package/dist/src/auth/{msalTokenCredential.js → msal/msalTokenCredential.js} +2 -2
  51. package/dist/src/auth/msal/msalTokenCredential.js.map +1 -0
  52. package/dist/src/auth/{msalTokenProvider.d.ts → msal/msalTokenProvider.d.ts} +30 -3
  53. package/dist/src/auth/msal/msalTokenProvider.js +733 -0
  54. package/dist/src/auth/msal/msalTokenProvider.js.map +1 -0
  55. package/dist/src/auth/settings.d.ts +336 -0
  56. package/dist/src/auth/settings.js +202 -0
  57. package/dist/src/auth/settings.js.map +1 -0
  58. package/dist/src/auth/sidecar/sidecarAuthProvider.d.ts +74 -0
  59. package/dist/src/auth/sidecar/sidecarAuthProvider.js +181 -0
  60. package/dist/src/auth/sidecar/sidecarAuthProvider.js.map +1 -0
  61. package/dist/src/auth/sidecar/sidecarConnectionSettings.d.ts +65 -0
  62. package/dist/src/auth/sidecar/sidecarConnectionSettings.js +7 -0
  63. package/dist/src/auth/sidecar/sidecarConnectionSettings.js.map +1 -0
  64. package/dist/src/auth/sidecar/sidecarHttpClient.d.ts +95 -0
  65. package/dist/src/auth/sidecar/sidecarHttpClient.js +406 -0
  66. package/dist/src/auth/sidecar/sidecarHttpClient.js.map +1 -0
  67. package/dist/src/auth/sidecar/sidecarModels.d.ts +110 -0
  68. package/dist/src/auth/sidecar/sidecarModels.js +48 -0
  69. package/dist/src/auth/sidecar/sidecarModels.js.map +1 -0
  70. package/dist/src/auth/sidecar/sidecarTokenExpiry.d.ts +13 -0
  71. package/dist/src/auth/sidecar/sidecarTokenExpiry.js +39 -0
  72. package/dist/src/auth/sidecar/sidecarTokenExpiry.js.map +1 -0
  73. package/dist/src/cloudAdapter.d.ts +58 -1
  74. package/dist/src/cloudAdapter.js +233 -53
  75. package/dist/src/cloudAdapter.js.map +1 -1
  76. package/dist/src/connector-client/connectorClient.d.ts +12 -8
  77. package/dist/src/connector-client/connectorClient.js +99 -82
  78. package/dist/src/connector-client/connectorClient.js.map +1 -1
  79. package/dist/src/errorHelper.js +181 -7
  80. package/dist/src/errorHelper.js.map +1 -1
  81. package/dist/src/getProductInfo.d.ts +23 -0
  82. package/dist/src/getProductInfo.js +71 -1
  83. package/dist/src/getProductInfo.js.map +1 -1
  84. package/dist/src/headerPropagation.d.ts +17 -2
  85. package/dist/src/headerPropagation.js +46 -24
  86. package/dist/src/headerPropagation.js.map +1 -1
  87. package/dist/src/httpClient.d.ts +60 -0
  88. package/dist/src/httpClient.js +173 -0
  89. package/dist/src/httpClient.js.map +1 -0
  90. package/dist/src/index.d.ts +3 -2
  91. package/dist/src/index.js +8 -2
  92. package/dist/src/index.js.map +1 -1
  93. package/dist/src/oauth/userTokenClient.d.ts +17 -4
  94. package/dist/src/oauth/userTokenClient.js +119 -80
  95. package/dist/src/oauth/userTokenClient.js.map +1 -1
  96. package/dist/src/observability/traces.d.ts +11 -4
  97. package/dist/src/observability/traces.js +39 -7
  98. package/dist/src/observability/traces.js.map +1 -1
  99. package/dist/src/utils/env.d.ts +50 -0
  100. package/dist/src/utils/env.js +113 -0
  101. package/dist/src/utils/env.js.map +1 -0
  102. package/dist/src/utils.d.ts +10 -0
  103. package/dist/src/utils.js +18 -0
  104. package/dist/src/utils.js.map +1 -0
  105. package/package.json +4 -9
  106. package/src/agent-client/agentClient.ts +2 -2
  107. package/src/agent-client/agentResponseHandler.ts +2 -2
  108. package/src/app/agentApplication.ts +30 -3
  109. package/src/app/agentApplicationOptions.ts +11 -0
  110. package/src/app/attachmentDownloader.ts +6 -8
  111. package/src/app/auth/authorizationManager.ts +84 -41
  112. package/src/app/auth/handlers/azureBotAuthorization.ts +2 -2
  113. package/src/app/proactive/createConversationOptionsBuilder.ts +8 -4
  114. package/src/app/proactive/proactive.ts +39 -23
  115. package/src/app/streaming/streamingResponse.ts +24 -1
  116. package/src/app/teamsAttachmentDownloader.ts +7 -11
  117. package/src/auth/MemoryCache.ts +7 -1
  118. package/src/auth/authConfiguration.ts +350 -356
  119. package/src/auth/authConstants.ts +7 -7
  120. package/src/auth/{msalConnectionManager.ts → connectionManager.ts} +65 -44
  121. package/src/auth/index.ts +5 -3
  122. package/src/auth/jwt-middleware.ts +32 -2
  123. package/src/auth/msal/msalConnectionManager.ts +55 -0
  124. package/src/auth/msal/msalConnectionSettings.ts +79 -0
  125. package/src/auth/{msalTokenCredential.ts → msal/msalTokenCredential.ts} +2 -1
  126. package/src/auth/msal/msalTokenProvider.ts +881 -0
  127. package/src/auth/settings.ts +378 -0
  128. package/src/auth/sidecar/sidecarAuthProvider.ts +234 -0
  129. package/src/auth/sidecar/sidecarConnectionSettings.ts +72 -0
  130. package/src/auth/sidecar/sidecarHttpClient.ts +443 -0
  131. package/src/auth/sidecar/sidecarModels.ts +153 -0
  132. package/src/auth/sidecar/sidecarTokenExpiry.ts +35 -0
  133. package/src/cloudAdapter.ts +292 -13
  134. package/src/connector-client/connectorClient.ts +116 -94
  135. package/src/errorHelper.ts +206 -7
  136. package/src/getProductInfo.ts +79 -1
  137. package/src/headerPropagation.ts +55 -23
  138. package/src/httpClient.ts +223 -0
  139. package/src/index.ts +6 -2
  140. package/src/oauth/userTokenClient.ts +137 -84
  141. package/src/observability/traces.ts +39 -8
  142. package/src/utils/env.ts +105 -0
  143. package/src/utils.ts +14 -0
  144. package/dist/src/auth/msalConnectionManager.d.ts +0 -64
  145. package/dist/src/auth/msalConnectionManager.js.map +0 -1
  146. package/dist/src/auth/msalTokenCredential.js.map +0 -1
  147. package/dist/src/auth/msalTokenProvider.js +0 -485
  148. package/dist/src/auth/msalTokenProvider.js.map +0 -1
  149. package/src/auth/msalTokenProvider.ts +0 -562
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { debug } from '@microsoft/agents-telemetry'
7
+ import { ExceptionHelper } from '@microsoft/agents-activity'
8
+ import { Errors } from '../errorHelper'
9
+ import type { MsalConnectionSettings } from './msal/msalConnectionSettings'
10
+ import type { SidecarConnectionSettings } from './sidecar/sidecarConnectionSettings'
11
+
12
+ export type { MsalConnectionSettings } from './msal/msalConnectionSettings'
13
+ export type { SidecarConnectionSettings } from './sidecar/sidecarConnectionSettings'
14
+
15
+ const logger = debug('agents:authConfiguration')
16
+
17
+ const DEFAULT_CONNECTION = 'serviceConnection'
18
+ const AUTHORITY_DEFAULT = 'https://login.microsoftonline.com'
19
+
20
+ /**
21
+ * A single entry in the connections map used to route an inbound activity
22
+ * (matched by audience and/or serviceUrl) to a named connection.
23
+ */
24
+ export interface ConnectionMapItem {
25
+ serviceUrl: string
26
+ audience?: string
27
+ connection: string
28
+ }
29
+
30
+ export const DEFAULT_CONNECTION_MAP: ConnectionMapItem = { serviceUrl: '*', connection: DEFAULT_CONNECTION }
31
+
32
+ export type ConnectionKeys = keyof Omit<AuthConfiguration, 'connections' | 'connectionsMap'>
33
+ export type ConnectionMapKeys = keyof ConnectionMapItem
34
+
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
+ export function applyDefaultSettings (config: AuthConfiguration) {
55
+ const settings = { ...config }
56
+ settings.authorityEndpoint ??= settings.authority ?? AUTHORITY_DEFAULT
57
+ settings.issuers ??= getDefaultIssuers(settings.tenantId ?? '', settings.authorityEndpoint)
58
+
59
+ // For backward compatibility
60
+ if (settings.federatedClientId) {
61
+ settings.FICClientId = settings.federatedClientId
62
+ }
63
+
64
+ if (settings.scopes?.length) {
65
+ settings.scope = settings.scopes?.[0]
66
+ }
67
+
68
+ if (settings.authorityEndpoint) {
69
+ settings.authority = settings.authorityEndpoint
70
+ }
71
+
72
+ // .NET parity alias: keep altBlueprintConnectionName and alternateBlueprintConnectionName in sync.
73
+ settings.altBlueprintConnectionName ??= settings.alternateBlueprintConnectionName
74
+ if (settings.altBlueprintConnectionName) {
75
+ settings.alternateBlueprintConnectionName = settings.altBlueprintConnectionName
76
+ }
77
+
78
+ if (!settings.clientId && process.env.NODE_ENV?.toLowerCase() === 'production') {
79
+ throw ExceptionHelper.generateException(Error, Errors.ClientIdRequiredInProduction)
80
+ }
81
+
82
+ const defaultConnections = settings.connections?.size
83
+ ? settings.connections
84
+ : new Map([[DEFAULT_CONNECTION, { ...settings }]])
85
+ const defaultConnectionsMap = settings.connectionsMap?.length
86
+ ? settings.connectionsMap
87
+ : [DEFAULT_CONNECTION_MAP]
88
+
89
+ settings.connections = defaultConnections
90
+ settings.connectionsMap = defaultConnectionsMap
91
+ return settings
92
+ }
93
+
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
+ /**
157
+ * Resolves the full authority URL including the tenant ID.
158
+ * Supports both patterns:
159
+ * - Tenant embedded in authority: https://login.microsoftonline.com/my-tenant
160
+ * - Authority + separate tenantId: https://login.microsoftonline.com + tenantId
161
+ * Also handles trailing slashes on authority.
162
+ */
163
+ export function resolveAuthority (authority?: string, tenantId?: string): string {
164
+ const base = trimTrailingSlashes(authority ?? AUTHORITY_DEFAULT)
165
+ const url = new URL(base)
166
+ const hasPathSegment = url.pathname !== '/'
167
+ if (hasPathSegment) {
168
+ return base
169
+ }
170
+ return `${base}/${tenantId ?? 'botframework.com'}`
171
+ }
172
+
173
+ function trimTrailingSlashes (value: string): string {
174
+ let end = value.length
175
+ while (end > 0 && value.charCodeAt(end - 1) === 47) {
176
+ end--
177
+ }
178
+
179
+ return end === value.length ? value : value.slice(0, end)
180
+ }
181
+
182
+ function getDefaultIssuers (tenantId: string, authority: string) : string[] {
183
+ // Convert empty string to undefined so resolveAuthority applies its 'botframework.com' default
184
+ const t = tenantId || undefined
185
+ if (!t) {
186
+ logger.warn('tenantId is not configured, defaulting to botframework.com')
187
+ }
188
+ return [
189
+ 'https://api.botframework.com',
190
+ `${resolveAuthority('https://sts.windows.net', t)}/`,
191
+ `${resolveAuthority(authority, t)}/v2.0`
192
+ ]
193
+ }
194
+
195
+ /**
196
+ * Connection-level settings common to every authentication provider.
197
+ *
198
+ * @remarks
199
+ * Mirrors the .NET `Microsoft.Agents.Authentication.ConnectionSettingsBase` abstract class: these are
200
+ * the credential-agnostic properties shared by all connection types. Provider-specific settings
201
+ * extend this base (see {@link MsalConnectionSettings} for MSAL and {@link SidecarConnectionSettings}
202
+ * for the Entra sidecar). The active provider for a connection is selected
203
+ * by {@link ConnectionSettingsBase.authType | authType}; see `AuthProviderFactory`.
204
+ */
205
+ export interface ConnectionSettingsBase {
206
+ /**
207
+ * The tenant ID for the authentication configuration.
208
+ */
209
+ tenantId?: string
210
+
211
+ /**
212
+ * The client ID for the authentication configuration. Required in production.
213
+ */
214
+ clientId?: string
215
+
216
+ /**
217
+ * The authentication type for the connection.
218
+ */
219
+ authType?: AuthType | string
220
+
221
+ /**
222
+ * A list of valid issuers for the authentication configuration.
223
+ */
224
+ issuers?: string[]
225
+
226
+ /**
227
+ * The connection name for the authentication configuration.
228
+ */
229
+ connectionName?: string
230
+
231
+ /**
232
+ * @deprecated Use authorityEndpoint instead.
233
+ *
234
+ * Entra Authentication Endpoint to use.
235
+ *
236
+ * @remarks
237
+ * If not populated the Entra Public Cloud endpoint is assumed.
238
+ * This example of Public Cloud Endpoint is https://login.microsoftonline.com
239
+ * see also https://learn.microsoft.com/entra/identity-platform/authentication-national-cloud
240
+ */
241
+ authority?: string
242
+
243
+ /**
244
+ * Entra Authentication Endpoint to use.
245
+ *
246
+ * @remarks
247
+ * If not populated the Entra Public Cloud endpoint is assumed.
248
+ * This example of Public Cloud Endpoint is https://login.microsoftonline.com
249
+ * see also https://learn.microsoft.com/entra/identity-platform/authentication-national-cloud
250
+ */
251
+ authorityEndpoint?: string
252
+
253
+ /**
254
+ * @deprecated Use scopes instead.
255
+ */
256
+ scope?: string
257
+
258
+ /**
259
+ * The scopes for the authentication configuration.
260
+ */
261
+ scopes?: string[]
262
+
263
+ /**
264
+ * An optional alternative blueprint Connection name used when constructing a connector client.
265
+ *
266
+ * @remarks
267
+ * Equivalent to the .NET `AlternateBlueprintConnectionName` connection setting. {@link alternateBlueprintConnectionName}
268
+ * is an alias of this property that matches the .NET name exactly; when both are provided this property takes precedence.
269
+ */
270
+ altBlueprintConnectionName?: string
271
+
272
+ /**
273
+ * Alias of {@link altBlueprintConnectionName} named to match the .NET `AlternateBlueprintConnectionName`
274
+ * connection setting exactly.
275
+ *
276
+ * @remarks
277
+ * Provided for stricter .NET parity. The two properties are kept in sync during configuration normalization;
278
+ * {@link altBlueprintConnectionName} takes precedence when both are set.
279
+ */
280
+ alternateBlueprintConnectionName?: string
281
+ }
282
+
283
+ /**
284
+ * The complete settings for a single connection, across every provider.
285
+ *
286
+ * @remarks
287
+ * This is the per-connection unit that an `AuthProvider` consumes: it combines the common base
288
+ * ({@link ConnectionSettingsBase}) with the MSAL settings ({@link MsalConnectionSettings}) and the
289
+ * Entra sidecar settings ({@link SidecarConnectionSettings}). The active provider for a connection is
290
+ * selected by `authType`, so only the subset of these properties relevant to that provider is used.
291
+ *
292
+ * Conceptually this is the consumer-facing counterpart to the {@link AuthConfiguration} *container*:
293
+ * each value in {@link AuthConfiguration.connections} is the settings for one connection. It parallels
294
+ * the .NET `IConnectionSettings` (a single connection's settings) as distinct from the connection
295
+ * registry. {@link AuthConfiguration} extends this type for backward compatibility (it doubles as the
296
+ * settings for the legacy single connection).
297
+ */
298
+ export interface ConnectionSettings extends MsalConnectionSettings, SidecarConnectionSettings {}
299
+
300
+ /**
301
+ * Represents the authentication configuration.
302
+ *
303
+ * @remarks
304
+ * The agent-level authentication container. It owns the connection registry
305
+ * (`connections`/`connectionsMap`) and, for backward compatibility, also extends
306
+ * {@link ConnectionSettings} so the legacy single-connection shape (a flat config with `clientId`,
307
+ * `clientSecret`, etc.) keeps working — in that mode the top-level object *is* the one connection's
308
+ * settings. The active provider for each connection is dispatched by `authType` via
309
+ * `AuthProviderFactory`, so a single connection only uses the subset of these properties relevant to
310
+ * its provider.
311
+ */
312
+ export interface AuthConfiguration extends ConnectionSettings {
313
+ /**
314
+ * The connection registry: a map of connection name to that connection's settings.
315
+ *
316
+ * @remarks
317
+ * Each value is the {@link ConnectionSettings} for one connection (typed as {@link AuthConfiguration}
318
+ * for backward compatibility; only the connection-settings subset is consumed). An `AuthProvider` is
319
+ * created per entry by `AuthProviderFactory`, dispatched by `authType`.
320
+ */
321
+ connections?: Map<string, AuthConfiguration>
322
+
323
+ /**
324
+ * A list of connection map items to map service URLs to connection names.
325
+ */
326
+ connectionsMap?: ConnectionMapItem[]
327
+ }
328
+
329
+ /**
330
+ * Supported authentication types for agent connections.
331
+ */
332
+ export enum AuthType {
333
+ Certificate = 'Certificate',
334
+ CertificateSubjectName = 'CertificateSubjectName',
335
+ ClientSecret = 'ClientSecret',
336
+ UserManagedIdentity = 'UserManagedIdentity',
337
+ SystemManagedIdentity = 'SystemManagedIdentity',
338
+ FederatedCredentials = 'FederatedCredentials',
339
+ WorkloadIdentity = 'WorkloadIdentity',
340
+ IdentityProxyManager = 'IdentityProxyManager',
341
+ EntraAuthSideCar = 'EntraAuthSideCar'
342
+ }
343
+
344
+ /**
345
+ * Resolves the authentication type for a given authentication configuration.
346
+ * @remarks
347
+ * The function checks various properties of the `authConfig` object to determine the appropriate authentication type.
348
+ * It returns a string representing the resolved authentication type or 'unknown' if it cannot be determined.
349
+ * @param authConfig The authentication configuration object.
350
+ * @returns The resolved authentication type as a string or 'unknown' if it cannot be determined.
351
+ */
352
+ export function resolveAuthType (authConfig?: AuthConfiguration): AuthType | string {
353
+ if (!authConfig) {
354
+ return 'none'
355
+ }
356
+ if (authConfig.authType) {
357
+ return authConfig.authType
358
+ }
359
+ if (authConfig.WIDAssertionFile !== undefined) {
360
+ return AuthType.WorkloadIdentity
361
+ }
362
+ if (authConfig.federatedClientId !== undefined || authConfig.FICClientId !== undefined) {
363
+ return AuthType.FederatedCredentials
364
+ }
365
+ if (authConfig.clientSecret !== undefined) {
366
+ return AuthType.ClientSecret
367
+ }
368
+ if (authConfig.certPemFile !== undefined && authConfig.certKeyFile !== undefined) {
369
+ return AuthType.Certificate
370
+ }
371
+ if (authConfig.clientSecret === undefined && authConfig.certPemFile === undefined && authConfig.certKeyFile === undefined) {
372
+ return AuthType.UserManagedIdentity
373
+ }
374
+ if (authConfig.idpmResource !== undefined) {
375
+ return AuthType.IdentityProxyManager
376
+ }
377
+ return 'unknown'
378
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { debug } from '@microsoft/agents-telemetry'
7
+ import { ExceptionHelper } from '@microsoft/agents-activity'
8
+ import { Errors } from '../../errorHelper'
9
+ import { AuthConfiguration } from '../authConfiguration'
10
+ import { AuthProvider } from '../authProvider'
11
+ import { SidecarHttpClient } from './sidecarHttpClient'
12
+ import { resolveTokenExpiry } from './sidecarTokenExpiry'
13
+ import {
14
+ ResolvedSidecarConnectionSettings,
15
+ SidecarRequestOptions,
16
+ toSidecarConnectionSettings
17
+ } from './sidecarModels'
18
+
19
+ const logger = debug('agents:sidecar')
20
+
21
+ const GUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
22
+
23
+ // Refresh slightly ahead of the real expiry so callers never receive a token that expires mid-flight.
24
+ const EXPIRY_BUFFER_MS = 30 * 1000
25
+
26
+ // Hard upper bound on cached entries; protects memory when many distinct identities are served.
27
+ const MAX_CACHE_ENTRIES = 500
28
+
29
+ interface CachedToken {
30
+ token: string
31
+ expiresOn: number
32
+ }
33
+
34
+ /**
35
+ * Authentication provider that delegates token acquisition to the Microsoft Entra Agent ID
36
+ * sidecar (agent container). This replaces MSAL at the connection layer, using the sidecar's
37
+ * `/AuthorizationHeaderUnauthenticated/{serviceName}` endpoint for app-only and agentic identity
38
+ * flows. The sidecar performs the full Blueprint→Instance→User chain internally; no MSAL exchange is
39
+ * performed in-process.
40
+ */
41
+ export class SidecarAuthProvider implements AuthProvider {
42
+ public readonly connectionSettings?: AuthConfiguration
43
+ private readonly _settings: ResolvedSidecarConnectionSettings
44
+ private readonly _httpClient: SidecarHttpClient
45
+ private readonly _tokenCache: Map<string, CachedToken> = new Map()
46
+
47
+ /**
48
+ * Creates a new {@link SidecarAuthProvider}.
49
+ * @param connectionSettings The connection authentication configuration.
50
+ */
51
+ constructor (connectionSettings?: AuthConfiguration) {
52
+ this.connectionSettings = connectionSettings
53
+ this._settings = toSidecarConnectionSettings(connectionSettings)
54
+
55
+ const resolvedUrl = SidecarHttpClient.resolveBaseUrl(this._settings.sidecarBaseUrl)
56
+ SidecarHttpClient.validateBaseUrl(resolvedUrl, this._settings.bypassLocalNetworkRestriction)
57
+ this._httpClient = new SidecarHttpClient(resolvedUrl, this._settings.requestTimeout, this._settings.retryCount)
58
+ logger.debug('SidecarAuthProvider initialized serviceName=%s blueprintServiceName=%s', this._settings.serviceName, this._settings.blueprintServiceName)
59
+ }
60
+
61
+ /**
62
+ * Acquires an app-only access token from the sidecar.
63
+ * @param scope The scope for the token.
64
+ */
65
+ async getAccessToken (scope: string): Promise<string>
66
+ /**
67
+ * Acquires an app-only access token from the sidecar.
68
+ * @param authConfig The authentication configuration. Ignored by the sidecar provider, which
69
+ * owns the credential and derives the token from its configured service name; accepted only to
70
+ * satisfy the {@link AuthProvider} overload.
71
+ * @param scope The scope for the token.
72
+ */
73
+ async getAccessToken (authConfig: AuthConfiguration, scope: string): Promise<string>
74
+ async getAccessToken (authConfigOrScope: AuthConfiguration | string, scope?: string): Promise<string> {
75
+ const actualScope = typeof authConfigOrScope === 'string' ? authConfigOrScope : scope
76
+ const options: SidecarRequestOptions = {
77
+ scopes: actualScope ? [actualScope] : this._settings.scopes,
78
+ requestAppToken: true
79
+ }
80
+ return this.getCachedToken(this._settings.serviceName, options)
81
+ }
82
+
83
+ /**
84
+ * Acquires the Blueprint (agent application) token from the sidecar.
85
+ * @param tenantId The tenant ID.
86
+ * @param agentAppInstanceId The agent instance ID (from the inbound activity); maps to `AgentIdentity`.
87
+ */
88
+ async getAgenticApplicationToken (tenantId: string, agentAppInstanceId: string): Promise<string> {
89
+ this.ensureConnectionSettings()
90
+ const options: SidecarRequestOptions = {
91
+ agentIdentity: agentAppInstanceId,
92
+ tenant: tenantId
93
+ }
94
+ return this.getCachedToken(this._settings.blueprintServiceName, options)
95
+ }
96
+
97
+ /**
98
+ * Acquires the autonomous agent (instance) token from the sidecar for the configured resource.
99
+ * @param tenantId The tenant ID.
100
+ * @param agentAppInstanceId The agent instance ID (from the inbound activity); maps to `AgentIdentity`.
101
+ */
102
+ async getAgenticInstanceToken (tenantId: string, agentAppInstanceId: string): Promise<string> {
103
+ this.ensureConnectionSettings()
104
+ const options: SidecarRequestOptions = {
105
+ agentIdentity: agentAppInstanceId,
106
+ requestAppToken: true,
107
+ tenant: tenantId,
108
+ scopes: this._settings.scopes
109
+ }
110
+ return this.getCachedToken(this._settings.serviceName, options)
111
+ }
112
+
113
+ /**
114
+ * Acquires the agentic user token from the sidecar for the configured resource.
115
+ * @param tenantId The tenant ID.
116
+ * @param agentAppInstanceId The agent instance ID (from the inbound activity); maps to `AgentIdentity`.
117
+ * @param upn The agentic user identifier. A GUID is sent as `AgentUserId`; otherwise as `AgentUsername`.
118
+ * @param scopes The OAuth scopes to request.
119
+ */
120
+ async getAgenticUserToken (tenantId: string, agentAppInstanceId: string, upn: string, scopes: string[]): Promise<string> {
121
+ this.ensureConnectionSettings()
122
+ const isObjectId = GUID_REGEX.test(upn ?? '')
123
+ const options: SidecarRequestOptions = {
124
+ agentIdentity: agentAppInstanceId,
125
+ agentUsername: isObjectId ? undefined : upn,
126
+ agentUserId: isObjectId ? upn : undefined,
127
+ tenant: tenantId,
128
+ scopes: scopes ?? this._settings.scopes
129
+ }
130
+ return this.getCachedToken(this._settings.serviceName, options)
131
+ }
132
+
133
+ /**
134
+ * On-behalf-of token exchange — not supported by the sidecar provider in Phase 1.
135
+ */
136
+ async acquireTokenOnBehalfOf (scopes: string[], oboAssertion: string): Promise<string>
137
+ async acquireTokenOnBehalfOf (authConfig: AuthConfiguration, scopes: string[], oboAssertion: string): Promise<string>
138
+ async acquireTokenOnBehalfOf (
139
+ _authConfigOrScopes: AuthConfiguration | string[],
140
+ _scopesOrOboAssertion?: string[] | string,
141
+ _oboAssertion?: string
142
+ ): Promise<string> {
143
+ throw ExceptionHelper.generateException(Error, Errors.OnBehalfOfNotSupportedBySidecar)
144
+ }
145
+
146
+ /**
147
+ * Checks sidecar availability via the `/healthz` endpoint.
148
+ * @returns `true` when the sidecar is reachable and healthy.
149
+ */
150
+ async isHealthy (): Promise<boolean> {
151
+ return this._httpClient.isHealthy()
152
+ }
153
+
154
+ private ensureConnectionSettings (): void {
155
+ if (!this.connectionSettings) {
156
+ throw ExceptionHelper.generateException(Error, Errors.SidecarConnectionSettingsRequired)
157
+ }
158
+ }
159
+
160
+ private async getCachedToken (serviceName: string, options: SidecarRequestOptions): Promise<string> {
161
+ const forceRefresh = options.forceRefresh === true
162
+ const cacheKey = SidecarAuthProvider.buildCacheKey(serviceName, options)
163
+
164
+ const cached = this.cacheGet(cacheKey, forceRefresh)
165
+ if (cached) {
166
+ return cached
167
+ }
168
+
169
+ const result = await this._httpClient.getAuthorizationHeaderUnauthenticated(serviceName, options)
170
+ // The hosting stack always transmits the token as `Bearer {token}`. Reject any other scheme
171
+ // (e.g. PoP) rather than silently emitting an invalid Authorization header.
172
+ if (result.scheme && result.scheme.toLowerCase() !== 'bearer') {
173
+ throw ExceptionHelper.generateException(Error, Errors.SidecarUnsupportedAuthScheme, undefined, { scheme: result.scheme })
174
+ }
175
+ this.cacheSet(cacheKey, { token: result.token, expiresOn: resolveTokenExpiry(result.token) })
176
+ return result.token
177
+ }
178
+
179
+ private cacheGet (cacheKey: string, forceRefresh: boolean): string | undefined {
180
+ const cached = this._tokenCache.get(cacheKey)
181
+ if (cached) {
182
+ if (!forceRefresh && cached.expiresOn >= Date.now() + EXPIRY_BUFFER_MS) {
183
+ return cached.token
184
+ }
185
+ this._tokenCache.delete(cacheKey)
186
+ }
187
+ return undefined
188
+ }
189
+
190
+ private cacheSet (cacheKey: string, token: CachedToken): void {
191
+ this._tokenCache.set(cacheKey, token)
192
+ if (this._tokenCache.size > MAX_CACHE_ENTRIES) {
193
+ this.pruneExpiredEntries()
194
+ if (this._tokenCache.size > MAX_CACHE_ENTRIES) {
195
+ this.evictNearestExpiry()
196
+ }
197
+ }
198
+ }
199
+
200
+ private pruneExpiredEntries (): void {
201
+ const now = Date.now()
202
+ for (const [key, value] of this._tokenCache) {
203
+ if (value.expiresOn <= now) {
204
+ this._tokenCache.delete(key)
205
+ }
206
+ }
207
+ }
208
+
209
+ private evictNearestExpiry (): void {
210
+ const overflow = this._tokenCache.size - MAX_CACHE_ENTRIES
211
+ if (overflow <= 0) {
212
+ return
213
+ }
214
+ const sorted = [...this._tokenCache.entries()].sort((a, b) => a[1].expiresOn - b[1].expiresOn)
215
+ for (let i = 0; i < overflow; i++) {
216
+ this._tokenCache.delete(sorted[i][0])
217
+ }
218
+ }
219
+
220
+ private static buildCacheKey (serviceName: string, options: SidecarRequestOptions): string {
221
+ const scopes = options.scopes
222
+ ? [...new Set(options.scopes.filter((s) => s && s.trim()))].sort().join(' ')
223
+ : ''
224
+ return [
225
+ serviceName,
226
+ options.agentIdentity ?? '',
227
+ options.agentUsername ?? '',
228
+ options.agentUserId ?? '',
229
+ options.tenant ?? '',
230
+ options.requestAppToken === true ? 'app' : 'user',
231
+ scopes
232
+ ].join('|')
233
+ }
234
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import type { ConnectionSettingsBase } from '../settings'
7
+
8
+ /**
9
+ * Connection settings for the Entra Agent ID sidecar (agent container) authentication provider, used when a
10
+ * connection's `authType` is `'EntraAuthSideCar'`.
11
+ *
12
+ * @remarks
13
+ * Mirrors the .NET `Microsoft.Agents.Authentication.EntraAuthSidecar.Model.SidecarConnectionSettings`
14
+ * class, which likewise derives from `ConnectionSettingsBase`. These are the configuration-level
15
+ * properties; they are normalized (with defaults applied) before use by the sidecar token provider.
16
+ */
17
+ export interface SidecarConnectionSettings extends ConnectionSettingsBase {
18
+ /**
19
+ * Optional base URL of the Entra Agent ID sidecar (agent container).
20
+ *
21
+ * @remarks
22
+ * Only used when `authType` is `'EntraAuthSideCar'`. Resolution order:
23
+ * `SIDECAR_URL` environment variable > this setting > `http://localhost:5178`.
24
+ * Regardless of how it is resolved, the host must be a loopback/private address
25
+ * unless `bypassLocalNetworkRestriction` is set.
26
+ */
27
+ sidecarBaseUrl?: string
28
+
29
+ /**
30
+ * The configured downstream API service name in the sidecar's DownstreamApis configuration.
31
+ *
32
+ * @remarks
33
+ * Only used when `authType` is `'EntraAuthSideCar'`. Defaults to `'default'`.
34
+ */
35
+ serviceName?: string
36
+
37
+ /**
38
+ * The sidecar downstream API name used to acquire the Blueprint (agent application) token for the
39
+ * agentic FIC chain.
40
+ *
41
+ * @remarks
42
+ * Only used when `authType` is `'EntraAuthSideCar'`. Defaults to `'agenticblueprint'`. This
43
+ * downstream API must be configured app-only with the `api://AzureAdTokenExchange/.default` scope.
44
+ */
45
+ blueprintServiceName?: string
46
+
47
+ /**
48
+ * When `true`, disables the loopback/private-address safety check on the resolved sidecar base URL.
49
+ *
50
+ * @remarks
51
+ * UNSAFE. Leave this `false` in all normal deployments. Only enable it for a carefully validated
52
+ * private-network configuration where the sidecar is reachable at a non-private address that the
53
+ * operator explicitly trusts. Only used when `authType` is `'EntraAuthSideCar'`.
54
+ */
55
+ bypassLocalNetworkRestriction?: boolean
56
+
57
+ /**
58
+ * HTTP request timeout (in milliseconds) for sidecar calls.
59
+ *
60
+ * @remarks
61
+ * Only used when `authType` is `'EntraAuthSideCar'`. Defaults to 30000 (30 seconds).
62
+ */
63
+ requestTimeout?: number
64
+
65
+ /**
66
+ * Number of retry attempts for transient sidecar failures (5xx, 408, 429, network/timeout).
67
+ *
68
+ * @remarks
69
+ * Only used when `authType` is `'EntraAuthSideCar'`. Defaults to 3.
70
+ */
71
+ retryCount?: number
72
+ }