@microsoft/agents-hosting 1.7.1 → 1.8.0-beta.34.g5d543f96c0

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 (100) hide show
  1. package/README.md +64 -2
  2. package/dist/package.json +3 -3
  3. package/dist/src/agent-client/agentClient.d.ts +18 -6
  4. package/dist/src/agent-client/agentClient.js +34 -18
  5. package/dist/src/agent-client/agentClient.js.map +1 -1
  6. package/dist/src/agent-client/agentResponseHandler.d.ts +14 -9
  7. package/dist/src/agent-client/agentResponseHandler.js +10 -5
  8. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  9. package/dist/src/agent-client/createAgentResponseHandler.d.ts +7 -2
  10. package/dist/src/agent-client/createAgentResponseHandler.js +55 -8
  11. package/dist/src/agent-client/createAgentResponseHandler.js.map +1 -1
  12. package/dist/src/app/adaptiveCards/activityValueParsers.d.ts +1 -4
  13. package/dist/src/app/adaptiveCards/activityValueParsers.js +2 -6
  14. package/dist/src/app/adaptiveCards/activityValueParsers.js.map +1 -1
  15. package/dist/src/app/adaptiveCards/adaptiveCardsActions.d.ts +18 -0
  16. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +53 -23
  17. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
  18. package/dist/src/app/agentApplication.d.ts +7 -0
  19. package/dist/src/app/agentApplication.js +130 -70
  20. package/dist/src/app/agentApplication.js.map +1 -1
  21. package/dist/src/app/attachmentDownloader.d.ts +4 -1
  22. package/dist/src/app/attachmentDownloader.js +27 -5
  23. package/dist/src/app/attachmentDownloader.js.map +1 -1
  24. package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +1 -1
  25. package/dist/src/app/auth/handlers/azureBotAuthorization.d.ts +1 -1
  26. package/dist/src/app/auth/types.d.ts +3 -4
  27. package/dist/src/app/index.d.ts +2 -0
  28. package/dist/src/app/index.js.map +1 -1
  29. package/dist/src/app/streaming/streamingResponse.js +2 -9
  30. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  31. package/dist/src/app/teamsAttachmentDownloader.d.ts +3 -1
  32. package/dist/src/app/teamsAttachmentDownloader.js +12 -1
  33. package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
  34. package/dist/src/auth/authConfiguration.js +12 -0
  35. package/dist/src/auth/authConfiguration.js.map +1 -1
  36. package/dist/src/auth/index.d.ts +3 -1
  37. package/dist/src/auth/index.js +3 -1
  38. package/dist/src/auth/index.js.map +1 -1
  39. package/dist/src/auth/jwt-middleware.d.ts +6 -0
  40. package/dist/src/auth/jwt-middleware.js +263 -13
  41. package/dist/src/auth/jwt-middleware.js.map +1 -1
  42. package/dist/src/auth/msal/msalConnectionSettings.d.ts +8 -0
  43. package/dist/src/auth/msal/msalHttpRetryHandlerHelper.d.ts +34 -0
  44. package/dist/src/auth/msal/msalHttpRetryHandlerHelper.js +126 -0
  45. package/dist/src/auth/msal/msalHttpRetryHandlerHelper.js.map +1 -0
  46. package/dist/src/auth/msal/msalTokenProvider.d.ts +1 -0
  47. package/dist/src/auth/msal/msalTokenProvider.js +23 -14
  48. package/dist/src/auth/msal/msalTokenProvider.js.map +1 -1
  49. package/dist/src/auth/settings.d.ts +18 -0
  50. package/dist/src/auth/settings.js +8 -2
  51. package/dist/src/auth/settings.js.map +1 -1
  52. package/dist/src/cloudAdapter.d.ts +29 -2
  53. package/dist/src/cloudAdapter.js +44 -2
  54. package/dist/src/cloudAdapter.js.map +1 -1
  55. package/dist/src/errorHelper.js +137 -101
  56. package/dist/src/errorHelper.js.map +1 -1
  57. package/dist/src/index.d.ts +1 -0
  58. package/dist/src/index.js +1 -0
  59. package/dist/src/index.js.map +1 -1
  60. package/dist/src/invoke/adaptiveCardInvokeValue.d.ts +5 -0
  61. package/dist/src/oauth/userTokenClient.d.ts +0 -1
  62. package/dist/src/oauth/userTokenClient.js +0 -1
  63. package/dist/src/oauth/userTokenClient.js.map +1 -1
  64. package/dist/src/observability/traces.js +3 -2
  65. package/dist/src/observability/traces.js.map +1 -1
  66. package/dist/src/outboundHostValidator.d.ts +47 -0
  67. package/dist/src/outboundHostValidator.js +149 -0
  68. package/dist/src/outboundHostValidator.js.map +1 -0
  69. package/dist/src/state/agentState.d.ts +5 -0
  70. package/dist/src/state/agentState.js +16 -3
  71. package/dist/src/state/agentState.js.map +1 -1
  72. package/package.json +3 -3
  73. package/src/agent-client/agentClient.ts +46 -23
  74. package/src/agent-client/agentResponseHandler.ts +14 -9
  75. package/src/agent-client/createAgentResponseHandler.ts +70 -9
  76. package/src/app/adaptiveCards/activityValueParsers.ts +3 -10
  77. package/src/app/adaptiveCards/adaptiveCardsActions.ts +52 -24
  78. package/src/app/agentApplication.ts +143 -67
  79. package/src/app/attachmentDownloader.ts +25 -5
  80. package/src/app/auth/handlers/agenticAuthorization.ts +1 -1
  81. package/src/app/auth/handlers/azureBotAuthorization.ts +1 -1
  82. package/src/app/auth/types.ts +3 -3
  83. package/src/app/index.ts +8 -0
  84. package/src/app/streaming/streamingResponse.ts +2 -8
  85. package/src/app/teamsAttachmentDownloader.ts +11 -1
  86. package/src/auth/authConfiguration.ts +12 -0
  87. package/src/auth/index.ts +3 -1
  88. package/src/auth/jwt-middleware.ts +298 -14
  89. package/src/auth/msal/msalConnectionSettings.ts +10 -0
  90. package/src/auth/msal/msalHttpRetryHandlerHelper.ts +161 -0
  91. package/src/auth/msal/msalTokenProvider.ts +32 -14
  92. package/src/auth/settings.ts +18 -3
  93. package/src/cloudAdapter.ts +56 -3
  94. package/src/errorHelper.ts +155 -114
  95. package/src/index.ts +1 -0
  96. package/src/invoke/adaptiveCardInvokeValue.ts +5 -0
  97. package/src/oauth/userTokenClient.ts +0 -1
  98. package/src/observability/traces.ts +2 -1
  99. package/src/outboundHostValidator.ts +172 -0
  100. package/src/state/agentState.ts +24 -6
