@microsoft/agents-hosting 1.1.0-alpha.58 → 1.1.0-alpha.78

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 (45) hide show
  1. package/dist/package.json +4 -4
  2. package/dist/src/activityWireCompat.d.ts +1 -1
  3. package/dist/src/activityWireCompat.js +3 -0
  4. package/dist/src/activityWireCompat.js.map +1 -1
  5. package/dist/src/app/agentApplication.d.ts +2 -2
  6. package/dist/src/app/agentApplication.js +1 -1
  7. package/dist/src/app/agentApplication.js.map +1 -1
  8. package/dist/src/app/auth/authorization.d.ts +9 -1
  9. package/dist/src/app/auth/authorization.js +4 -4
  10. package/dist/src/app/auth/authorization.js.map +1 -1
  11. package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +0 -4
  12. package/dist/src/app/auth/handlers/agenticAuthorization.js +1 -12
  13. package/dist/src/app/auth/handlers/agenticAuthorization.js.map +1 -1
  14. package/dist/src/app/index.d.ts +1 -0
  15. package/dist/src/app/index.js.map +1 -1
  16. package/dist/src/auth/authConfiguration.js +14 -23
  17. package/dist/src/auth/authConfiguration.js.map +1 -1
  18. package/dist/src/auth/connections.d.ts +5 -4
  19. package/dist/src/auth/msalConnectionManager.d.ts +6 -5
  20. package/dist/src/auth/msalConnectionManager.js +31 -7
  21. package/dist/src/auth/msalConnectionManager.js.map +1 -1
  22. package/dist/src/cloudAdapter.d.ts +3 -3
  23. package/dist/src/cloudAdapter.js +37 -39
  24. package/dist/src/cloudAdapter.js.map +1 -1
  25. package/dist/src/connector-client/connectorClient.d.ts +6 -0
  26. package/dist/src/connector-client/connectorClient.js +11 -7
  27. package/dist/src/connector-client/connectorClient.js.map +1 -1
  28. package/dist/src/oauth/customUserTokenAPI.d.ts +1 -0
  29. package/dist/src/oauth/customUserTokenAPI.js +11 -0
  30. package/dist/src/oauth/customUserTokenAPI.js.map +1 -0
  31. package/dist/src/oauth/userTokenClient.js +2 -6
  32. package/dist/src/oauth/userTokenClient.js.map +1 -1
  33. package/package.json +4 -4
  34. package/src/activityWireCompat.ts +4 -1
  35. package/src/app/agentApplication.ts +3 -4
  36. package/src/app/auth/authorization.ts +11 -2
  37. package/src/app/auth/handlers/agenticAuthorization.ts +1 -13
  38. package/src/app/index.ts +1 -0
  39. package/src/auth/authConfiguration.ts +15 -28
  40. package/src/auth/connections.ts +5 -4
  41. package/src/auth/msalConnectionManager.ts +33 -8
  42. package/src/cloudAdapter.ts +28 -29
  43. package/src/connector-client/connectorClient.ts +11 -6
  44. package/src/oauth/customUserTokenAPI.ts +5 -0
  45. package/src/oauth/userTokenClient.ts +3 -7
