@microsoft/agents-hosting 1.6.0-beta.9.gce9d8facd2 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) 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/teamsAttachmentDownloader.js +6 -14
  22. package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
  23. package/dist/src/auth/authConfiguration.d.ts +2 -143
  24. package/dist/src/auth/authConfiguration.js +301 -257
  25. package/dist/src/auth/authConfiguration.js.map +1 -1
  26. package/dist/src/auth/jwt-middleware.js +2 -1
  27. package/dist/src/auth/jwt-middleware.js.map +1 -1
  28. package/dist/src/auth/msalConnectionManager.js +27 -14
  29. package/dist/src/auth/msalConnectionManager.js.map +1 -1
  30. package/dist/src/auth/msalTokenProvider.d.ts +7 -0
  31. package/dist/src/auth/msalTokenProvider.js +188 -44
  32. package/dist/src/auth/msalTokenProvider.js.map +1 -1
  33. package/dist/src/auth/settings.d.ts +327 -0
  34. package/dist/src/auth/settings.js +158 -0
  35. package/dist/src/auth/settings.js.map +1 -0
  36. package/dist/src/cloudAdapter.d.ts +58 -1
  37. package/dist/src/cloudAdapter.js +232 -52
  38. package/dist/src/cloudAdapter.js.map +1 -1
  39. package/dist/src/connector-client/connectorClient.d.ts +12 -8
  40. package/dist/src/connector-client/connectorClient.js +99 -82
  41. package/dist/src/connector-client/connectorClient.js.map +1 -1
  42. package/dist/src/errorHelper.js +85 -7
  43. package/dist/src/errorHelper.js.map +1 -1
  44. package/dist/src/getProductInfo.d.ts +23 -0
  45. package/dist/src/getProductInfo.js +71 -1
  46. package/dist/src/getProductInfo.js.map +1 -1
  47. package/dist/src/headerPropagation.d.ts +17 -2
  48. package/dist/src/headerPropagation.js +46 -24
  49. package/dist/src/headerPropagation.js.map +1 -1
  50. package/dist/src/httpClient.d.ts +60 -0
  51. package/dist/src/httpClient.js +173 -0
  52. package/dist/src/httpClient.js.map +1 -0
  53. package/dist/src/index.d.ts +2 -1
  54. package/dist/src/index.js +7 -2
  55. package/dist/src/index.js.map +1 -1
  56. package/dist/src/oauth/userTokenClient.d.ts +5 -4
  57. package/dist/src/oauth/userTokenClient.js +97 -78
  58. package/dist/src/oauth/userTokenClient.js.map +1 -1
  59. package/dist/src/observability/traces.d.ts +11 -4
  60. package/dist/src/observability/traces.js +39 -7
  61. package/dist/src/observability/traces.js.map +1 -1
  62. package/dist/src/utils/env.d.ts +50 -0
  63. package/dist/src/utils/env.js +113 -0
  64. package/dist/src/utils/env.js.map +1 -0
  65. package/dist/src/utils.d.ts +10 -0
  66. package/dist/src/utils.js +18 -0
  67. package/dist/src/utils.js.map +1 -0
  68. package/package.json +4 -9
  69. package/src/agent-client/agentClient.ts +2 -2
  70. package/src/agent-client/agentResponseHandler.ts +2 -2
  71. package/src/app/agentApplication.ts +30 -3
  72. package/src/app/agentApplicationOptions.ts +11 -0
  73. package/src/app/attachmentDownloader.ts +6 -8
  74. package/src/app/auth/authorizationManager.ts +84 -41
  75. package/src/app/auth/handlers/azureBotAuthorization.ts +2 -2
  76. package/src/app/proactive/createConversationOptionsBuilder.ts +8 -4
  77. package/src/app/proactive/proactive.ts +39 -23
  78. package/src/app/teamsAttachmentDownloader.ts +7 -11
  79. package/src/auth/authConfiguration.ts +317 -356
  80. package/src/auth/jwt-middleware.ts +1 -1
  81. package/src/auth/msalConnectionManager.ts +27 -10
  82. package/src/auth/msalTokenProvider.ts +180 -42
  83. package/src/auth/settings.ts +323 -0
  84. package/src/cloudAdapter.ts +291 -12
  85. package/src/connector-client/connectorClient.ts +116 -94
  86. package/src/errorHelper.ts +96 -7
  87. package/src/getProductInfo.ts +79 -1
  88. package/src/headerPropagation.ts +55 -23
  89. package/src/httpClient.ts +223 -0
  90. package/src/index.ts +5 -1
  91. package/src/oauth/userTokenClient.ts +103 -84
  92. package/src/observability/traces.ts +39 -8
  93. package/src/utils/env.ts +105 -0
  94. package/src/utils.ts +14 -0