@@ -14,11 +14,18 @@ import { normalizeIncomingActivity } from '../activityWireCompat'
14
14
  import { Errors } from '../errorHelper'
15
15
  import { debug } from '@microsoft/agents-telemetry'
16
16
  import { ConversationState } from '../state'
17
+ import { getAuthorizedAudience } from '../auth/jwt-middleware'
17
18
 
18
19
  const logger = debug('agents:agent-client')
19
20
 
20
21
  interface ConversationReferenceState {
21
22
  conversationReference: ConversationReference
23
+ expectedAgentClientId?: string
24
+ }
25
+
26
+ interface DelegatedConversationState {
27
+ conversationReference: ConversationReference
28
+ expectedAgentClientId: string
22
29
  }
23
30
 
24
31
  /**
@@ -31,7 +38,7 @@ export interface AgentResponseHandlerParams {
31
38
  }
32
39
 
33
40
  /**
34
- * Framework-agnostic handler signature for the agent response controller endpoint.
41
+ * Framework-agnostic handler signature for the SDK-specific Activity callback endpoint.
35
42
  *
36
43
  * @remarks
37
44
  * The handler is intended to be invoked by a thin framework-specific wrapper (such
@@ -45,7 +52,7 @@ export type AgentResponseHandler = (
45
52
  ) => Promise<void>
46
53
 
47
54
  /**
48
- * Creates a framework-agnostic handler for the agent response controller endpoint.
55
+ * Creates a framework-agnostic handler for the authenticated Activity callback endpoint.
49
56
  *
50
57
  * This is the core, Express-free implementation used by:
51
58
  * - `configureResponseController` in `@microsoft/agents-hosting-express`
@@ -54,6 +61,11 @@ export type AgentResponseHandler = (
54
61
  * Both wrappers register the canonical route
55
62
  * `POST /api/agentresponse/v3/conversations/:conversationId/activities/:activityId`
56
63
  * and forward the parsed body + path parameters to the handler returned here.
64
+ * The handler returns `401` for failed JWT authentication and `403` when the
65
+ * authenticated caller does not match the delegated agent stored for the
66
+ * conversation, or when that delegated state is missing or malformed.
67
+ *
68
+ * This callback contract is SDK-specific and is not the open A2A protocol.
57
69
  *
58
70
  * @param adapter - The CloudAdapter used for processing activities and managing conversations.
59
71
  * @param agent - The ActivityHandler containing the agent logic.
@@ -65,21 +77,57 @@ export const createAgentResponseHandler = (
65
77
  agent: ActivityHandler,
66
78
  conversationState: ConversationState
67
79
  ): AgentResponseHandler => {
80
+ const appId = adapter.getClientId() ?? ''
81
+ const anonymousDevelopment = appId.length === 0 && process.env.NODE_ENV !== 'production'
82
+ if (anonymousDevelopment) {
83
+ emitAgentResponseWarning(
84
+ 'The agent-response endpoint is using anonymous authentication outside production. Callback ownership cannot be cryptographically verified; configure a client ID before deployment.'
85
+ )
86
+ }
87
+
68
88
  return async (req: Request, res: WebResponse, params: AgentResponseHandlerParams) => {
89
+ let middlewareError: any
90
+ let nextCalled = false
91
+ await adapter.authorizeRequest(req, res, (err?: any) => {
92
+ nextCalled = true
93
+ middlewareError = err
94
+ })
95
+ if (middlewareError) {
96
+ throw middlewareError
97
+ }
98
+ if (!nextCalled || res.headersSent) {
99
+ return
100
+ }
101
+
69
102
  if (!req.body) {
70
103
  throw ExceptionHelper.generateException(TypeError, Errors.MissingRequestBody)
71
104
  }
72
105
  const incoming = normalizeIncomingActivity(req.body)
73
106
  const activity = Activity.fromObject(incoming)
74
107
 
75
- logger.debug('received response: ', activity)
108
+ logger.debug('received delegated agent response')
76
109
 
77
- const connection = adapter.connectionManager.getDefaultConnection()
78
- const appId = connection?.connectionSettings?.clientId ?? ''
79
-
80
- const myTurnContext = new TurnContext(adapter, activity, CloudAdapter.createIdentity(appId))
110
+ const continuationAudience = getAuthorizedAudience(req) ?? appId
111
+ const myTurnContext = new TurnContext(adapter, activity, CloudAdapter.createIdentity(continuationAudience))
81
112
  const conversationDataAccessor = conversationState.createProperty<ConversationReferenceState>(params.conversationId)
82
- const conversationRefState = await conversationDataAccessor.get(myTurnContext, undefined, { channelId: activity.channelId!, conversationId: params.conversationId })
113
+ const incomingChannelId = activity.channelId!
114
+ const conversationRefState = await conversationDataAccessor.get(myTurnContext, undefined, { channelId: incomingChannelId, conversationId: params.conversationId })
115
+ if (!isDelegatedConversationState(conversationRefState)) {
116
+ res.status(403).send({ 'agent-response-auth-error': 'caller is not authorized for this conversation' })
117
+ return
118
+ }
119
+
120
+ const callerClientId = req.user?.azp ?? req.user?.appid
121
+ const anonymousCaller = anonymousDevelopment && req.user?.name === 'anonymous'
122
+ const delegatedAgentMatches = anonymousCaller || (
123
+ typeof callerClientId === 'string' &&
124
+ callerClientId.length > 0 &&
125
+ callerClientId.toLowerCase() === conversationRefState.expectedAgentClientId.toLowerCase()
126
+ )
127
+ if (!delegatedAgentMatches) {
128
+ res.status(403).send({ 'agent-response-auth-error': 'caller is not authorized for this conversation' })
129
+ return
130
+ }
83
131
 
84
132
  const callback = async (turnContext: TurnContext) => {
85
133
  activity.applyConversationReference(conversationRefState.conversationReference)
@@ -88,7 +136,7 @@ export const createAgentResponseHandler = (
88
136
  let response: unknown
89
137
  let responseContentType: string | undefined
90
138
  if (activity.type === ActivityTypes.EndOfConversation) {
91
- await conversationDataAccessor.delete(turnContext, { channelId: activity.channelId!, conversationId: activity.conversation!.id })
139
+ await conversationState.delete(myTurnContext, { channelId: incomingChannelId, conversationId: params.conversationId })
92
140
 
93
141
  applyActivityToTurnContext(turnContext, activity)
94
142
  await agent.run(turnContext)
@@ -101,6 +149,7 @@ export const createAgentResponseHandler = (
101
149
  } else {
102
150
  response = await turnContext.sendActivity(activity)
103
151
  }
152
+
104
153
  if (responseContentType !== undefined) {
105
154
  res.setHeader('content-type', responseContentType)
106
155
  }
@@ -111,6 +160,18 @@ export const createAgentResponseHandler = (
111
160
  }
112
161
  }
113
162
 
163
+ function isDelegatedConversationState (state: ConversationReferenceState | undefined): state is DelegatedConversationState {
164
+ return typeof state?.conversationReference === 'object' &&
165
+ state.conversationReference !== null &&
166
+ typeof state.expectedAgentClientId === 'string' &&
167
+ state.expectedAgentClientId.trim().length > 0
168
+ }
169
+
170
+ function emitAgentResponseWarning (message: string): void {
171
+ console.warn(`[agents:agent-client] ${message}`)
172
+ logger.warn(message)
173
+ }
174
+
114
175
  const applyActivityToTurnContext = (turnContext: TurnContext, activity: Activity) => {
115
176
  turnContext.activity.channelData = activity.channelData
116
177
  turnContext.activity.code = activity.code
@@ -5,7 +5,6 @@
5
5
 
6
6
  import { z } from 'zod'
7
7
  import { activityZodSchema, AdaptiveCardInvokeAction, adaptiveCardInvokeActionZodSchema, ExceptionHelper } from '@microsoft/agents-activity'
8
- // import { MessagingExtensionQuery, messagingExtensionQueryZodSchema } from '../messageExtension/messagingExtensionQuery'
9
8
  import { adaptiveCardsSearchParamsZodSchema } from './adaptiveCardsSearchParams'
10
9
  import { Errors } from '../../errorHelper'
11
10
 
@@ -52,10 +51,7 @@ export function parseValueContinuation (value: unknown): string {
52
51
  }
53
52
 
54
53
  interface ValueAction {
55
- action: {
56
- type: string;
57
- verb: string;
58
- }
54
+ action: AdaptiveCardInvokeAction
59
55
  }
60
56
 
61
57
  /**
@@ -71,7 +67,7 @@ export function parseValueActionExecuteSelector (value: unknown): ValueAction |
71
67
  const actionZodSchema = z.object({
72
68
  type: z.string().min(1),
73
69
  verb: z.string().min(1)
74
- })
70
+ }).passthrough()
75
71
  const actionValueExecuteSelector = z.object({
76
72
  action: actionZodSchema
77
73
  })
@@ -81,10 +77,7 @@ export function parseValueActionExecuteSelector (value: unknown): ValueAction |
81
77
  }
82
78
  const parsedValue = safeParsedValue.data
83
79
  return {
84
- action: {
85
- type: parsedValue.action.type,
86
- verb: parsedValue.action.verb
87
- }
80
+ action: parsedValue.action as unknown as AdaptiveCardInvokeAction
88
81
  }
89
82
  }
90
83
 
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Activity, ActivityTypes, ExceptionHelper } from '@microsoft/agents-activity'
7
+ import { debug } from '@microsoft/agents-telemetry'
7
8
  import { AdaptiveCardInvokeResponse, AgentApplication, CardFactory, INVOKE_RESPONSE_KEY, InvokeResponse, MessageFactory, RouteSelector, TurnContext, TurnState } from '../../'
8
9
  import { AdaptiveCardActionExecuteResponseType } from './adaptiveCardActionExecuteResponseType'
9
10
  import { parseAdaptiveCardInvokeAction, parseValueActionExecuteSelector, parseValueDataset, parseValueSearchQuery } from './activityValueParsers'
@@ -16,6 +17,7 @@ export const ACTION_INVOKE_NAME = 'adaptiveCard/action'
16
17
  const ACTION_EXECUTE_TYPE = 'Action.Execute'
17
18
  const DEFAULT_ACTION_SUBMIT_FILTER = 'verb'
18
19
  const SEARCH_INVOKE_NAME = 'application/search'
20
+ const logger = debug('agents:adaptive-cards')
19
21
 
20
22
  enum AdaptiveCardInvokeResponseType {
21
23
  /**
@@ -100,12 +102,30 @@ export class AdaptiveCardsActions<TState extends TurnState> {
100
102
  * @param verb - A string, RegExp, RouteSelector, or an array of these to match the action verb.
101
103
  * @param handler - A function to handle the action execution.
102
104
  * @returns The Teams application instance.
105
+ *
106
+ * @remarks
107
+ * The delivery behavior depends on the action trigger and
108
+ * `adaptiveCardsOptions.actionExecuteResponseType`:
109
+ *
110
+ * | Response type | Automatic trigger | Manual trigger |
111
+ * | --- | --- | --- |
112
+ * | `REPLACE_FOR_INTERACTOR` | User response | User response |
113
+ * | `REPLACE_FOR_ALL` | User response | Shared update |
114
+ * | `NEW_MESSAGE_FOR_ALL` | User response | New message |
115
+ *
116
+ * - **User response** — Return an Adaptive Card invoke response only to the requesting or interacting user.
117
+ * - **Shared update** — Update the shared activity for all users and return an Adaptive Card invoke response to the interacting user.
118
+ * - **New message** — Send a new activity for all users and return a message acknowledgement to the interacting user.
119
+ *
120
+ * Automatic refresh actions always return only an Adaptive Card invoke response.
121
+ * Updating or sending a refreshable card while processing an automatic refresh
122
+ * would cause Teams to invoke the refresh action again.
103
123
  */
