@microsoft/agents-hosting 1.7.2 → 1.8.0-beta.36.gb033f0abc1

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 (102) 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 -5
  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 +3 -0
  19. package/dist/src/app/agentApplication.js +140 -81
  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.d.ts +56 -0
  30. package/dist/src/app/streaming/streamingResponse.js +250 -26
  31. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  32. package/dist/src/app/teamsAttachmentDownloader.d.ts +3 -1
  33. package/dist/src/app/teamsAttachmentDownloader.js +12 -1
  34. package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
  35. package/dist/src/auth/authConfiguration.js +12 -0
  36. package/dist/src/auth/authConfiguration.js.map +1 -1
  37. package/dist/src/auth/index.d.ts +2 -1
  38. package/dist/src/auth/index.js +2 -1
  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 +263 -13
  42. package/dist/src/auth/jwt-middleware.js.map +1 -1
  43. package/dist/src/auth/msal/msalConnectionSettings.d.ts +8 -0
  44. package/dist/src/auth/msal/msalHttpRetryHandlerHelper.d.ts +34 -0
  45. package/dist/src/auth/msal/msalHttpRetryHandlerHelper.js +126 -0
  46. package/dist/src/auth/msal/msalHttpRetryHandlerHelper.js.map +1 -0
  47. package/dist/src/auth/msal/msalTokenProvider.d.ts +1 -0
  48. package/dist/src/auth/msal/msalTokenProvider.js +23 -14
  49. package/dist/src/auth/msal/msalTokenProvider.js.map +1 -1
  50. package/dist/src/auth/settings.d.ts +18 -0
  51. package/dist/src/auth/settings.js +8 -2
  52. package/dist/src/auth/settings.js.map +1 -1
  53. package/dist/src/cloudAdapter.d.ts +29 -2
  54. package/dist/src/cloudAdapter.js +44 -2
  55. package/dist/src/cloudAdapter.js.map +1 -1
  56. package/dist/src/errorHelper.js +137 -101
  57. package/dist/src/errorHelper.js.map +1 -1
  58. package/dist/src/index.d.ts +1 -0
  59. package/dist/src/index.js +1 -0
  60. package/dist/src/index.js.map +1 -1
  61. package/dist/src/invoke/adaptiveCardInvokeValue.d.ts +5 -0
  62. package/dist/src/oauth/userTokenClient.d.ts +0 -1
  63. package/dist/src/oauth/userTokenClient.js +0 -1
  64. package/dist/src/oauth/userTokenClient.js.map +1 -1
  65. package/dist/src/observability/traces.d.ts +6 -0
  66. package/dist/src/observability/traces.js +20 -2
  67. package/dist/src/observability/traces.js.map +1 -1
  68. package/dist/src/outboundHostValidator.d.ts +47 -0
  69. package/dist/src/outboundHostValidator.js +149 -0
  70. package/dist/src/outboundHostValidator.js.map +1 -0
  71. package/dist/src/state/agentState.d.ts +5 -0
  72. package/dist/src/state/agentState.js +16 -3
  73. package/dist/src/state/agentState.js.map +1 -1
  74. package/package.json +3 -3
  75. package/src/agent-client/agentClient.ts +46 -23
  76. package/src/agent-client/agentResponseHandler.ts +14 -9
  77. package/src/agent-client/createAgentResponseHandler.ts +70 -9
  78. package/src/app/adaptiveCards/activityValueParsers.ts +3 -9
  79. package/src/app/adaptiveCards/adaptiveCardsActions.ts +52 -24
  80. package/src/app/agentApplication.ts +149 -77
  81. package/src/app/attachmentDownloader.ts +25 -5
  82. package/src/app/auth/handlers/agenticAuthorization.ts +1 -1
  83. package/src/app/auth/handlers/azureBotAuthorization.ts +1 -1
  84. package/src/app/auth/types.ts +3 -3
  85. package/src/app/index.ts +8 -0
  86. package/src/app/streaming/streamingResponse.ts +276 -22
  87. package/src/app/teamsAttachmentDownloader.ts +11 -1
  88. package/src/auth/authConfiguration.ts +12 -0
  89. package/src/auth/index.ts +2 -1
  90. package/src/auth/jwt-middleware.ts +298 -14
  91. package/src/auth/msal/msalConnectionSettings.ts +10 -0
  92. package/src/auth/msal/msalHttpRetryHandlerHelper.ts +161 -0
  93. package/src/auth/msal/msalTokenProvider.ts +32 -14
  94. package/src/auth/settings.ts +18 -3
  95. package/src/cloudAdapter.ts +56 -3
  96. package/src/errorHelper.ts +155 -114
  97. package/src/index.ts +1 -0
  98. package/src/invoke/adaptiveCardInvokeValue.ts +5 -0
  99. package/src/oauth/userTokenClient.ts +0 -1
  100. package/src/observability/traces.ts +19 -1
  101. package/src/outboundHostValidator.ts +172 -0
  102. 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