@@ -213,39 +213,27 @@ export const loadPrevAuthConfigFromEnv: () => AuthConfiguration = () => {
213
213
 
214
214
  function loadConnectionsMapFromEnv () {
215
215
  const envVars = process.env
216
- const connections = new Map<string, AuthConfiguration>()
216
+ const connectionsObj: Record<string, any> = {}
217
217
  const connectionsMap: ConnectionMapItem[] = []
218
+ const CONNECTIONS_PREFIX = 'connections__'
219
+ const CONNECTIONS_MAP_PREFIX = 'connectionsMap__'
218
220
 
219
221
  for (const [key, value] of Object.entries(envVars)) {
220
- if (key.startsWith('connections__')) {
221
- const parts = key.split('__')
222
- if (parts.length >= 4 && parts[2] === 'settings') {
223
- const connectionName = parts[1]
224
- const propertyPath = parts.slice(3).join('.') // e.g., 'issuers.0' or 'clientId'
225
-
226
- let config = connections.get(connectionName)
227
- if (!config) {
228
- config = {}
229
- connections.set(connectionName, config)
230
- }
231
-
232
- objectPath.set(config, propertyPath, value)
233
- }
234
- } else if (key.startsWith('connectionsMap__')) {
235
- const parts = key.split('__')
236
- if (parts.length === 3) {
237
- const index = parseInt(parts[1], 10)
238
- const property = parts[2]
239
-
240
- if (!connectionsMap[index]) {
241
- connectionsMap[index] = { serviceUrl: '', connection: '' }
242
- }
243
-
244
- (connectionsMap[index] as any)[property] = value
245
- }
222
+ if (key.startsWith(CONNECTIONS_PREFIX)) {
223
+ // Convert to dot notation
224
+ let path = key.substring(CONNECTIONS_PREFIX.length).replace(/__/g, '.')
225
+ // Remove ".settings." from the path
226
+ path = path.replace('.settings.', '.')
227
+ objectPath.set(connectionsObj, path, value)
228
+ } else if (key.startsWith(CONNECTIONS_MAP_PREFIX)) {
229
+ const path = key.substring(CONNECTIONS_MAP_PREFIX.length).replace(/__/g, '.')
230
+ objectPath.set(connectionsMap, path, value)
246
231
  }
247
232
  }
248
233
 
234
+ // Convert connectionsObj to Map<string, AuthConfiguration>
235
+ const connections: Map<string, AuthConfiguration> = new Map(Object.entries(connectionsObj))
236
+
249
237
  if (connections.size === 0) {
250
238
  logger.warn('No connections found in configuration.')
251
239
  }
@@ -265,7 +253,6 @@ function loadConnectionsMapFromEnv () {
265
253
  }
266
254
  }
267
255
  }
268
-
269
256
  return {
270
257
  connections,
271
258
  connectionsMap,
@@ -6,6 +6,7 @@
6
6
  import { Activity } from '@microsoft/agents-activity'
7
7
  import { AuthConfiguration } from './authConfiguration'
8
8
  import { AuthProvider } from './authProvider'
9
+ import { JwtPayload } from 'jsonwebtoken'
9
10
 
10
11
  export interface Connections {
11
12
  /**
@@ -24,19 +25,19 @@ export interface Connections {
24
25
 
25
26
  /**
26
27
  * Get the OAuth token provider for the agent.
27
- * @param audience - The audience.
28
+ * @param identity - The identity. Usually TurnContext.identity.
28
29
  * @param serviceUrl - The service url.
29
30
  * @returns An AuthProvider instance.
30
31
  */
31
- getTokenProvider: (audience: string, serviceUrl: string) => AuthProvider
32
+ getTokenProvider: (identity: JwtPayload, serviceUrl: string) => AuthProvider
32
33
 
33
34
  /**
34
35
  * Get the OAuth token provider for the agent.
35
- * @param audience - The audience.
36
+ * @param identity - The identity. Usually TurnContext.identity.
36
37
  * @param activity - The activity.
37
38
  * @returns An AuthProvider instance.
38
39
  */
39
- getTokenProviderFromActivity: (audience: string, activity: Activity) => AuthProvider
40
+ getTokenProviderFromActivity: (identity: JwtPayload, activity: Activity) => AuthProvider
40
41
 
41
42
  /**
42
43
  * Get the default connection configuration for the agent.
@@ -5,9 +5,9 @@
5
5
 
6
6
  import { Activity, RoleTypes } from '@microsoft/agents-activity'
7
7
  import { AuthConfiguration } from './authConfiguration'
8
- import { AuthProvider } from './authProvider'
9
8
  import { Connections } from './connections'
10
9
  import { MsalTokenProvider } from './msalTokenProvider'
10
+ import { JwtPayload } from 'jsonwebtoken'
11
11
 
12
12
  export interface ConnectionMapItem {
13
13
  audience?: string
@@ -50,7 +50,7 @@ export class MsalConnectionManager implements Connections {
50
50
  if (!conn) {
51
51
  throw new Error(`Connection not found: ${connectionName}`)
52
52
  }
53
- return conn
53
+ return this.applyConnectionDefaults(conn)
54
54
  }
55
55
 
56
56
  /**
@@ -69,13 +69,15 @@ export class MsalConnectionManager implements Connections {
69
69
  }
70
70
  }
71
71
 
72
- return this._connections.values().next().value as MsalTokenProvider
72
+ const conn = this._connections.values().next().value as MsalTokenProvider
73
+
74
+ return this.applyConnectionDefaults(conn)
73
75
  }
74
76
 
75
77
  /**
76
78
  * Finds a connection based on a map.
77
79
  *
78
- * @param audience The audience.
80
+ * @param identity - The identity. Usually TurnContext.identity.
79
81
  * @param serviceUrl The service URL.
80
82
  * @returns The TokenProvider for the connection.
81
83
  *
@@ -90,7 +92,18 @@ export class MsalConnectionManager implements Connections {
90
92
  * ServiceUrl is: A regex to match with, or "*" for any serviceUrl value.
91
93
  * Connection is: A name in the 'Connections' list.
92
94
  */
93
- getTokenProvider (audience: string, serviceUrl: string): MsalTokenProvider {
95
+ getTokenProvider (identity: JwtPayload, serviceUrl: string): MsalTokenProvider {
96
+ if (!identity) {
97
+ throw new Error('Identity is required to get the token provider.')
98
+ }
99
+
100
+ let audience
101
+ if (Array.isArray(identity?.aud)) {
102
+ audience = identity.aud[0]
103
+ } else {
104
+ audience = identity.aud
105
+ }
106
+
94
107
  if (!audience || !serviceUrl) throw new Error('Audience and Service URL are required to get the token provider.')
95
108
 
96
109
  if (this._connectionsMap.length === 0) {
@@ -121,12 +134,12 @@ export class MsalConnectionManager implements Connections {
121
134
 
122
135
  /**
123
136
  * Finds a connection based on an activity's blueprint.
124
- * @param audience The audience.
137
+ * @param identity - The identity. Usually TurnContext.identity.
125
138
  * @param activity The activity.
126
139
  * @returns The TokenProvider for the connection.
127
140
  */
128
- getTokenProviderFromActivity (audience: string, activity: Activity): AuthProvider {
129
- let connection = this.getTokenProvider(audience, activity.serviceUrl || '')
141
+ getTokenProviderFromActivity (identity: JwtPayload, activity: Activity): MsalTokenProvider {
142
+ let connection = this.getTokenProvider(identity, activity.serviceUrl || '')
130
143
 
131
144
  // This is for the case where the Agentic BlueprintId is not the same as the AppId
132
145
  if (connection &&
@@ -147,4 +160,16 @@ export class MsalConnectionManager implements Connections {
147
160
  getDefaultConnectionConfiguration (): AuthConfiguration {
148
161
  return this._serviceConnectionConfiguration
149
162
  }
163
+
164
+ private applyConnectionDefaults (conn: MsalTokenProvider): MsalTokenProvider {
165
+ if (conn.connectionSettings) {
166
+ conn.connectionSettings.authority ??= 'https://login.microsoftonline.com'
167
+ conn.connectionSettings.issuers ??= [
168
+ 'https://api.botframework.com',
169
+ `https://sts.windows.net/${conn.connectionSettings.tenantId}/`,
170
+ `${conn.connectionSettings.authority}/${conn.connectionSettings.tenantId}/v2.0`
171
+ ]
172
+ }
173
+ return conn
174
+ }
150
175
  }
@@ -25,7 +25,7 @@ import { normalizeIncomingActivity } from './activityWireCompat'
25
25
  import { UserTokenClient } from './oauth'
26
26
  import { HeaderPropagation, HeaderPropagationCollection, HeaderPropagationDefinition } from './headerPropagation'
27
27
  import { JwtPayload } from 'jsonwebtoken'
28
-
28
+ import { getTokenServiceEndpoint } from './oauth/customUserTokenAPI'
29
29
  const logger = debug('agents:cloud-adapter')
30
30
 
31
31
  /**
@@ -93,11 +93,11 @@ export class CloudAdapter extends BaseAdapter {
93
93
  protected async createConnectorClient (
94
94
  serviceUrl: string,
95
95
  scope: string,
96
- audience: string,
96
+ identity: JwtPayload,
97
97
  headers?: HeaderPropagationCollection
98
98
  ): Promise<ConnectorClient> {
99
99
  // get the correct token provider
100
- const tokenProvider = this.connectionManager.getTokenProvider(audience, serviceUrl)
100
+ const tokenProvider = this.connectionManager.getTokenProvider(identity, serviceUrl)
101
101
 
102
102
  const token = await tokenProvider.getAccessToken(scope)
103
103
  return ConnectorClient.createClientWithToken(
@@ -110,16 +110,8 @@ export class CloudAdapter extends BaseAdapter {
110
110
  protected async createConnectorClientWithIdentity (
111
111
  identity: JwtPayload,
112
112
  activity: Activity,
113
- scope: string,
114
113
  headers?: HeaderPropagationCollection) {
115
- let audience
116
- if (Array.isArray(identity.aud)) {
117
- audience = identity.aud[0]
118
- } else {
119
- audience = identity.aud
120
- }
121
-
122
- if (!audience) {
114
+ if (!identity?.aud) {
123
115
  // anonymous
124
116
  return ConnectorClient.createClientWithToken(
125
117
  activity.serviceUrl!,
@@ -129,7 +121,7 @@ export class CloudAdapter extends BaseAdapter {
129
121
  }
130
122
 
131
123
  let connectorClient
132
- const tokenProvider = this.connectionManager.getTokenProvider(audience!, activity.serviceUrl ?? '')
124
+ const tokenProvider = this.connectionManager.getTokenProviderFromActivity(identity, activity)
133
125
  if (activity.isAgenticRequest()) {
134
126
  logger.debug('Activity is from an agentic source, using special scope', activity.recipient)
135
127
 
@@ -154,6 +146,9 @@ export class CloudAdapter extends BaseAdapter {
154
146
  throw new Error('Could not create connector client for agentic user')
155
147
  }
156
148
  } else {
149
+ // ABS tokens will not have an azp/appid so use the botframework scope.
150
+ // Otherwise use the appId. This will happen when communicating back to another agent.
151
+ const scope = identity.azp ?? identity.appid ?? 'https://api.botframework.com'
157
152
  const token = await tokenProvider.getAccessToken(scope)
158
153
  connectorClient = ConnectorClient.createClientWithToken(
159
154
  activity.serviceUrl!,
@@ -194,13 +189,14 @@ export class CloudAdapter extends BaseAdapter {
194
189
  * @protected
195
190
  */
196
191
  protected async createUserTokenClient (
197
- tokenServiceEndpoint: string = 'https://api.botframework.com',
192
+ identity: JwtPayload,
193
+ tokenServiceEndpoint: string = getTokenServiceEndpoint(),
198
194
  scope: string = 'https://api.botframework.com',
199
195
  audience: string = 'https://api.botframework.com',
200
196
  headers?: HeaderPropagationCollection
201
197
  ): Promise<UserTokenClient> {
202
198
  // get the correct token provider
203
- const tokenProvider = this.connectionManager.getTokenProvider(audience, tokenServiceEndpoint)
199
+ const tokenProvider = this.connectionManager.getTokenProvider(identity, tokenServiceEndpoint)
204
200
 
205
201
  return UserTokenClient.createClientWithScope(
206
202
  tokenServiceEndpoint,
@@ -258,9 +254,7 @@ export class CloudAdapter extends BaseAdapter {
258
254
  delete activity.id
259
255
  let response: ResourceResponse = { id: '' }
260
256
 
261
- if (activity.type === ActivityTypes.Delay) {
262
- await setTimeout(() => { }, typeof activity.value === 'number' ? activity.value : 1000)
263
- } else if (activity.type === ActivityTypes.InvokeResponse) {
257
+ if (activity.type === ActivityTypes.InvokeResponse) {
264
258
  context.turnState.set(INVOKE_RESPONSE_KEY, activity)
265
259
  } else if (activity.type === ActivityTypes.Trace && activity.channelId !== Channels.Emulator) {
266
260
  // no-op
@@ -328,15 +322,14 @@ export class CloudAdapter extends BaseAdapter {
328
322
  logger.debug('Received activity: ', activity)
329
323
 
330
324
  const context = new TurnContext(this, activity, request.user!)
331
- const scope = request.user?.azp ?? request.user?.appid ?? 'https://api.botframework.com'
332
325
  // if Delivery Mode == ExpectReplies, we don't need a connector client.
333
326
  if (this.resolveIfConnectorClientIsNeeded(activity)) {
334
- const connectorClient = await this.createConnectorClientWithIdentity(request.user!, activity, scope, headers)
327
+ const connectorClient = await this.createConnectorClientWithIdentity(request.user!, activity, headers)
335
328
  this.setConnectorClient(context, connectorClient)
336
329
  }
337
330
 
338
331
  if (!activity.isAgenticRequest()) {
339
- const userTokenClient = await this.createUserTokenClient()
332
+ const userTokenClient = await this.createUserTokenClient(request.user!)
340
333
  this.setUserTokenClient(context, userTokenClient)
341
334
  }
342
335
 
@@ -433,24 +426,29 @@ export class CloudAdapter extends BaseAdapter {
433
426
  logic: (revocableContext: TurnContext) => Promise<void>,
434
427
  isResponse: Boolean = false): Promise<void> {
435
428
  if (!reference || !reference.serviceUrl || (reference.conversation == null) || !reference.conversation.id) {
436
- throw new Error('Invalid conversation reference object')
429
+ throw new Error('continueConversation: Invalid conversation reference object')
437
430
  }
438
431
 
432
+ if (!botAppIdOrIdentity) {
433
+ throw new TypeError('continueConversation: botAppIdOrIdentity is required')
434
+ }
439
435
  const botAppId = typeof botAppIdOrIdentity === 'string' ? botAppIdOrIdentity : botAppIdOrIdentity.aud as string
440
436
 
437
+ // Only having the botId will only work against ABS or Agentic. Proactive to other agents will
438
+ // not work with just botId. Use a JwtPayload with property aud (which is botId) and appid populated.
441
439
  const identity =
442
440
  typeof botAppIdOrIdentity !== 'string'
443
441
  ? botAppIdOrIdentity
444
442
  : CloudAdapter.createIdentity(botAppId)
445
443
 
446
- const continuationActivity = Activity.getContinuationActivity(reference)
447
444
  const context = new TurnContext(this, Activity.getContinuationActivity(reference), identity)
448
- const scope = identity.azp ?? identity.appid ?? 'https://api.botframework.com'
449
- const connectorClient = await this.createConnectorClientWithIdentity(identity, continuationActivity, scope)
445
+ const connectorClient = await this.createConnectorClientWithIdentity(identity, context.activity)
450
446
  this.setConnectorClient(context, connectorClient)
451
447
 
452
- const userTokenClient = await this.createUserTokenClient()
453
- this.setUserTokenClient(context, userTokenClient)
448
+ if (!context.activity.isAgenticRequest()) {
449
+ const userTokenClient = await this.createUserTokenClient(identity)
450
+ this.setUserTokenClient(context, userTokenClient)
451
+ }
454
452
 
455
453
  await this.runMiddleware(context, logic)
456
454
  }
@@ -544,8 +542,9 @@ export class CloudAdapter extends BaseAdapter {
544
542
  if (!conversationParameters) throw new TypeError('`conversationParameters` must be defined')
545
543
  if (!logic) throw new TypeError('`logic` must be defined')
546
544
 
547
- const restClient = await this.createConnectorClient(serviceUrl, audience, audience)
548
- const userTokenClient = await this.createUserTokenClient()
545
+ const identity = CloudAdapter.createIdentity(audience)
546
+ const restClient = await this.createConnectorClient(serviceUrl, audience, identity)
547
+ const userTokenClient = await this.createUserTokenClient(identity)
549
548
  const createConversationResult = await restClient.createConversation(conversationParameters)
550
549
  const createActivity = this.createCreateActivity(
551
550
  createConversationResult.id,
@@ -117,10 +117,6 @@ export class ConnectorClient {
117
117
  const axiosInstance = axios.create({
118
118
  baseURL,
119
119
  headers: headerPropagation.outgoing,
120
- transformRequest: [
121
- (data, headers) => {
122
- return JSON.stringify(normalizeOutgoingActivity(data))
123
- }]
124
120
  })
125
121
 
126
122
  if (token && token.length > 1) {
@@ -166,7 +162,10 @@ export class ConnectorClient {
166
162
  * @returns The conversation resource response.
167
163
  */
168
164
  public async createConversation (body: ConversationParameters): Promise<ConversationResourceResponse> {
169
- const payload = normalizeOutgoingActivity(body)
165
+ const payload = {
166
+ ...body,
167
+ activity: normalizeOutgoingActivity(body.activity)
168
+ }
170
169
  const config: AxiosRequestConfig = {
171
170
  method: 'post',
172
171
  url: '/v3/conversations',
@@ -211,6 +210,12 @@ export class ConnectorClient {
211
210
  return response.data
212
211
  }
213
212
 
213
+ /**
214
+ * Trim the conversationId to a fixed length when creating the URL. This is applied only in specific API calls for agentic calls.
215
+ * @param conversationId The ID of the conversation to potentially truncate.
216
+ * @param activity The activity object used to determine if truncation is necessary.
217
+ * @returns The original or truncated conversationId, depending on the channel and activity role.
218
+ */
214
219
  private conditionallyTruncateConversationId (conversationId: string, activity: Activity): string {
215
220
  if (
216
221
  (activity.channelIdChannel === Channels.Msteams || activity.channelIdChannel === Channels.Agents) &&
@@ -275,7 +280,7 @@ export class ConnectorClient {
275
280
  headers: {
276
281
  'Content-Type': 'application/json'
277
282
  },
278
- data: body
283
+ data: normalizeOutgoingActivity(body)
279
284
  }
280
285
  const response = await this._axiosInstance(config)
281
286
  return response.data
@@ -0,0 +1,5 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved.
2
+ // Licensed under the MIT License.
3
+ export const getTokenServiceEndpoint = (): string => {
4
+ return process.env.TOKEN_SERVICE_ENDPOINT ?? 'https://api.botframework.com'
5
+ }
@@ -4,11 +4,12 @@
4
4
  import axios, { AxiosInstance } from 'axios'
5
5
  import { Activity, ConversationReference } from '@microsoft/agents-activity'
6
6
  import { debug } from '@microsoft/agents-activity/logger'
7
- import { normalizeOutgoingActivity, normalizeTokenExchangeState } from '../activityWireCompat'
7
+ import { normalizeTokenExchangeState } from '../activityWireCompat'
8
8
  import { AadResourceUrls, SignInResource, TokenExchangeRequest, TokenOrSinginResourceResponse, TokenResponse, TokenStatus } from './userTokenClient.types'
9
9
  import { getProductInfo } from '../getProductInfo'
10
10
  import { AuthProvider, MsalTokenProvider } from '../auth'
11
11
  import { HeaderPropagationCollection } from '../headerPropagation'
12
+ import { getTokenServiceEndpoint } from './customUserTokenAPI'
12
13
 
13
14
  const logger = debug('agents:user-token-client')
14
15
 
@@ -31,7 +32,7 @@ export class UserTokenClient {
31
32
 
32
33
  constructor (param: string | AxiosInstance) {
33
34
  if (typeof param === 'string') {
34
- const baseURL = 'https://api.botframework.com'
35
+ const baseURL = getTokenServiceEndpoint()
35
36
  this.client = axios.create({
36
37
  baseURL,
37
38
  headers: {
@@ -116,11 +117,6 @@ export class UserTokenClient {
116
117
  'Content-Type': 'application/json', // Required by transformRequest
117
118
  'User-Agent': getProductInfo(),
118
119
  },
119
- transformRequest: [
120
- (data, headers) => {
121
- return JSON.stringify(normalizeOutgoingActivity(data))
122
- },
123
- ],
124
120
  })
125
121
  const token = await (authProvider as MsalTokenProvider).getAccessToken(scope)
126
122
  if (token.length > 1) {