104
124
  public actionExecute<TData = Record<string, any>>(
105
125
  verb: string | RegExp | RouteSelector | (string | RegExp | RouteSelector)[],
106
126
  handler: (context: TurnContext, state: TState, data: TData) => Promise<AdaptiveCard | string>
107
127
  ): AgentApplication<TState> {
108
- let actionExecuteResponseType = this._app.options.adaptiveCardsOptions?.actionExecuteResponseType ?? AdaptiveCardActionExecuteResponseType.REPLACE_FOR_INTERACTOR;
128
+ const actionExecuteResponseType = this._app.options.adaptiveCardsOptions?.actionExecuteResponseType ?? AdaptiveCardActionExecuteResponseType.REPLACE_FOR_INTERACTOR;
109
129
  (Array.isArray(verb) ? verb : [verb]).forEach((v) => {
110
130
  const selector = createActionExecuteSelector(v)
111
131
  this._app.addRoute(
@@ -113,6 +133,7 @@ export class AdaptiveCardsActions<TState extends TurnState> {
113
133
  async (context, state) => {
114
134
  const a = context?.activity
115
135
  const invokeAction = parseValueActionExecuteSelector(a.value)
136
+ const isAutomaticRefresh = (a.value as { trigger?: string } | undefined)?.trigger === 'automatic'
116
137
  if (
117
138
  a?.type !== ActivityTypes.Invoke ||
118
139
  a?.name !== ACTION_INVOKE_NAME ||
@@ -121,13 +142,11 @@ export class AdaptiveCardsActions<TState extends TurnState> {
121
142
  throw ExceptionHelper.generateException(Error, Errors.UnexpectedActionExecute, undefined, { activityType: invokeAction?.action.type ?? 'undefined' })
122
143
  }
123
144
 
124
- if (invokeAction.action.verb !== v) {
125
- // TODO: add logger to this class
126
- console.log(`AdaptiveCards.actionExecute() triggered for verb: ${invokeAction.action.verb} does not match expected verb: ${v}`)
145
+ if (typeof v === 'string' && invokeAction.action.verb !== v) {
146
+ logger.warn(`AdaptiveCards.actionExecute() triggered for verb: ${invokeAction.action.verb} does not match expected verb: ${v}`)
127
147
  }
128
148
 
129
- // TODO: review any, and check verb
130
- const result = await handler(context, state, ((a.value as any).action as TData) ?? {} as TData)
149
+ const result = await handler(context, state, (invokeAction.action as unknown as TData) ?? {} as TData)
131
150
  if (!context.turnState.get(INVOKE_RESPONSE_KEY)) {
132
151
  let response: AdaptiveCardInvokeResponse
133
152
  if (typeof result === 'string') {
@@ -138,20 +157,14 @@ export class AdaptiveCardsActions<TState extends TurnState> {
138
157
  }
139
158
  await sendInvokeResponse(context, response)
140
159
  } else {
141
- if (
142
- result.refresh &&
143
- actionExecuteResponseType !== AdaptiveCardActionExecuteResponseType.NEW_MESSAGE_FOR_ALL
144
- ) {
145
- actionExecuteResponseType = AdaptiveCardActionExecuteResponseType.REPLACE_FOR_ALL
146
- }
147
-
148
- const activity = MessageFactory.attachment(CardFactory.adaptiveCard(result))
149
160
  response = {
150
161
  statusCode: 200,
151
162
  type: AdaptiveCardInvokeResponseType.ADAPTIVE,
152
163
  value: result
153
164
  }
154
- if (
165
+ if (isAutomaticRefresh) {
166
+ await sendInvokeResponse(context, response)
167
+ } else if (
155
168
  actionExecuteResponseType === AdaptiveCardActionExecuteResponseType.NEW_MESSAGE_FOR_ALL
156
169
  ) {
157
170
  await sendInvokeResponse(context, {
@@ -159,10 +172,11 @@ export class AdaptiveCardsActions<TState extends TurnState> {
159
172
  type: AdaptiveCardInvokeResponseType.MESSAGE,
160
173
  value: 'Your response was sent to the app' as any
161
174
  })
162
- await context.sendActivity(activity)
175
+ await context.sendActivity(MessageFactory.attachment(CardFactory.adaptiveCard(result)))
163
176
  } else if (
164
177
  actionExecuteResponseType === AdaptiveCardActionExecuteResponseType.REPLACE_FOR_ALL
165
178
  ) {
179
+ const activity = MessageFactory.attachment(CardFactory.adaptiveCard(result))
166
180
  activity.id = context.activity.replyToId
167
181
  await context.updateActivity(activity)
168
182
  await sendInvokeResponse(context, response)
@@ -270,12 +284,15 @@ function createActionExecuteSelector (verb: string | RegExp | RouteSelector): Ro
270
284
  } else if (verb instanceof RegExp) {
271
285
  return (context: TurnContext) => {
272
286
  const a = context?.activity
273
- const valueAction = parseValueActionExecuteSelector(a.value)
274
287
  const isInvoke =
275
288
  a?.type === ActivityTypes.Invoke &&
276
- a?.name === ACTION_INVOKE_NAME &&
277
- valueAction?.action?.type === ACTION_EXECUTE_TYPE
278
- if (isInvoke && typeof valueAction.action.verb === 'string') {
289
+ a?.name === ACTION_INVOKE_NAME
290
+ if (!isInvoke) {
291
+ return Promise.resolve(false)
292
+ }
293
+
294
+ const valueAction = tryParseActionExecuteSelectorValue(a.value)
295
+ if (valueAction?.action?.type === ACTION_EXECUTE_TYPE && typeof valueAction.action.verb === 'string') {
279
296
  return Promise.resolve(verb.test(valueAction.action.verb))
280
297
  } else {
281
298
  return Promise.resolve(false)
@@ -284,12 +301,15 @@ function createActionExecuteSelector (verb: string | RegExp | RouteSelector): Ro
284
301
  } else {
285
302
  return (context: TurnContext) => {
286
303
  const a = context?.activity
287
- const valueAction = parseValueActionExecuteSelector(a.value)
288
304
  const isInvoke =
289
305
  a?.type === ActivityTypes.Invoke &&
290
- a?.name === ACTION_INVOKE_NAME &&
291
- valueAction?.action?.type === ACTION_EXECUTE_TYPE
292
- if (isInvoke && valueAction.action?.verb === verb) {
306
+ a?.name === ACTION_INVOKE_NAME
307
+ if (!isInvoke) {
308
+ return Promise.resolve(false)
309
+ }
310
+
311
+ const valueAction = tryParseActionExecuteSelectorValue(a.value)
312
+ if (valueAction?.action?.type === ACTION_EXECUTE_TYPE && valueAction.action?.verb === verb) {
293
313
  return Promise.resolve(true)
294
314
  } else {
295
315
  return Promise.resolve(false)
@@ -298,6 +318,14 @@ function createActionExecuteSelector (verb: string | RegExp | RouteSelector): Ro
298
318
  }
299
319
  }
300
320
 
321
+ function tryParseActionExecuteSelectorValue (value: unknown): ReturnType<typeof parseValueActionExecuteSelector> {
322
+ try {
323
+ return parseValueActionExecuteSelector(value)
324
+ } catch {
325
+ return undefined
326
+ }
327
+ }
328
+
301
329
  function createActionSubmitSelector (verb: string | RegExp | RouteSelector, filter: string): RouteSelector {
302
330
  if (typeof verb === 'function') {
303
331
  return verb
@@ -87,6 +87,12 @@ export type ApplicationEventHandler<TState extends TurnState> = (context: TurnCo
87
87
  *
88
88
  */
89
89
  export class AgentApplication<TState extends TurnState> {
90
+ /** Turn-state key for the user authorization service configured on this application. */
91
+ public static readonly UserAuthorizationKey = Symbol('UserAuthorization')
92
+
93
+ /** Turn-state key for the token connections configured on this application. */
94
+ public static readonly ConnectionsKey = Symbol('Connections')
95
+
90
96
  protected readonly _options: AgentApplicationOptions<TState>
91
97
  protected readonly _routes: RouteList<TState> = new RouteList<TState>()
92
98
  protected readonly _beforeTurn: ApplicationEventHandler<TState>[] = []
@@ -672,7 +678,7 @@ export class AgentApplication<TState extends TurnState> {
672
678
 
673
679
  if (!authorized) {
674
680
  const managed = trace(AgentApplicationTraceDefinitions.run)
675
- managed.record({ authorized, activity: context.activity })
681
+ managed.record({ authorized, activity: Activity.fromObject(context.activity) })
676
682
  managed.end()
677
683
  // We don't log a message here because it is handled by the authorization manager and could cause confusion during mid sign-in operations.
678
684
  return false
@@ -713,75 +719,90 @@ export class AgentApplication<TState extends TurnState> {
713
719
  * Executes the turn processing logic for the given context, including routing and handler execution.
714
720
  */
715
721
  private async runTurn (context: TurnContext): Promise<boolean> {
716
- return trace(AgentApplicationTraceDefinitions.run, async ({ record }) => {
717
- record({ authorized: true, activity: context.activity })
722
+ const managed = trace(AgentApplicationTraceDefinitions.run)
723
+ managed.record({ authorized: true, activity: Activity.fromObject(context.activity) })
718
724
 
719
- try {
720
- if (this._options.startTypingTimer) {
721
- this.startTypingTimer(context)
722
- }
725
+ try {
726
+ if (this._authorizationManager.handlers.length > 0) {
727
+ context.turnState.set(AgentApplication.UserAuthorizationKey, this._authorization)
728
+ }
723
729
 
724
- if (this._options.removeRecipientMention && context.activity.type === ActivityTypes.Message) {
725
- context.activity.removeRecipientMention()
726
- }
730
+ const connections = this._options.connections ?? this._adapter?.connectionManager
731
+ if (connections) {
732
+ context.turnState.set(AgentApplication.ConnectionsKey, connections)
733
+ }
727
734
 
728
- if (this._options.normalizeMentions && context.activity.type === ActivityTypes.Message) {
729
- context.activity.normalizeMentions()
730
- }
735
+ if (this._options.startTypingTimer) {
736
+ this.startTypingTimer(context)
737
+ }
731
738
 
732
- const { storage, turnStateFactory } = this._options
733
- const state = turnStateFactory()
734
- await state.load(context, storage)
739
+ if (this._options.removeRecipientMention && context.activity.type === ActivityTypes.Message) {
740
+ context.activity.removeRecipientMention()
741
+ }
735
742
 
736
- const route = await this.getRoute(context)
743
+ if (this._options.normalizeMentions && context.activity.type === ActivityTypes.Message) {
744
+ context.activity.normalizeMentions()
745
+ }
737
746
 
738
- record({ routeMatched: route !== undefined })
747
+ const { storage, turnStateFactory } = this._options
748
+ const state = turnStateFactory()
749
+ await state.load(context, storage)
739
750
 
740
- if (!route) {
741
- logger.debug('No matching route found for activity:', context.activity)
742
- return false
743
- }
751
+ const route = await this.getRoute(context)
744
752
 
745
- const fileDownloaders = this._options.fileDownloaders
746
- if (Array.isArray(fileDownloaders) && fileDownloaders.length > 0) {
747
- await trace(AgentApplicationTraceDefinitions.downloadFiles, async ({ record }) => {
748
- record({ attachmentsCount: context.activity.attachments?.length })
749
- for (let i = 0; i < fileDownloaders.length; i++) {
750
- await fileDownloaders[i].downloadAndStoreFiles(context, state)
751
- }
752
- })
753
- }
753
+ managed.record({ routeMatched: route !== undefined })
754
754
 
755
- if (this._beforeTurn.length > 0) {
756
- await trace(AgentApplicationTraceDefinitions.beforeTurn, async () => {
757
- if (!(await this.callEventHandlers(context, state, this._beforeTurn))) {
758
- await state.save(context, storage)
759
- return false
760
- }
761
- })
762
- }
755
+ if (!route) {
756
+ logger.debug('No matching route found for activity:', context.activity)
757
+ return false
758
+ }
763
759
 
764
- await trace(AgentApplicationTraceDefinitions.routeHandler, async ({ record }) => {
765
- record({ isInvoke: route.isInvokeRoute, isAgentic: route.isAgenticRoute })
766
- await route.handler(context, state)
760
+ const fileDownloaders = this._options.fileDownloaders
761
+ if (Array.isArray(fileDownloaders) && fileDownloaders.length > 0) {
762
+ await managed.child(AgentApplicationTraceDefinitions.downloadFiles, async ({ record }) => {
763
+ record({ attachmentsCount: context.activity.attachments?.length })
764
+ for (let i = 0; i < fileDownloaders.length; i++) {
765
+ await fileDownloaders[i].downloadAndStoreFiles(context, state)
766
+ }
767
767
  })
768
+ }
769
+
770
+ if (this._beforeTurn.length > 0) {
771
+ const continueTurn = await managed.child(AgentApplicationTraceDefinitions.beforeTurn, async () => {
772
+ if (!(await this.callEventHandlers(context, state, this._beforeTurn))) {
773
+ await state.save(context, storage)
774
+ return false
775
+ }
768
776
 
769
- if (this._afterTurn.length > 0) {
770
- await trace(AgentApplicationTraceDefinitions.afterTurn, async () => {
771
- if (await this.callEventHandlers(context, state, this._afterTurn)) {
772
- await state.save(context, storage)
773
- }
774
- })
777
+ return true
778
+ })
779
+
780
+ if (!continueTurn) {
781
+ return false
775
782
  }
783
+ }
776
784
 
777
- return true
778
- } catch (err: any) {
779
- logger.error(err)
780
- throw err
781
- } finally {
782
- this.stopTypingTimer(context)
785
+ await managed.child(AgentApplicationTraceDefinitions.routeHandler, async ({ record }) => {
786
+ record({ isInvoke: route.isInvokeRoute, isAgentic: route.isAgenticRoute })
787
+ await route.handler(context, state)
788
+ })
789
+
790
+ if (this._afterTurn.length > 0) {
791
+ await managed.child(AgentApplicationTraceDefinitions.afterTurn, async () => {
792
+ if (await this.callEventHandlers(context, state, this._afterTurn)) {
793
+ await state.save(context, storage)
794
+ }
795
+ })
783
796
  }
784
- })
797
+
798
+ return true
799
+ } catch (err: any) {
800
+ logger.error(err)
801
+ throw managed.fail(err)
802
+ } finally {
803
+ this.stopTypingTimer(context)
804
+ managed.end()
805
+ }
785
806
  }
786
807
 
787
808
  /**
@@ -1078,22 +1099,77 @@ export class AgentApplication<TState extends TurnState> {
1078
1099
  handler: (context: TurnContext) => Promise<any>
1079
1100
  ) {
1080
1101
  const activity = Activity.fromObject(context.activity)
1081
- this.continueConversationAsync(context.identity, activity.getConversationReference(), async (ctx) => {
1082
- try {
1083
- Object.assign(ctx.activity, activity)
1084
- await handler(ctx)
1085
- } catch (err) {
1086
- if (this.adapter.onTurnError && err instanceof Error) {
1087
- await this.adapter.onTurnError(ctx, err)
1088
- } else {
1089
- throw err
1102
+ const adapter = context.adapter
1103
+ const identity = context.identity
1104
+ const createErrorContext = () => new TurnContext(adapter, Activity.fromObject(activity), identity)
1105
+
1106
+ try {
1107
+ const reference = activity.getConversationReference()
1108
+ this.continueConversationAsync(identity, reference, async (ctx) => {
1109
+ try {
1110
+ Object.assign(ctx.activity, activity)
1111
+ await handler(ctx)
1112
+ } catch (err) {
1113
+ await this.handleLongRunningCallError(ctx, activity, err)
1090
1114
  }
1091
- }
1092
- }).catch(err => {
1093
- logger.error(`Unhandled error in long-running call for activity '${activity.type}' (id: ${activity.id}):`, err)
1115
+ }).catch(async err => {
1116
+ await this.handleLongRunningCallError(createErrorContext(), activity, err)
1117
+ })
1118
+ } catch (err) {
1119
+ this.handleLongRunningCallError(createErrorContext(), activity, err).catch(handlerErr => {
1120
+ logger.error(`Unhandled error in long-running error handler for activity '${activity.type}' (id: ${activity.id}):`, this.normalizeLongRunningCallError(handlerErr))
1121
+ })
1122
+ }
1123
+ }
1124
+
1125
+ private async handleLongRunningCallError (
1126
+ context: TurnContext,
1127
+ activity: Activity,
1128
+ err: unknown
1129
+ ): Promise<void> {
1130
+ const error = this.normalizeLongRunningCallError(err)
1131
+
1132
+ try {
1133
+ await context.adapter.onTurnError(context, error)
1134
+ } catch (handlerErr) {
1135
+ logger.error(`Unhandled error in long-running error handler for activity '${activity.type}' (id: ${activity.id}):`, this.normalizeLongRunningCallError(handlerErr))
1136
+ }
1137
+ }
1138
+
1139
+ private normalizeLongRunningCallError (err: unknown): Error {
1140
+ if (err instanceof Error) {
1141
+ return err
1142
+ }
1143
+
1144
+ return ExceptionHelper.generateException(Error, Errors.UnknownErrorType, undefined, {
1145
+ errorMessage: this.stringifyThrownValue(err)
1094
1146
  })
1095
1147
  }
1096
1148
 
1149
+ private stringifyThrownValue (value: unknown): string {
1150
+ if (typeof value === 'string') {
1151
+ return value
1152
+ }
1153
+
1154
+ if (value === undefined) {
1155
+ return 'undefined'
1156
+ }
1157
+
1158
+ if (value === null) {
1159
+ return 'null'
1160
+ }
1161
+
1162
+ if (typeof value === 'object') {
1163
+ try {
1164
+ return JSON.stringify(value) ?? Object.prototype.toString.call(value)
1165
+ } catch {
1166
+ return Object.prototype.toString.call(value)
1167
+ }
1168
+ }
1169
+
1170
+ return String(value)
1171
+ }
1172
+
1097
1173
  /**
1098
1174
  * Creates a selector function for activity types.
1099
1175
  *