@@ -51,10 +51,7 @@ export function parseValueContinuation (value: unknown): string {
51
51
  }
52
52
 
53
53
  interface ValueAction {
54
- action: {
55
- type: string;
56
- verb: string;
57
- }
54
+ action: AdaptiveCardInvokeAction
58
55
  }
59
56
 
60
57
  /**
@@ -70,7 +67,7 @@ export function parseValueActionExecuteSelector (value: unknown): ValueAction |
70
67
  const actionZodSchema = z.object({
71
68
  type: z.string().min(1),
72
69
  verb: z.string().min(1)
73
- })
70
+ }).passthrough()
74
71
  const actionValueExecuteSelector = z.object({
75
72
  action: actionZodSchema
76
73
  })
@@ -80,10 +77,7 @@ export function parseValueActionExecuteSelector (value: unknown): ValueAction |
80
77
  }
81
78
  const parsedValue = safeParsedValue.data
82
79
  return {
83
- action: {
84
- type: parsedValue.action.type,
85
- verb: parsedValue.action.verb
86
- }
80
+ action: parsedValue.action as unknown as AdaptiveCardInvokeAction
87
81
  }
88
82
  }
89
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
@@ -36,6 +36,7 @@ const TYPING_TIMER_STATE_KEY = Symbol('typingTimerState')
36
36
  type TypingTimerState = {
37
37
  timer?: NodeJS.Timeout
38
38
  lastSend: Promise<unknown>
39
+ notificationSequence: number
39
40
  stop: () => void
40
41
  }
41
42
 
@@ -678,7 +679,7 @@ export class AgentApplication<TState extends TurnState> {
678
679
 
679
680
  if (!authorized) {
680
681
  const managed = trace(AgentApplicationTraceDefinitions.run)
681
- managed.record({ authorized, activity: context.activity })
682
+ managed.record({ authorized, activity: Activity.fromObject(context.activity) })
682
683
  managed.end()
683
684
  // We don't log a message here because it is handled by the authorization manager and could cause confusion during mid sign-in operations.
684
685
  return false
@@ -719,84 +720,90 @@ export class AgentApplication<TState extends TurnState> {
719
720
  * Executes the turn processing logic for the given context, including routing and handler execution.
720
721
  */