@@ -1,5 +1,8 @@
1
- /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */
2
- import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
3
6
  import { AuthConfiguration } from '../auth/authConfiguration'
4
7
  import { AuthProvider } from '../auth/authProvider'
5
8
  import { debug } from '@microsoft/agents-telemetry'
@@ -11,72 +14,47 @@ import { ResourceResponse } from './resourceResponse'
11
14
  import { AttachmentInfo } from './attachmentInfo'
12
15
  import { AttachmentData } from './attachmentData'
13
16
  import { normalizeOutgoingActivity } from '../activityWireCompat'
14
- import { getProductInfo } from '../getProductInfo'
17
+ import { applyUserAgentHeader, getProductInfo } from '../getProductInfo'
15
18
  import { HeaderPropagation, HeaderPropagationCollection } from '../headerPropagation'
16
19
  import { trace } from '@microsoft/agents-telemetry'
17
20
  import { ConnectorClientTraceDefinitions } from '../observability'
21
+ import { parseIntEnv } from '../utils/env'
22
+ import { HttpClient, HttpRequestConfig, HttpResponse, HttpError } from '../httpClient'
18
23
 
19
24
  const logger = debug('agents:connector-client')
20
25
 
26
+ function formatHttpErrorMessage (error: HttpError): string {
27
+ const responseData = error.response?.data
28
+ if (responseData === undefined) {
29
+ return error.message
30
+ }
31
+
32
+ try {
33
+ const serializedResponseData = JSON.stringify(responseData)
34
+ return serializedResponseData === undefined ? error.message : `${error.message}: ${serializedResponseData}`
35
+ } catch {
36
+ return error.message
37
+ }
38
+ }
39
+
21
40
  export { getProductInfo }
22
41
 
23
42
  /**
24
43
  * ConnectorClient is a client for interacting with the Microsoft Connector API.
25
44
  */
26
45
  export class ConnectorClient {
27
- protected readonly _axiosInstance: AxiosInstance
46
+ protected readonly _httpClient: HttpClient
28
47
 
29
48
  /**
30
49
  * Private constructor for the ConnectorClient.
31
- * @param axInstance - The AxiosInstance to use for HTTP requests.
50
+ * @param httpClient - The HttpClient instance to use for HTTP requests.
32
51
  */
33
- protected constructor (axInstance: AxiosInstance) {
34
- this._axiosInstance = axInstance
35
- this._axiosInstance.interceptors.request.use((config) => {
36
- const { method, url, data, headers, params } = config
37
- // Clone headers and remove Authorization before logging
38
- const { Authorization, authorization, ...headersToLog } = headers || {}
39
- logger.debug('Request: ', {
40
- host: this._axiosInstance.getUri(),
41
- url,
42
- data,
43
- method,
44
- params,
45
- headers: headersToLog
46
- })
47
- return config
48
- })
49
- this._axiosInstance.interceptors.response.use(
50
- (config) => {
51
- const { status, statusText, config: requestConfig } = config
52
- logger.debug('Response: ', {
53
- status,
54
- statusText,
55
- host: this._axiosInstance.getUri(),
56
- url: requestConfig?.url,
57
- data: config.config.data,
58
- method: requestConfig?.method,
59
- })
60
- return config
61
- },
62
- (error) => {
63
- const { code, message, stack, response } = error
64
- const errorDetails = {
65
- code,
66
- host: this._axiosInstance.getUri(),
67
- url: error.config.url,
68
- method: error.config.method,
69
- data: error.config.data,
70
- message: message + JSON.stringify(response?.data),
71
- stack,
72
- }
73
- return Promise.reject(errorDetails)
74
- }
75
- )
52
+ protected constructor (httpClient: HttpClient) {
53
+ this._httpClient = httpClient
76
54
  }
77
55
 
78
- public get axiosInstance (): AxiosInstance {
79
- return this._axiosInstance
56
+ public get httpClient (): HttpClient {
57
+ return this._httpClient
80
58
  }
81
59
 
82
60
  /**
@@ -112,28 +90,23 @@ export class ConnectorClient {
112
90
  headers?: HeaderPropagationCollection
113
91
  ): ConnectorClient {
114
92
  const headerPropagation = headers ?? new HeaderPropagation({})
115
- const userAgent = headerPropagation.outgoing['user-agent']
116
- const productInfo = getProductInfo()
117
- if (!userAgent) {
118
- headerPropagation.add({ 'User-Agent': productInfo })
119
- } else if (!userAgent.includes(productInfo)) {
120
- headerPropagation.concat({ 'User-Agent': productInfo })
121
- }
93
+ applyUserAgentHeader(headerPropagation)
122
94
  headerPropagation.override({
123
95
  Accept: 'application/json',
124
- 'Content-Type': 'application/json', // Required by transformRequest
125
- })
126
-
127
- const axiosInstance = axios.create({
128
- baseURL,
129
- headers: headerPropagation.outgoing,
96
+ 'Content-Type': 'application/json',
130
97
  })
131
98
 
99
+ const clientHeaders: Record<string, string> = { ...headerPropagation.outgoing }
132
100
  if (token && token.length > 1) {
133
- axiosInstance.defaults.headers.common.Authorization = `Bearer ${token}`
101
+ clientHeaders.Authorization = `Bearer ${token}`
134
102
  }
135
103
 
136
- return new ConnectorClient(axiosInstance)
104
+ const httpClient = new HttpClient({
105
+ baseURL,
106
+ headers: clientHeaders,
107
+ })
108
+
109
+ return new ConnectorClient(httpClient)
137
110
  }
138
111
 
139
112
  /**
@@ -143,12 +116,12 @@ export class ConnectorClient {
143
116
  */
144
117
  public async getConversations (continuationToken?: string): Promise<ConversationsResult> {
145
118
  return trace(ConnectorClientTraceDefinitions.getConversations, async ({ record }) => {
146
- const config: AxiosRequestConfig = {
119
+ const config: HttpRequestConfig = {
147
120
  method: 'get',
148
121
  url: '/v3/conversations',
149
122
  params: continuationToken ? { continuationToken } : undefined
150
123
  }
151
- const response = await this._axiosInstance(config)
124
+ const response = await this.executeRequest<ConversationsResult>(config)
152
125
  record({ httpStatusCode: response.status?.toString() })
153
126
  return response.data
154
127
  })
@@ -159,14 +132,14 @@ export class ConnectorClient {
159
132
  if (!userId || !conversationId) {
160
133
  throw ExceptionHelper.generateException(Error, Errors.UserIdAndConversationIdRequired)
161
134
  }
162
- const config: AxiosRequestConfig = {
135
+ const config: HttpRequestConfig = {
163
136
  method: 'get',
164
137
  url: `v3/conversations/${conversationId}/members/${userId}`,
165
138
  headers: {
166
139
  'Content-Type': 'application/json'
167
140
  }
168
141
  }
169
- const response = await this._axiosInstance(config)
142
+ const response = await this.executeRequest<ChannelAccount>(config)
170
143
  record({ httpStatusCode: response.status?.toString() })
171
144
  return response.data
172
145
  })
@@ -183,7 +156,7 @@ export class ConnectorClient {
183
156
  ...body,
184
157
  activity: normalizeOutgoingActivity(body.activity)
185
158
  }
186
- const config: AxiosRequestConfig = {
159
+ const config: HttpRequestConfig = {
187
160
  method: 'post',
188
161
  url: '/v3/conversations',
189
162
  headers: {
@@ -191,7 +164,7 @@ export class ConnectorClient {
191
164
  },
192
165
  data: payload
193
166
  }
194
- const response: AxiosResponse = await this._axiosInstance(config)
167
+ const response = await this.executeRequest<ConversationResourceResponse>(config)
195
168
  record({ httpStatusCode: response.status?.toString() })
196
169
  return response.data
197
170
  })
@@ -219,7 +192,7 @@ export class ConnectorClient {
219
192
 
220
193
  const trimmedConversationId: string = this.conditionallyTruncateConversationId(conversationId, body)
221
194
 
222
- const config: AxiosRequestConfig = {
195
+ const config: HttpRequestConfig = {
223
196
  method: 'post',
224
197
  url: `v3/conversations/${trimmedConversationId}/activities/${encodeURIComponent(activityId)}`,
225
198
  headers: {
@@ -228,7 +201,7 @@ export class ConnectorClient {
228
201
  data: normalizeOutgoingActivity(body),
229
202
  ...(body.channelId === Channels.Msteams && body.isTargetedActivity() ? { params: { isTargetedActivity: 'true' } } : {})
230
203
  }
231
- const response = await this._axiosInstance(config)
204
+ const response = await this.executeRequest<ResourceResponse>(config)
232
205
  record({ httpStatusCode: response.status?.toString() })
233
206
  logger.info('Reply to conversation/activity: ', response.data.id!, activityId)
234
207
  return response.data
@@ -236,20 +209,19 @@ export class ConnectorClient {
236
209
  }
237
210
 
238
211
  /**
239
- * Trim the conversationId to a fixed length when creating the URL. This is applied only in specific API calls for agentic calls.
212
+ * Trim and sanitize the conversationId when creating the URL for agentic calls in agents channels.
240
213
  * @param conversationId The ID of the conversation to potentially truncate.
241
214
  * @param activity The activity object used to determine if truncation is necessary.
242
- * @returns The original or truncated conversationId, depending on the channel and activity role.
215
+ * @returns The original conversationId for non-agents channels/non-agentic roles, otherwise a trimmed and sanitized value.
243
216
  */
244
217
  private conditionallyTruncateConversationId (conversationId: string, activity: Activity): string {
245
218
  if (
246
- (activity.channelIdChannel === Channels.Msteams || activity.channelIdChannel === Channels.Agents) &&
219
+ activity.channelIdChannel === Channels.Agents &&
247
220
  (activity.from?.role === RoleTypes.AgenticIdentity || activity.from?.role === RoleTypes.AgenticUser)) {
248
- let maxLength = 150
249
- if (process.env.MAX_APX_CONVERSATION_ID_LENGTH && !isNaN(parseInt(process.env.MAX_APX_CONVERSATION_ID_LENGTH, 10))) {
250
- maxLength = parseInt(process.env.MAX_APX_CONVERSATION_ID_LENGTH, 10)
251
- }
252
- return conversationId.length > maxLength ? conversationId.substring(0, maxLength) : conversationId
221
+ const maxLength = parseIntEnv(process.env.MAX_APX_CONVERSATION_ID_LENGTH, 150)
222
+ const trimmedConversationId = conversationId.length > maxLength ? conversationId.substring(0, maxLength) : conversationId
223
+
224
+ return trimmedConversationId.replace(/[/\\#?]/g, '_')
253
225
  } else {
254
226
  return conversationId
255
227
  }
@@ -275,7 +247,7 @@ export class ConnectorClient {
275
247
 
276
248
  const trimmedConversationId: string = this.conditionallyTruncateConversationId(conversationId, body)
277
249
 
278
- const config: AxiosRequestConfig = {
250
+ const config: HttpRequestConfig = {
279
251
  method: 'post',
280
252
  url: `v3/conversations/${trimmedConversationId}/activities`,
281
253
  headers: {
@@ -285,7 +257,7 @@ export class ConnectorClient {
285
257
  ...(body.channelId === Channels.Msteams && body.isTargetedActivity() ? { params: { isTargetedActivity: 'true' } } : {})
286
258
 
287
259
  }
288
- const response = await this._axiosInstance(config)
260
+ const response = await this.executeRequest<ResourceResponse>(config)
289
261
  record({ httpStatusCode: response.status?.toString() })
290
262
  return response.data
291
263
  })
@@ -308,7 +280,7 @@ export class ConnectorClient {
308
280
  throw ExceptionHelper.generateException(Error, Errors.ConversationIdAndActivityIdRequired)
309
281
  }
310
282
  record({ conversationId, activityId })
311
- const config: AxiosRequestConfig = {
283
+ const config: HttpRequestConfig = {
312
284
  method: 'put',
313
285
  url: `v3/conversations/${conversationId}/activities/${activityId}`,
314
286
  headers: {
@@ -317,7 +289,7 @@ export class ConnectorClient {
317
289
  data: normalizeOutgoingActivity(body),
318
290
  ...(body.channelId === Channels.Msteams && body.isTargetedActivity() ? { params: { isTargetedActivity: 'true' } } : {})
319
291
  }
320
- const response = await this._axiosInstance(config)
292
+ const response = await this.executeRequest<ResourceResponse>(config)
321
293
  record({ httpStatusCode: response.status?.toString() })
322
294
  return response.data
323
295
  })
@@ -341,7 +313,7 @@ export class ConnectorClient {
341
313
  if (!conversationId || !activityId) {
342
314
  throw ExceptionHelper.generateException(Error, Errors.ConversationIdAndActivityIdRequired)
343
315
  }
344
- const config: AxiosRequestConfig = {
316
+ const config: HttpRequestConfig = {
345
317
  method: 'delete',
346
318
  url: `v3/conversations/${conversationId}/activities/${activityId}`,
347
319
  headers: {
@@ -349,7 +321,7 @@ export class ConnectorClient {
349
321
  },
350
322
  ...(isTargetedActivity ? { params: { isTargetedActivity: 'true' } } : {})
351
323
  }
352
- const response = await this._axiosInstance(config)
324
+ const response = await this.executeRequest<void>(config)
353
325
  record({ httpStatusCode: response.status?.toString() })
354
326
  return response.data
355
327
  })
@@ -370,7 +342,7 @@ export class ConnectorClient {
370
342
  if (conversationId === undefined) {
371
343
  throw ExceptionHelper.generateException(Error, Errors.ConversationIdRequired)
372
344
  }
373
- const config: AxiosRequestConfig = {
345
+ const config: HttpRequestConfig = {
374
346
  method: 'post',
375
347
  url: `v3/conversations/${conversationId}/attachments`,
376
348
  headers: {
@@ -378,7 +350,7 @@ export class ConnectorClient {
378
350
  },
379
351
  data: body
380
352
  }
381
- const response = await this._axiosInstance(config)
353
+ const response = await this.executeRequest<ResourceResponse>(config)
382
354
  record({ httpStatusCode: response.status?.toString() })
383
355
  return response.data
384
356
  })
@@ -397,14 +369,14 @@ export class ConnectorClient {
397
369
  if (attachmentId === undefined) {
398
370
  throw ExceptionHelper.generateException(Error, Errors.AttachmentIdRequired)
399
371
  }
400
- const config: AxiosRequestConfig = {
372
+ const config: HttpRequestConfig = {
401
373
  method: 'get',
402
374
  url: `v3/attachments/${attachmentId}`,
403
375
  headers: {
404
376
  'Content-Type': 'application/json'
405
377
  }
406
378
  }
407
- const response = await this._axiosInstance(config)
379
+ const response = await this.executeRequest<AttachmentInfo>(config)
408
380
  record({ httpStatusCode: response.status?.toString() })
409
381
  return response.data
410
382
  })
@@ -428,16 +400,66 @@ export class ConnectorClient {
428
400
  if (viewId === undefined) {
429
401
  throw ExceptionHelper.generateException(Error, Errors.ViewIdRequired)
430
402
  }
431
- const config: AxiosRequestConfig = {
403
+ const config: HttpRequestConfig = {
432
404
  method: 'get',
433
405
  url: `v3/attachments/${attachmentId}/views/${viewId}`,
434
406
  headers: {
435
407
  'Content-Type': 'application/json'
436
- }
408
+ },
409
+ responseType: 'stream'
437
410
  }
438
- const response = await this._axiosInstance(config)
411
+ const response = await this.executeRequest<NodeJS.ReadableStream>(config)
439
412
  record({ httpStatusCode: response.status?.toString() })
440
413
  return response.data
441
414
  })
442
415
  }
416
+
417
+ private async executeRequest<T = unknown> (config: HttpRequestConfig): Promise<HttpResponse<T>> {
418
+ const { method, url, data, headers, params } = config
419
+ const { Authorization, authorization, ...headersToLog } = { ...this._httpClient.defaultHeaders, ...headers } as Record<string, string>
420
+ logger.debug('Request: ', {
421
+ host: this._httpClient.baseURL,
422
+ url,
423
+ data,
424
+ method,
425
+ params,
426
+ headers: headersToLog
427
+ })
428
+
429
+ try {
430
+ const response = await this._httpClient.request<T>(config)
431
+ logger.debug('Response: ', {
432
+ status: response.status,
433
+ statusText: response.statusText,
434
+ host: this._httpClient.baseURL,
435
+ url: response.config?.url,
436
+ data: response.config?.data,
437
+ method: response.config?.method,
438
+ })
439
+ return response
440
+ } catch (error) {
441
+ if (error instanceof HttpError) {
442
+ const message = formatHttpErrorMessage(error)
443
+ logger.debug('Response error: ', {
444
+ host: this._httpClient.baseURL,
445
+ url: error.config.url,
446
+ method: error.config.method,
447
+ data: error.config.data,
448
+ message,
449
+ stack: error.stack,
450
+ })
451
+
452
+ Object.assign(error, {
453
+ host: this._httpClient.baseURL,
454
+ url: error.config.url,
455
+ method: error.config.method,
456
+ data: error.config.data,
457
+ message,
458
+ })
459
+
460
+ throw error
461
+ }
462
+ throw error
463
+ }
464
+ }
443
465
  }
@@ -256,6 +256,14 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
256
256
  description: 'request.body parameter required'
257
257
  },
258
258
 
259
+ /**
260
+ * Error thrown when HttpClient is given a relative URL without a configured base URL.
261
+ */
262
+ HttpClientRelativeUrlRequiresBaseUrl: {
263
+ code: -120291,
264
+ description: 'HttpClient requires baseURL when using a relative URL: {url}'
265
+ },
266
+
259
267
  // ============================================================================
260
268
  // Connection and Authentication Errors (-120300 to -120590)
261
269
  // ============================================================================
@@ -516,8 +524,64 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
516
524
  description: "The current token for the '{connectionName}' AzureBot connection is not exchangeable for an on-behalf-of flow. Ensure the base token audience is for the bot/resource app, such as an App ID URI like 'api://...' or otherwise includes the app's client id."
517
525
  },
518
526
 
527
+ /**
528
+ * Error thrown when an unsupported authentication type is specified in authConfig.
529
+ */
530
+ UnsupportedAuthType: {
531
+ code: -120593,
532
+ description: 'Unsupported authentication type: {authType}'
533
+ },
534
+
535
+ /**
536
+ * Error thrown when WorkloadIdentity authentication requires `federatedTokenFile` or the deprecated `WIDAssertionFile` to be configured.
537
+ */
538
+ WorkloadIdentityTokenFileRequired: {
539
+ code: -120594,
540
+ description: 'WorkloadIdentity authentication requires `federatedTokenFile` or the deprecated `WIDAssertionFile` to be configured'
541
+ },
542
+
543
+ /**
544
+ * Error thrown when ClientSecret authentication is specified but `clientSecret` is not configured.
545
+ */
546
+ ClientSecretRequired: {
547
+ code: -120595,
548
+ description: 'ClientSecret authentication requires `clientSecret` to be configured'
549
+ },
550
+
551
+ /**
552
+ * Error thrown when Certificate authentication is specified but `certPemFile` or `certKeyFile` is not configured.
553
+ */
554
+ CertificateFilesRequired: {
555
+ code: -120596,
556
+ description: 'Certificate authentication requires both `certPemFile` and `certKeyFile` to be configured'
557
+ },
558
+
559
+ /**
560
+ * Error thrown when FederatedCredentials authentication is specified but `federatedClientId` or the deprecated `FICClientId` is not configured.
561
+ */
562
+ FICClientIdRequired: {
563
+ code: -120597,
564
+ description: 'FederatedCredentials authentication requires `federatedClientId` or the deprecated `FICClientId` to be configured'
565
+ },
566
+
567
+ /**
568
+ * Error thrown when UserManagedIdentity authentication is specified but `clientId` is not configured.
569
+ */
570
+ ClientIdRequiredForUserManagedIdentity: {
571
+ code: -120598,
572
+ description: 'UserManagedIdentity authentication requires `clientId` to be configured'
573
+ },
574
+
575
+ /**
576
+ * Error thrown when there's a timeout during a token request.
577
+ */
578
+ TokenRequestTimeout: {
579
+ code: -120599,
580
+ description: 'Token request timed out after {timeoutMs} ms'
581
+ },
582
+
519
583
  // ============================================================================
520
- // Agent and Client Errors (-120600 to -120630)
584
+ // Agent and Client Errors (-120600 to -120650)
521
585
  // ============================================================================
522
586
 
523
587
  /**
@@ -537,18 +601,34 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
537
601
  },
538
602
 
539
603
  /**
540
- * Error thrown when failed to post activity to agent.
541
- */
542
- FailedToPostActivityToAgent: {
604
+ * Error thrown when agent ID is required.
605
+ */
606
+ AgentIdRequired: {
543
607
  code: -120620,
608
+ description: 'Agent ID is required to apply outbound agent headers'
609
+ },
610
+
611
+ /**
612
+ * Error thrown when agent name contains invalid characters.
613
+ */
614
+ AgentNameInvalid: {
615
+ code: -120630,
616
+ description: 'Agent name contains invalid characters: {agentName}'
617
+ },
618
+
619
+ /**
620
+ * Error thrown when failed to post activity to agent.
621
+ */
622
+ FailedToPostActivityToAgent: {
623
+ code: -120640,
544
624
  description: 'Failed to post activity to agent: {statusText}'
545
625
  },
546
626
 
547
627
  /**
548
- * Error thrown when logic parameter must be defined.
549
- */
628
+ * Error thrown when logic parameter must be defined.
629
+ */
550
630
  LogicParameterRequired: {
551
- code: -120630,
631
+ code: -120650,
552
632
  description: 'logic must be defined'
553
633
  },
554
634
 
@@ -784,6 +864,15 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
784
864
  description: 'This proactive operation requires app storage to load and save TurnState. Set options.storage on AgentApplication.'
785
865
  },
786
866
 
867
+ /**
868
+ * Error thrown when CreateConversationOptionsBuilder.build() is called for a Teams channel
869
+ * conversation (withTeamsChannelId) without an activity set via withActivity().
870
+ */
871
+ CreateConversationBuilderChannelActivityRequired: {
872
+ code: -120753,
873
+ description: 'CreateConversationOptionsBuilder: Teams channel conversations require an initial activity. Call withActivity() before build().'
874
+ },
875
+
787
876
  // ============================================================================
788
877
  // General Errors (-120990)
789
878
  // ============================================================================
@@ -1,5 +1,15 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved.
2
+ // Licensed under the MIT License.
3
+
1
4
  import { version } from '../package.json'
2
5
  import os from 'os'
6
+ import { Activity, ExceptionHelper } from '@microsoft/agents-activity'
7
+ import { Errors } from './errorHelper'
8
+ import { HeaderPropagationCollection } from './headerPropagation'
9
+
10
+ const AGENT_NAME_PATTERN = /^[A-Za-z0-9 _-]+$/
11
+ const AGENT_REGISTRAR = 'A365'
12
+ const DEFAULT_AGENT_NAME = 'Agents-SDK-JS'
3
13
 
4
14
  /**
5
15
  * Generates a string containing information about the SDK version and runtime environment.
@@ -7,4 +17,72 @@ import os from 'os'
7
17
  *
8
18
  * @returns A formatted string containing the SDK version, Node.js version, and OS details
9
19
  */
10
- export const getProductInfo = () : string => `agents-sdk-js/${version} nodejs/${process.version} ${os.platform()}-${os.arch()}/${os.release()}`
20
+ export const getProductInfo = () : string => `${DEFAULT_AGENT_NAME.toLowerCase()}/${version} nodejs/${process.version} ${os.platform()}-${os.arch()}/${os.release()}`
21
+
22
+ /**
23
+ * Applies the SDK product information to the User-Agent header of outgoing requests.
24
+ * @param headers The HeaderPropagationCollection to which the User-Agent header will be added or updated
25
+ */
26
+ export function applyUserAgentHeader (headers: HeaderPropagationCollection): void {
27
+ const userAgentKey = headers.key?.('User-Agent') ?? 'User-Agent'
28
+ const userAgent = headers.outgoing[userAgentKey] ?? headers.incoming[userAgentKey]
29
+ const productInfo = getProductInfo()
30
+ if (!userAgent) {
31
+ headers.add({ [userAgentKey]: productInfo })
32
+ } else if (!userAgent.includes(productInfo)) {
33
+ headers.concat({ [userAgentKey]: productInfo })
34
+ } else {
35
+ headers.propagate([userAgentKey])
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Applies standardized agent-related headers for agentic requests.
41
+ *
42
+ * @param headers The HeaderPropagationCollection to which the agent headers will be applied.
43
+ * @param activity The incoming Activity containing agentic request information.
44
+ * @param agentName An optional human-friendly name for the agent, which will be validated and included in the headers.
45
+ *
46
+ * @remarks
47
+ * The function sets the following headers:
48
+ * - `AgentRegistrar`: A fixed value indicating the registrar of the agent (e.g., "A365").
49
+ * - `AgentName`: A human-friendly name for the agent, validated against a pattern to ensure it only contains allowed characters.
50
+ * - `AgentID`: A unique identifier for the agent instance, resolved from the activity's agentic instance ID or agentic user.
51
+ * - `Agent-Referrer`: The channel ID from the incoming activity, indicating the source of the request.
52
+ * The function ensures that the agent headers are consistently applied to outgoing requests for proper identification and tracing.
53
+ */
54
+ export function applyAgenticHeaders (
55
+ headers: HeaderPropagationCollection,
56
+ activity: Activity,
57
+ agentName?: string
58
+ ): void {
59
+ headers.override({
60
+ AgentRegistrar: AGENT_REGISTRAR,
61
+ AgentName: normalizeAgentName(agentName),
62
+ AgentID: resolveAgentId(activity),
63
+ 'Agent-Referrer': activity.channelId ?? '',
64
+ })
65
+ }
66
+
67
+ function resolveAgentId (activity: Activity): string {
68
+ const agentId = activity.getAgenticInstanceId() ?? activity.getAgenticUser()
69
+
70
+ if (!agentId) {
71
+ throw ExceptionHelper.generateException(Error, Errors.AgentIdRequired)
72
+ }
73
+
74
+ return agentId
75
+ }
76
+
77
+ function normalizeAgentName (agentName?: string): string {
78
+ const normalizedAgentName = agentName?.trim()
79
+ if (!normalizedAgentName) {
80
+ return DEFAULT_AGENT_NAME
81
+ }
82
+
83
+ if (!AGENT_NAME_PATTERN.test(normalizedAgentName)) {
84
+ throw ExceptionHelper.generateException(Error, Errors.AgentNameInvalid, undefined, { agentName: normalizedAgentName })
85
+ }
86
+
87
+ return normalizedAgentName
88
+ }