721
722
  private async runTurn (context: TurnContext): Promise<boolean> {
722
- return trace(AgentApplicationTraceDefinitions.run, async ({ record }) => {
723
- record({ authorized: true, activity: context.activity })
723
+ const managed = trace(AgentApplicationTraceDefinitions.run)
724
+ managed.record({ authorized: true, activity: Activity.fromObject(context.activity) })
724
725
 
725
- try {
726
- if (this._authorizationManager.handlers.length > 0) {
727
- context.turnState.set(AgentApplication.UserAuthorizationKey, this._authorization)
728
- }
726
+ try {
727
+ if (this._authorizationManager.handlers.length > 0) {
728
+ context.turnState.set(AgentApplication.UserAuthorizationKey, this._authorization)
729
+ }
729
730
 
730
- const connections = this._options.connections ?? this._adapter?.connectionManager
731
- if (connections) {
732
- context.turnState.set(AgentApplication.ConnectionsKey, connections)
733
- }
731
+ const connections = this._options.connections ?? this._adapter?.connectionManager
732
+ if (connections) {
733
+ context.turnState.set(AgentApplication.ConnectionsKey, connections)
734
+ }
734
735
 
735
- if (this._options.startTypingTimer) {
736
- this.startTypingTimer(context)
737
- }
736
+ if (this._options.startTypingTimer) {
737
+ this.startTypingTimer(context)
738
+ }
738
739
 
739
- if (this._options.removeRecipientMention && context.activity.type === ActivityTypes.Message) {
740
- context.activity.removeRecipientMention()
741
- }
740
+ if (this._options.removeRecipientMention && context.activity.type === ActivityTypes.Message) {
741
+ context.activity.removeRecipientMention()
742
+ }
742
743
 
743
- if (this._options.normalizeMentions && context.activity.type === ActivityTypes.Message) {
744
- context.activity.normalizeMentions()
745
- }
744
+ if (this._options.normalizeMentions && context.activity.type === ActivityTypes.Message) {
745
+ context.activity.normalizeMentions()
746
+ }
746
747
 
747
- const { storage, turnStateFactory } = this._options
748
- const state = turnStateFactory()
749
- await state.load(context, storage)
748
+ const { storage, turnStateFactory } = this._options
749
+ const state = turnStateFactory()
750
+ await state.load(context, storage)
750
751
 
751
- const route = await this.getRoute(context)
752
+ const route = await this.getRoute(context)
752
753
 
753
- record({ routeMatched: route !== undefined })
754
+ managed.record({ routeMatched: route !== undefined })
754
755
 
755
- if (!route) {
756
- logger.debug('No matching route found for activity:', context.activity)
757
- return false
758
- }
756
+ if (!route) {
757
+ logger.debug('No matching route found for activity:', context.activity)
758
+ return false
759
+ }
759
760
 
760
- const fileDownloaders = this._options.fileDownloaders
761
- if (Array.isArray(fileDownloaders) && fileDownloaders.length > 0) {
762
- await trace(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
- })
768
- }
761
+ const fileDownloaders = this._options.fileDownloaders
762
+ if (Array.isArray(fileDownloaders) && fileDownloaders.length > 0) {
763
+ await managed.child(AgentApplicationTraceDefinitions.downloadFiles, async ({ record }) => {
764
+ record({ attachmentsCount: context.activity.attachments?.length })
765
+ for (let i = 0; i < fileDownloaders.length; i++) {
766
+ await fileDownloaders[i].downloadAndStoreFiles(context, state)
767
+ }
768
+ })
769
+ }
769
770
 
770
- if (this._beforeTurn.length > 0) {
771
- await trace(AgentApplicationTraceDefinitions.beforeTurn, async () => {
772
- if (!(await this.callEventHandlers(context, state, this._beforeTurn))) {
773
- await state.save(context, storage)
774
- return false
775
- }
776
- })
777
- }
771
+ if (this._beforeTurn.length > 0) {
772
+ const continueTurn = await managed.child(AgentApplicationTraceDefinitions.beforeTurn, async () => {
773
+ if (!(await this.callEventHandlers(context, state, this._beforeTurn))) {
774
+ await state.save(context, storage)
775
+ return false
776
+ }
778
777
 
779
- await trace(AgentApplicationTraceDefinitions.routeHandler, async ({ record }) => {
780
- record({ isInvoke: route.isInvokeRoute, isAgentic: route.isAgenticRoute })
781
- await route.handler(context, state)
778
+ return true
782
779
  })
783
780
 
784
- if (this._afterTurn.length > 0) {
785
- await trace(AgentApplicationTraceDefinitions.afterTurn, async () => {
786
- if (await this.callEventHandlers(context, state, this._afterTurn)) {
787
- await state.save(context, storage)
788
- }
789
- })
781
+ if (!continueTurn) {
782
+ return false
790
783
  }
784
+ }
791
785
 
792
- return true
793
- } catch (err: any) {
794
- logger.error(err)
795
- throw err
796
- } finally {
797
- this.stopTypingTimer(context)
786
+ await managed.child(AgentApplicationTraceDefinitions.routeHandler, async ({ record }) => {
787
+ record({ isInvoke: route.isInvokeRoute, isAgentic: route.isAgenticRoute })
788
+ await route.handler(context, state)
789
+ })
790
+
791
+ if (this._afterTurn.length > 0) {
792
+ await managed.child(AgentApplicationTraceDefinitions.afterTurn, async () => {
793
+ if (await this.callEventHandlers(context, state, this._afterTurn)) {
794
+ await state.save(context, storage)
795
+ }
796
+ })
798
797
  }
799
- })
798
+
799
+ return true
800
+ } catch (err: any) {
801
+ logger.error(err)
802
+ throw managed.fail(err)
803
+ } finally {
804
+ this.stopTypingTimer(context)
805
+ managed.end()
806
+ }
800
807
  }
801
808
 
802
809
  /**
@@ -882,6 +889,7 @@ export class AgentApplication<TState extends TurnState> {
882
889
 
883
890
  const state: TypingTimerState = {
884
891
  lastSend: Promise.resolve(),
892
+ notificationSequence: 0,
885
893
  stop: () => {
886
894
  if (state.timer) {
887
895
  clearTimeout(state.timer)
@@ -909,7 +917,8 @@ export class AgentApplication<TState extends TurnState> {
909
917
 
910
918
  const onTimeout = async () => {
911
919
  try {
912
- state.lastSend = this.sendTypingActivity(context)
920
+ state.notificationSequence += 1
921
+ state.lastSend = this.sendTypingActivity(context, state.notificationSequence)
913
922
  await state.lastSend
914
923
  } catch (err: any) {
915
924
  logger.error(err)
@@ -942,11 +951,19 @@ export class AgentApplication<TState extends TurnState> {
942
951
  return streamingEntity?.streamType ?? activity.channelData?.streamType
943
952
  }
944
953
 
945
- private async sendTypingActivity (context: TurnContext): Promise<ResourceResponse[] | undefined> {
954
+ private async sendTypingActivity (context: TurnContext, notificationSequence: number): Promise<ResourceResponse[] | undefined> {
946
955
  const conversationReference = context.activity.getConversationReference()
947
956
  const typingActivity = Activity.fromObject({ type: ActivityTypes.Typing }).applyConversationReference(conversationReference)
948
957
 
949
- return await context.adapter.sendActivities(context, [typingActivity])
958
+ return await trace(AgentApplicationTraceDefinitions.typingIndicator, ({ record }) => {
959
+ record({
960
+ activityType: typingActivity.type,
961
+ channelId: typingActivity.channelId,
962
+ conversationId: typingActivity.conversation?.id,
963
+ notificationSequence,
964
+ })
965
+ return context.adapter.sendActivities(context, [typingActivity])
966
+ })
950
967
  }
951
968
 
952
969
  /**
@@ -1093,22 +1110,77 @@ export class AgentApplication<TState extends TurnState> {
1093
1110
  handler: (context: TurnContext) => Promise<any>
1094
1111
  ) {
1095
1112
  const activity = Activity.fromObject(context.activity)
1096
- this.continueConversationAsync(context.identity, activity.getConversationReference(), async (ctx) => {
1097
- try {
1098
- Object.assign(ctx.activity, activity)
1099
- await handler(ctx)
1100
- } catch (err) {
1101
- if (this.adapter.onTurnError && err instanceof Error) {
1102
- await this.adapter.onTurnError(ctx, err)
1103
- } else {
1104
- throw err
1113
+ const adapter = context.adapter
1114
+ const identity = context.identity
1115
+ const createErrorContext = () => new TurnContext(adapter, Activity.fromObject(activity), identity)
1116
+
1117
+ try {
1118
+ const reference = activity.getConversationReference()
1119
+ this.continueConversationAsync(identity, reference, async (ctx) => {
1120
+ try {
1121
+ Object.assign(ctx.activity, activity)
1122
+ await handler(ctx)
1123
+ } catch (err) {
1124
+ await this.handleLongRunningCallError(ctx, activity, err)
1105
1125
  }
1106
- }
1107
- }).catch(err => {
1108
- logger.error(`Unhandled error in long-running call for activity '${activity.type}' (id: ${activity.id}):`, err)
1126
+ }).catch(async err => {
1127
+ await this.handleLongRunningCallError(createErrorContext(), activity, err)
1128
+ })
1129
+ } catch (err) {
1130
+ this.handleLongRunningCallError(createErrorContext(), activity, err).catch(handlerErr => {
1131
+ logger.error(`Unhandled error in long-running error handler for activity '${activity.type}' (id: ${activity.id}):`, this.normalizeLongRunningCallError(handlerErr))
1132
+ })
1133
+ }
1134
+ }
1135
+
1136
+ private async handleLongRunningCallError (
1137
+ context: TurnContext,
1138
+ activity: Activity,
1139
+ err: unknown
1140
+ ): Promise<void> {
1141
+ const error = this.normalizeLongRunningCallError(err)
1142
+
1143
+ try {
1144
+ await context.adapter.onTurnError(context, error)
1145
+ } catch (handlerErr) {
1146
+ logger.error(`Unhandled error in long-running error handler for activity '${activity.type}' (id: ${activity.id}):`, this.normalizeLongRunningCallError(handlerErr))
1147
+ }
1148
+ }
1149
+
1150
+ private normalizeLongRunningCallError (err: unknown): Error {
1151
+ if (err instanceof Error) {
1152
+ return err
1153
+ }
1154
+
1155
+ return ExceptionHelper.generateException(Error, Errors.UnknownErrorType, undefined, {
1156
+ errorMessage: this.stringifyThrownValue(err)
1109
1157
  })
1110
1158
  }
1111
1159
 
1160
+ private stringifyThrownValue (value: unknown): string {
1161
+ if (typeof value === 'string') {
1162
+ return value
1163
+ }
1164
+
1165
+ if (value === undefined) {
1166
+ return 'undefined'
1167
+ }
1168
+
1169
+ if (value === null) {
1170
+ return 'null'
1171
+ }
1172
+
1173
+ if (typeof value === 'object') {
1174
+ try {
1175
+ return JSON.stringify(value) ?? Object.prototype.toString.call(value)
1176
+ } catch {
1177
+ return Object.prototype.toString.call(value)
1178
+ }
1179
+ }
1180
+
1181
+ return String(value)
1182
+ }
1183
+
1112
1184
  /**
1113
1185
  * Creates a selector function for activity types.
1114
1186
  *