@microsoft/agents-hosting 1.5.0-beta.5.g18f3420031 → 1.5.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 (157) hide show
  1. package/dist/package.json +10 -9
  2. package/dist/src/activityHandler.js +2 -2
  3. package/dist/src/activityHandler.js.map +1 -1
  4. package/dist/src/agent-client/agentClient.js +49 -40
  5. package/dist/src/agent-client/agentClient.js.map +1 -1
  6. package/dist/src/agent-client/agentResponseHandler.js +2 -2
  7. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  8. package/dist/src/app/agentApplication.d.ts +36 -10
  9. package/dist/src/app/agentApplication.js +169 -99
  10. package/dist/src/app/agentApplication.js.map +1 -1
  11. package/dist/src/app/agentApplicationBuilder.d.ts +15 -0
  12. package/dist/src/app/agentApplicationBuilder.js +22 -4
  13. package/dist/src/app/agentApplicationBuilder.js.map +1 -1
  14. package/dist/src/app/agentApplicationOptions.d.ts +38 -0
  15. package/dist/src/app/attachmentDownloader.js +2 -2
  16. package/dist/src/app/attachmentDownloader.js.map +1 -1
  17. package/dist/src/app/auth/authorization.js +12 -9
  18. package/dist/src/app/auth/authorization.js.map +1 -1
  19. package/dist/src/app/auth/authorizationManager.d.ts +18 -5
  20. package/dist/src/app/auth/authorizationManager.js +258 -45
  21. package/dist/src/app/auth/authorizationManager.js.map +1 -1
  22. package/dist/src/app/auth/handlerStorage.js +3 -1
  23. package/dist/src/app/auth/handlerStorage.js.map +1 -1
  24. package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +19 -16
  25. package/dist/src/app/auth/handlers/agenticAuthorization.js +46 -52
  26. package/dist/src/app/auth/handlers/agenticAuthorization.js.map +1 -1
  27. package/dist/src/app/auth/handlers/azureBotAuthorization.d.ts +51 -75
  28. package/dist/src/app/auth/handlers/azureBotAuthorization.js +217 -192
  29. package/dist/src/app/auth/handlers/azureBotAuthorization.js.map +1 -1
  30. package/dist/src/app/auth/types.d.ts +100 -1
  31. package/dist/src/app/auth/utils.d.ts +10 -0
  32. package/dist/src/app/auth/utils.js +21 -0
  33. package/dist/src/app/auth/utils.js.map +1 -0
  34. package/dist/src/app/index.d.ts +1 -0
  35. package/dist/src/app/index.js +1 -0
  36. package/dist/src/app/index.js.map +1 -1
  37. package/dist/src/app/proactive/conversation.d.ts +43 -0
  38. package/dist/src/app/proactive/conversation.js +67 -0
  39. package/dist/src/app/proactive/conversation.js.map +1 -0
  40. package/dist/src/app/proactive/conversationBuilder.d.ts +54 -0
  41. package/dist/src/app/proactive/conversationBuilder.js +110 -0
  42. package/dist/src/app/proactive/conversationBuilder.js.map +1 -0
  43. package/dist/src/app/proactive/conversationReferenceBuilder.d.ts +68 -0
  44. package/dist/src/app/proactive/conversationReferenceBuilder.js +125 -0
  45. package/dist/src/app/proactive/conversationReferenceBuilder.js.map +1 -0
  46. package/dist/src/app/proactive/createConversationOptions.d.ts +30 -0
  47. package/dist/src/app/proactive/createConversationOptions.js +10 -0
  48. package/dist/src/app/proactive/createConversationOptions.js.map +1 -0
  49. package/dist/src/app/proactive/createConversationOptionsBuilder.d.ts +69 -0
  50. package/dist/src/app/proactive/createConversationOptionsBuilder.js +141 -0
  51. package/dist/src/app/proactive/createConversationOptionsBuilder.js.map +1 -0
  52. package/dist/src/app/proactive/index.d.ts +7 -0
  53. package/dist/src/app/proactive/index.js +26 -0
  54. package/dist/src/app/proactive/index.js.map +1 -0
  55. package/dist/src/app/proactive/proactive.d.ts +248 -0
  56. package/dist/src/app/proactive/proactive.js +310 -0
  57. package/dist/src/app/proactive/proactive.js.map +1 -0
  58. package/dist/src/app/proactive/proactiveOptions.d.ts +19 -0
  59. package/dist/src/app/proactive/proactiveOptions.js +5 -0
  60. package/dist/src/app/proactive/proactiveOptions.js.map +1 -0
  61. package/dist/src/app/streaming/streamingResponse.js +2 -2
  62. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  63. package/dist/src/app/teamsAttachmentDownloader.js +2 -2
  64. package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
  65. package/dist/src/app/turnState.js +2 -2
  66. package/dist/src/app/turnState.js.map +1 -1
  67. package/dist/src/auth/authConfiguration.d.ts +61 -0
  68. package/dist/src/auth/authConfiguration.js +52 -3
  69. package/dist/src/auth/authConfiguration.js.map +1 -1
  70. package/dist/src/auth/jwt-middleware.js +2 -2
  71. package/dist/src/auth/jwt-middleware.js.map +1 -1
  72. package/dist/src/auth/msalConnectionManager.js +20 -0
  73. package/dist/src/auth/msalConnectionManager.js.map +1 -1
  74. package/dist/src/auth/msalTokenCredential.js +3 -0
  75. package/dist/src/auth/msalTokenCredential.js.map +1 -1
  76. package/dist/src/auth/msalTokenProvider.js +136 -110
  77. package/dist/src/auth/msalTokenProvider.js.map +1 -1
  78. package/dist/src/baseAdapter.js +2 -2
  79. package/dist/src/baseAdapter.js.map +1 -1
  80. package/dist/src/cloudAdapter.js +201 -154
  81. package/dist/src/cloudAdapter.js.map +1 -1
  82. package/dist/src/connector-client/connectorClient.js +176 -127
  83. package/dist/src/connector-client/connectorClient.js.map +1 -1
  84. package/dist/src/errorHelper.js +108 -0
  85. package/dist/src/errorHelper.js.map +1 -1
  86. package/dist/src/middlewareSet.js +2 -2
  87. package/dist/src/middlewareSet.js.map +1 -1
  88. package/dist/src/oauth/userTokenClient.js +78 -48
  89. package/dist/src/oauth/userTokenClient.js.map +1 -1
  90. package/dist/src/observability/index.d.ts +2 -0
  91. package/dist/src/observability/index.js +21 -0
  92. package/dist/src/observability/index.js.map +1 -0
  93. package/dist/src/observability/metrics.d.ts +21 -0
  94. package/dist/src/observability/metrics.js +87 -0
  95. package/dist/src/observability/metrics.js.map +1 -0
  96. package/dist/src/observability/traces.d.ts +234 -0
  97. package/dist/src/observability/traces.js +962 -0
  98. package/dist/src/observability/traces.js.map +1 -0
  99. package/dist/src/state/agentState.js +2 -2
  100. package/dist/src/state/agentState.js.map +1 -1
  101. package/dist/src/storage/fileStorage.js +38 -28
  102. package/dist/src/storage/fileStorage.js.map +1 -1
  103. package/dist/src/storage/memoryStorage.js +41 -30
  104. package/dist/src/storage/memoryStorage.js.map +1 -1
  105. package/dist/src/transcript/fileTranscriptLogger.js +2 -2
  106. package/dist/src/transcript/fileTranscriptLogger.js.map +1 -1
  107. package/dist/src/transcript/transcriptLoggerMiddleware.js +2 -2
  108. package/dist/src/transcript/transcriptLoggerMiddleware.js.map +1 -1
  109. package/dist/src/turnContext.js +48 -42
  110. package/dist/src/turnContext.js.map +1 -1
  111. package/package.json +10 -9
  112. package/src/activityHandler.ts +1 -1
  113. package/src/agent-client/agentClient.ts +53 -42
  114. package/src/agent-client/agentResponseHandler.ts +1 -1
  115. package/src/app/agentApplication.ts +212 -86
  116. package/src/app/agentApplicationBuilder.ts +26 -4
  117. package/src/app/agentApplicationOptions.ts +43 -0
  118. package/src/app/attachmentDownloader.ts +1 -1
  119. package/src/app/auth/authorization.ts +11 -8
  120. package/src/app/auth/authorizationManager.ts +297 -45
  121. package/src/app/auth/handlerStorage.ts +3 -1
  122. package/src/app/auth/handlers/agenticAuthorization.ts +68 -72
  123. package/src/app/auth/handlers/azureBotAuthorization.ts +260 -264
  124. package/src/app/auth/types.ts +102 -1
  125. package/src/app/auth/utils.ts +22 -0
  126. package/src/app/index.ts +1 -0
  127. package/src/app/proactive/conversation.ts +87 -0
  128. package/src/app/proactive/conversationBuilder.ts +139 -0
  129. package/src/app/proactive/conversationReferenceBuilder.ts +161 -0
  130. package/src/app/proactive/createConversationOptions.ts +35 -0
  131. package/src/app/proactive/createConversationOptionsBuilder.ts +181 -0
  132. package/src/app/proactive/index.ts +10 -0
  133. package/src/app/proactive/proactive.ts +524 -0
  134. package/src/app/proactive/proactiveOptions.ts +24 -0
  135. package/src/app/streaming/streamingResponse.ts +1 -1
  136. package/src/app/teamsAttachmentDownloader.ts +1 -1
  137. package/src/app/turnState.ts +1 -1
  138. package/src/auth/authConfiguration.ts +58 -1
  139. package/src/auth/jwt-middleware.ts +1 -1
  140. package/src/auth/msalConnectionManager.ts +22 -0
  141. package/src/auth/msalTokenCredential.ts +4 -0
  142. package/src/auth/msalTokenProvider.ts +138 -107
  143. package/src/baseAdapter.ts +1 -1
  144. package/src/cloudAdapter.ts +239 -184
  145. package/src/connector-client/connectorClient.ts +169 -126
  146. package/src/errorHelper.ts +124 -0
  147. package/src/middlewareSet.ts +1 -1
  148. package/src/oauth/userTokenClient.ts +70 -46
  149. package/src/observability/index.ts +5 -0
  150. package/src/observability/metrics.ts +103 -0
  151. package/src/observability/traces.ts +988 -0
  152. package/src/state/agentState.ts +1 -1
  153. package/src/storage/fileStorage.ts +36 -26
  154. package/src/storage/memoryStorage.ts +40 -29
  155. package/src/transcript/fileTranscriptLogger.ts +1 -1
  156. package/src/transcript/transcriptLoggerMiddleware.ts +1 -1
  157. package/src/turnContext.ts +47 -41
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@microsoft/agents-hosting",
4
- "version": "1.5.0-beta.5.g18f3420031",
4
+ "version": "1.5.1",
5
5
  "homepage": "https://github.com/microsoft/Agents-for-js",
6
6
  "repository": {
7
7
  "type": "git",
@@ -19,13 +19,14 @@
19
19
  "main": "dist/src/index.js",
20
20
  "types": "dist/src/index.d.ts",
21
21
  "dependencies": {
22
- "@azure/core-auth": "^1.10.1",
23
- "@azure/msal-node": "^5.0.6",
24
- "@microsoft/agents-activity": "1.5.0-beta.5.g18f3420031",
25
- "axios": "^1.13.6",
26
- "jsonwebtoken": "^9.0.3",
27
- "jwks-rsa": "^4.0.1",
28
- "object-path": "^0.11.8",
22
+ "@azure/core-auth": "1.10.1",
23
+ "@azure/msal-node": "5.0.6",
24
+ "@microsoft/agents-activity": "1.5.1",
25
+ "@microsoft/agents-telemetry": "1.5.1",
26
+ "axios": "1.15.0",
27
+ "jsonwebtoken": "9.0.3",
28
+ "jwks-rsa": "4.0.1",
29
+ "object-path": "0.11.8",
29
30
  "zod": "3.25.75"
30
31
  },
31
32
  "license": "MIT",
@@ -47,6 +48,6 @@
47
48
  "node": ">=20.0.0"
48
49
  },
49
50
  "devDependencies": {
50
- "@types/object-path": "^0.11.4"
51
+ "@types/object-path": "0.11.4"
51
52
  }
52
53
  }
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- import { debug } from '@microsoft/agents-activity/logger'
5
+ import { debug } from '@microsoft/agents-telemetry'
6
6
  import { TurnContext } from './turnContext'
7
7
  import { Activity, ActivityTypes, Channels, ExceptionHelper } from '@microsoft/agents-activity'
8
8
  import { Errors } from './errorHelper'
@@ -1,9 +1,11 @@
1
1
  import { AuthConfiguration, MsalTokenProvider } from '../auth'
2
2
  import { Activity, ConversationReference, RoleTypes } from '@microsoft/agents-activity'
3
3
  import { v4 } from 'uuid'
4
- import { debug } from '@microsoft/agents-activity/logger'
4
+ import { debug } from '@microsoft/agents-telemetry'
5
5
  import { ConversationState } from '../state'
6
6
  import { TurnContext } from '../turnContext'
7
+ import { trace } from '@microsoft/agents-telemetry'
8
+ import { AgentClientTraceDefinitions } from '../observability'
7
9
 
8
10
  const logger = debug('agents:agent-client')
9
11
 
@@ -68,56 +70,65 @@ export class AgentClient {
68
70
  * @throws Error if the request to the agent endpoint fails
69
71
  */
70
72
  public async postActivity (activity: Activity, authConfig: AuthConfiguration, conversationState: ConversationState, context: TurnContext): Promise<string> {
71
- const activityCopy = activity.clone()
72
- activityCopy.serviceUrl = this.agentClientConfig.serviceUrl
73
- activityCopy.recipient = { ...activityCopy.recipient, role: RoleTypes.Skill }
74
- activityCopy.relatesTo = {
75
- serviceUrl: activity.serviceUrl,
76
- activityId: activityCopy.id,
77
- channelId: activityCopy.channelId!,
78
- locale: activityCopy.locale,
79
- conversation: {
80
- id: activity.conversation!.id,
81
- ...activityCopy.conversation
73
+ return trace(AgentClientTraceDefinitions.postActivity, async ({ record }) => {
74
+ record({
75
+ endpoint: this.agentClientConfig.endPoint,
76
+ clientId: this.agentClientConfig.clientId,
77
+ })
78
+ const activityCopy = activity.clone()
79
+ activityCopy.serviceUrl = this.agentClientConfig.serviceUrl
80
+ activityCopy.recipient = { ...activityCopy.recipient, role: RoleTypes.Skill }
81
+ activityCopy.relatesTo = {
82
+ serviceUrl: activity.serviceUrl,
83
+ activityId: activityCopy.id,
84
+ channelId: activityCopy.channelId!,
85
+ locale: activityCopy.locale,
86
+ conversation: {
87
+ id: activity.conversation!.id,
88
+ ...activityCopy.conversation
89
+ }
82
90
  }
83
- }
84
- activityCopy.conversation!.id = v4()
91
+ activityCopy.conversation!.id = v4()
85
92
 
86
- const conversationDataAccessor = conversationState.createProperty<ConversationData>(activityCopy.conversation!.id)
87
- const convRef = await conversationDataAccessor.set(context,
88
- { conversationReference: activity.getConversationReference(), nameRequested: false },
89
- { channelId: activityCopy.channelId!, conversationId: activityCopy.conversation!.id }
90
- )
93
+ const conversationDataAccessor = conversationState.createProperty<ConversationData>(activityCopy.conversation!.id)
94
+ const convRef = await conversationDataAccessor.set(context,
95
+ { conversationReference: activity.getConversationReference(), nameRequested: false },
96
+ { channelId: activityCopy.channelId!, conversationId: activityCopy.conversation!.id }
97
+ )
91
98
 
92
- const stateChanges = JSON.stringify(convRef)
93
- logger.debug('stateChanges: ', stateChanges)
99
+ const stateChanges = JSON.stringify(convRef)
100
+ logger.debug('stateChanges: ', stateChanges)
94
101
 
95
- const authProvider = new MsalTokenProvider(authConfig)
96
- const token = await authProvider.getAccessToken(this.agentClientConfig.clientId)
102
+ const authProvider = new MsalTokenProvider(authConfig)
103
+ const token = await authProvider.getAccessToken(this.agentClientConfig.clientId)
97
104
 
98
- logger.debug('agent request: ', activityCopy)
105
+ logger.debug('agent request: ', activityCopy)
99
106
 
100
- let authHeader = '' // Allow anonymous auth.
107
+ let authHeader = '' // Allow anonymous auth.
101
108
 
102
- if (token.trim().length > 0) {
103
- authHeader = `Bearer ${token}`
104
- }
109
+ if (token.trim().length > 0) {
110
+ authHeader = `Bearer ${token}`
111
+ }
105
112
 
106
- await conversationState.saveChanges(context, false, { channelId: activityCopy.channelId!, conversationId: activityCopy.conversation!.id })
107
- const response = await fetch(this.agentClientConfig.endPoint, {
108
- method: 'POST',
109
- headers: {
110
- 'Content-Type': 'application/json',
111
- Authorization: authHeader,
112
- 'x-ms-conversation-id': activityCopy.conversation!.id
113
- },
114
- body: JSON.stringify(activityCopy)
113
+ await conversationState.saveChanges(context, false, { channelId: activityCopy.channelId!, conversationId: activityCopy.conversation!.id })
114
+ const response = await fetch(this.agentClientConfig.endPoint, {
115
+ method: 'POST',
116
+ headers: {
117
+ 'Content-Type': 'application/json',
118
+ Authorization: authHeader,
119
+ 'x-ms-conversation-id': activityCopy.conversation!.id
120
+ },
121
+ body: JSON.stringify(activityCopy)
122
+ })
123
+
124
+ record({ httpStatusCode: response.status.toString() })
125
+
126
+ if (!response.ok) {
127
+ await conversationDataAccessor.delete(context, { channelId: activityCopy.channelId!, conversationId: activityCopy.conversation!.id })
128
+ throw new Error(`Failed to post activity to agent: ${response.statusText}`)
129
+ }
130
+ return response.statusText
115
131
  })
116
- if (!response.ok) {
117
- await conversationDataAccessor.delete(context, { channelId: activityCopy.channelId!, conversationId: activityCopy.conversation!.id })
118
- throw new Error(`Failed to post activity to agent: ${response.statusText}`)
119
- }
120
- return response.statusText
121
132
  }
122
133
 
123
134
  /**
@@ -5,7 +5,7 @@ import { Request, Response, Application } from 'express'
5
5
  import { TurnContext } from '../turnContext'
6
6
  import { v4 } from 'uuid'
7
7
  import { normalizeIncomingActivity } from '../activityWireCompat'
8
- import { debug } from '@microsoft/agents-activity/logger'
8
+ import { debug } from '@microsoft/agents-telemetry'
9
9
  import { ConversationState } from '../state'
10
10
 
11
11
  const logger = debug('agents:agent-client')
@@ -3,12 +3,11 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { Activity, ActivityTypes, ConversationReference } from '@microsoft/agents-activity'
6
+ import { Activity, ActivityTypes, ConversationReference, ExceptionHelper } from '@microsoft/agents-activity'
7
7
  import { ResourceResponse } from '../connector-client'
8
- import { debug } from '@microsoft/agents-activity/logger'
9
8
  import { TurnContext } from '../turnContext'
10
9
  import { AdaptiveCardsActions } from './adaptiveCards'
11
- import { AgentApplicationOptions } from './agentApplicationOptions'
10
+ import { AgentApplicationOptions, TypingTimingOptions } from './agentApplicationOptions'
12
11
  import { ConversationUpdateEvents } from './conversationUpdateEvents'
13
12
  import { AgentExtension } from './extensions'
14
13
  import { RouteHandler } from './routeHandler'
@@ -20,11 +19,30 @@ import { RouteList } from './routeList'
20
19
  import { TranscriptLoggerMiddleware } from '../transcript'
21
20
  import { CloudAdapter } from '../cloudAdapter'
22
21
  import { Authorization, UserAuthorization, AuthorizationManager } from './auth'
22
+ import { Proactive } from './proactive'
23
23
  import { JwtPayload } from 'jsonwebtoken'
24
+ import { trace, debug } from '@microsoft/agents-telemetry'
25
+ import { AgentApplicationTraceDefinitions } from '../observability'
26
+ import { Errors } from '../errorHelper'
24
27
 
25
28
  const logger = debug('agents:app')
26
29
 
27
- const TYPING_TIMER_DELAY = 1000
30
+ // Resend typing every 4 seconds to stay ahead of the ~5 second timeout seen in
31
+ // Web Chat and Microsoft 365. Teams may keep typing indicators visible longer.
32
+ const DEFAULT_TYPING_INITIAL_DELAY = 0
33
+ const DEFAULT_TYPING_INTERVAL = 4000
34
+ const TYPING_TIMER_STATE_KEY = Symbol('typingTimerState')
35
+
36
+ type TypingTimerState = {
37
+ timer?: NodeJS.Timeout
38
+ lastSend: Promise<unknown>
39
+ stop: () => void
40
+ }
41
+
42
+ type StreamInfoEntity = {
43
+ type?: string
44
+ streamType?: string
45
+ }
28
46
 
29
47
  /**
30
48
  * Event handler function type for application events.
@@ -74,9 +92,9 @@ export class AgentApplication<TState extends TurnState> {
74
92
  protected readonly _beforeTurn: ApplicationEventHandler<TState>[] = []
75
93
  protected readonly _afterTurn: ApplicationEventHandler<TState>[] = []
76
94
  private readonly _adapter?: CloudAdapter
77
- private readonly _authorizationManager?: AuthorizationManager
78
- private readonly _authorization?: Authorization
79
- private _typingTimer: NodeJS.Timeout | undefined
95
+ private readonly _authorizationManager: AuthorizationManager
96
+ private readonly _authorization: Authorization
97
+ private readonly _proactive?: Proactive<TState>
80
98
  protected readonly _extensions: AgentExtension<TState>[] = []
81
99
  private readonly _adaptiveCards: AdaptiveCardsActions<TState>
82
100
 
@@ -112,6 +130,7 @@ export class AgentApplication<TState extends TurnState> {
112
130
  ...options,
113
131
  turnStateFactory: options?.turnStateFactory || (() => new TurnState() as TState),
114
132
  startTypingTimer: options?.startTypingTimer !== undefined ? options.startTypingTimer : false,
133
+ typing: options?.typing || undefined,
115
134
  longRunningMessages: options?.longRunningMessages !== undefined ? options.longRunningMessages : false,
116
135
  removeRecipientMention: options?.removeRecipientMention !== undefined ? options.removeRecipientMention : true,
117
136
  transcriptLogger: options?.transcriptLogger || undefined,
@@ -125,9 +144,12 @@ export class AgentApplication<TState extends TurnState> {
125
144
  this._adapter = new CloudAdapter()
126
145
  }
127
146
 
128
- if (this._options.authorization) {
129
- this._authorizationManager = new AuthorizationManager(this, this._adapter.connectionManager)
130
- this._authorization = new UserAuthorization(this._authorizationManager)
147
+ // Create Proactive whenever proactive options are explicitly configured or a storage
148
+ // backend is available — no explicit `proactive` option is required.
149
+ if (this._options.proactive !== undefined || this._options.storage !== undefined) {
150
+ const proactiveOpts = this._options.proactive ?? {}
151
+ const proactiveStorage = proactiveOpts.storage ?? this._options.storage
152
+ this._proactive = new Proactive<TState>(this, { ...proactiveOpts, storage: proactiveStorage })
131
153
  }
132
154
 
133
155
  if (this._options.longRunningMessages && !this._adapter && !this._options.agentAppId) {
@@ -141,7 +163,11 @@ export class AgentApplication<TState extends TurnState> {
141
163
  this._adapter?.use(new TranscriptLoggerMiddleware(this._options.transcriptLogger))
142
164
  }
143
165
  }
166
+
144
167
  logger.debug('AgentApplication created with options:', this._options)
168
+
169
+ this._authorizationManager = new AuthorizationManager(this, this._adapter.connectionManager)
170
+ this._authorization = new UserAuthorization(this._authorizationManager)
145
171
  }
146
172
 
147
173
  /**
@@ -151,12 +177,33 @@ export class AgentApplication<TState extends TurnState> {
151
177
  * @throws Error if no authentication options were configured.
152
178
  */
153
179
  public get authorization (): Authorization {
154
- if (!this._authorization) {
180
+ if (this._authorizationManager.handlers.length === 0) {
155
181
  throw new Error('The Application.authorization property is unavailable because no authorization options were configured.')
156
182
  }
157
183
  return this._authorization
158
184
  }
159
185
 
186
+ /**
187
+ * Gets the proactive messaging subsystem.
188
+ *
189
+ * @throws Error if no storage backend was configured (neither `options.storage` nor
190
+ * `options.proactive.storage`).
191
+ */
192
+ public get proactive (): Proactive<TState> {
193
+ if (!this._proactive) {
194
+ throw ExceptionHelper.generateException(Error, Errors.ProactivePropertyUnavailable)
195
+ }
196
+ return this._proactive
197
+ }
198
+
199
+ /**
200
+ * Returns `true` if user authorization was configured, without throwing.
201
+ * Used internally by the Proactive subsystem to check whether token acquisition is available.
202
+ */
203
+ public get hasUserAuthorization (): boolean {
204
+ return this._authorization !== undefined
205
+ }
206
+
160
207
  /**
161
208
  * Gets the options used to configure the application.
162
209
  *
@@ -605,6 +652,9 @@ export class AgentApplication<TState extends TurnState> {
605
652
  const { authorized, context } = await this.handleAuthorization(turnContext)
606
653
 
607
654
  if (!authorized) {
655
+ const managed = trace(AgentApplicationTraceDefinitions.run)
656
+ managed.record({ authorized, activity: context.activity })
657
+ managed.end()
608
658
  // We don't log a message here because it is handled by the authorization manager and could cause confusion during mid sign-in operations.
609
659
  return false
610
660
  }
@@ -644,54 +694,75 @@ export class AgentApplication<TState extends TurnState> {
644
694
  * Executes the turn processing logic for the given context, including routing and handler execution.
645
695
  */
646
696
  private async runTurn (context: TurnContext): Promise<boolean> {
647
- try {
648
- if (this._options.startTypingTimer) {
649
- this.startTypingTimer(context)
650
- }
697
+ return trace(AgentApplicationTraceDefinitions.run, async ({ record }) => {
698
+ record({ authorized: true, activity: context.activity })
651
699
 
652
- if (this._options.removeRecipientMention && context.activity.type === ActivityTypes.Message) {
653
- context.activity.removeRecipientMention()
654
- }
700
+ try {
701
+ if (this._options.startTypingTimer) {
702
+ this.startTypingTimer(context)
703
+ }
655
704
 
656
- if (this._options.normalizeMentions && context.activity.type === ActivityTypes.Message) {
657
- context.activity.normalizeMentions()
658
- }
705
+ if (this._options.removeRecipientMention && context.activity.type === ActivityTypes.Message) {
706
+ context.activity.removeRecipientMention()
707
+ }
659
708
 
660
- const { storage, turnStateFactory } = this._options
661
- const state = turnStateFactory()
662
- await state.load(context, storage)
709
+ if (this._options.normalizeMentions && context.activity.type === ActivityTypes.Message) {
710
+ context.activity.normalizeMentions()
711
+ }
663
712
 
664
- const route = await this.getRoute(context)
713
+ const { storage, turnStateFactory } = this._options
714
+ const state = turnStateFactory()
715
+ await state.load(context, storage)
665
716
 
666
- if (!route) {
667
- logger.debug('No matching route found for activity:', context.activity)
668
- return false
669
- }
717
+ const route = await this.getRoute(context)
670
718
 
671
- if (Array.isArray(this._options.fileDownloaders) && this._options.fileDownloaders.length > 0) {
672
- for (let i = 0; i < this._options.fileDownloaders.length; i++) {
673
- await this._options.fileDownloaders[i].downloadAndStoreFiles(context, state)
719
+ record({ routeMatched: route !== undefined })
720
+
721
+ if (!route) {
722
+ logger.debug('No matching route found for activity:', context.activity)
723
+ return false
674
724
  }
675
- }
676
725
 
677
- if (!(await this.callEventHandlers(context, state, this._beforeTurn))) {
678
- await state.save(context, storage)
679
- return false
680
- }
726
+ const fileDownloaders = this._options.fileDownloaders
727
+ if (Array.isArray(fileDownloaders) && fileDownloaders.length > 0) {
728
+ await trace(AgentApplicationTraceDefinitions.downloadFiles, async ({ record }) => {
729
+ record({ attachmentsCount: context.activity.attachments?.length })
730
+ for (let i = 0; i < fileDownloaders.length; i++) {
731
+ await fileDownloaders[i].downloadAndStoreFiles(context, state)
732
+ }
733
+ })
734
+ }
681
735
 
682
- await route.handler(context, state)
736
+ if (this._beforeTurn.length > 0) {
737
+ await trace(AgentApplicationTraceDefinitions.beforeTurn, async () => {
738
+ if (!(await this.callEventHandlers(context, state, this._beforeTurn))) {
739
+ await state.save(context, storage)
740
+ return false
741
+ }
742
+ })
743
+ }
683
744
 
684
- if (await this.callEventHandlers(context, state, this._afterTurn)) {
685
- await state.save(context, storage)
686
- }
745
+ await trace(AgentApplicationTraceDefinitions.routeHandler, async ({ record }) => {
746
+ record({ isInvoke: route.isInvokeRoute, isAgentic: route.isAgenticRoute })
747
+ await route.handler(context, state)
748
+ })
687
749
 
688
- return true
689
- } catch (err: any) {
690
- logger.error(err)
691
- throw err
692
- } finally {
693
- this.stopTypingTimer()
694
- }
750
+ if (this._afterTurn.length > 0) {
751
+ await trace(AgentApplicationTraceDefinitions.afterTurn, async () => {
752
+ if (await this.callEventHandlers(context, state, this._afterTurn)) {
753
+ await state.save(context, storage)
754
+ }
755
+ })
756
+ }
757
+
758
+ return true
759
+ } catch (err: any) {
760
+ logger.error(err)
761
+ throw err
762
+ } finally {
763
+ this.stopTypingTimer(context)
764
+ }
765
+ })
695
766
  }
696
767
 
697
768
  /**
@@ -766,43 +837,84 @@ export class AgentApplication<TState extends TurnState> {
766
837
  *
767
838
  */
768
839
  public startTypingTimer (context: TurnContext): void {
769
- if (context.activity.type === ActivityTypes.Message && !this._typingTimer) {
770
- let timerRunning = true
771
- context.onSendActivities(async (context, activities, next) => {
772
- if (timerRunning) {
773
- for (let i = 0; i < activities.length; i++) {
774
- if (activities[i].type === ActivityTypes.Message || activities[i].channelData?.streamType) {
775
- this.stopTypingTimer()
776
- timerRunning = false
777
- await lastSend
778
- break
779
- }
780
- }
840
+ const turnState = context.turnState
841
+ const typingOptions = this.getTypingTimingOptions(context)
842
+ // Timer state is stored on the current turn so concurrent turns stay isolated.
843
+ const currentState = () => turnState.get<TypingTimerState>(TYPING_TIMER_STATE_KEY)
844
+
845
+ if (context.activity.type !== ActivityTypes.Message || currentState()) {
846
+ return
847
+ }
848
+
849
+ const state: TypingTimerState = {
850
+ lastSend: Promise.resolve(),
851
+ stop: () => {
852
+ if (state.timer) {
853
+ clearTimeout(state.timer)
854
+ state.timer = undefined
781
855
  }
782
856
 
783
- return next()
784
- })
857
+ turnState.delete(TYPING_TIMER_STATE_KEY)
858
+ }
859
+ }
785
860
 
786
- let lastSend: Promise<any> = Promise.resolve()
787
- const onTimeout = async () => {
788
- try {
789
- lastSend = context.sendActivity(Activity.fromObject({ type: ActivityTypes.Typing }))
790
- await lastSend
791
- } catch (err: any) {
861
+ turnState.set(TYPING_TIMER_STATE_KEY, state)
862
+
863
+ context.onSendActivities(async (context, activities, next) => {
864
+ // Any real response or stream start ends the typing loop for this turn.
865
+ if (activities.some(activity => activity.type === ActivityTypes.Message || this.getStreamType(activity) !== undefined)) {
866
+ state.stop()
867
+ // Wait for any in-flight typing send to finish before sending the real response.
868
+ await state.lastSend.catch((err: any) => {
792
869
  logger.error(err)
793
- this._typingTimer = undefined
794
- timerRunning = false
795
- lastSend = Promise.resolve()
796
- }
870
+ })
871
+ }
797
872
 
798
- if (timerRunning) {
799
- this._typingTimer = setTimeout(onTimeout, TYPING_TIMER_DELAY)
800
- }
873
+ return next()
874
+ })
875
+
876
+ const onTimeout = async () => {
877
+ try {
878
+ state.lastSend = this.sendTypingActivity(context)
879
+ await state.lastSend
880
+ } catch (err: any) {
881
+ logger.error(err)
882
+ state.lastSend = Promise.resolve()
883
+ state.stop()
884
+ return
801
885
  }
802
- this._typingTimer = setTimeout(onTimeout, TYPING_TIMER_DELAY)
886
+
887
+ // Only reschedule if this turn still owns the active timer state.
888
+ if (currentState() === state) {
889
+ state.timer = setTimeout(onTimeout, typingOptions.intervalMs)
890
+ }
891
+ }
892
+
893
+ state.timer = setTimeout(onTimeout, typingOptions.initialDelayMs)
894
+ }
895
+
896
+ private getTypingTimingOptions (context: TurnContext): Required<TypingTimingOptions> {
897
+ const channelId = context.activity.channelId || context.activity.channelIdChannel || ''
898
+ const channelOptions = channelId ? this._options.typing?.channelStrategies?.[channelId] : undefined
899
+
900
+ return {
901
+ initialDelayMs: channelOptions?.initialDelayMs ?? this._options.typing?.initialDelayMs ?? DEFAULT_TYPING_INITIAL_DELAY,
902
+ intervalMs: channelOptions?.intervalMs ?? this._options.typing?.intervalMs ?? DEFAULT_TYPING_INTERVAL
803
903
  }
804
904
  }
805
905
 
906
+ private getStreamType (activity: Activity): string | undefined {
907
+ const streamingEntity = activity.entities?.find((entity) => (entity as StreamInfoEntity).type === 'streaminfo') as StreamInfoEntity | undefined
908
+ return streamingEntity?.streamType ?? activity.channelData?.streamType
909
+ }
910
+
911
+ private async sendTypingActivity (context: TurnContext): Promise<ResourceResponse[] | undefined> {
912
+ const conversationReference = context.activity.getConversationReference()
913
+ const typingActivity = Activity.fromObject({ type: ActivityTypes.Typing }).applyConversationReference(conversationReference)
914
+
915
+ return await context.adapter.sendActivities(context, [typingActivity])
916
+ }
917
+
806
918
  /**
807
919
  * Registers an extension with the application.
808
920
  *
@@ -838,23 +950,37 @@ export class AgentApplication<TState extends TurnState> {
838
950
  * @returns void
839
951
  *
840
952
  * @remarks
841
- * This method clears the typing indicator timer to prevent further typing indicators
842
- * from being sent. It's typically called automatically when a message is sent, but
843
- * can also be called manually to stop the typing indicator.
953
+ * Calling this overload without a context is deprecated. It only logs a warning and does not stop any timer.
954
+ *
955
+ * @deprecated Pass the current TurnContext to stop only that turn's typing timer.
956
+ */
957
+ public stopTypingTimer (): void
958
+
959
+ /**
960
+ * Stops the typing indicator timer for the provided turn context.
961
+ *
962
+ * @param context - The turn context whose typing timer should be stopped.
963
+ * @returns void
964
+ *
965
+ * @remarks
966
+ * This method clears the typing indicator timer for the current turn to prevent further typing indicators
967
+ * from being sent. It's typically called automatically when a message is sent, but can also be called manually.
844
968
  *
845
969
  * @example
846
970
  * ```typescript
847
- * app.startTypingTimer(turnContext);
971
+ * app.startTypingTimer(turnContext)
848
972
  * // Do some processing...
849
- * app.stopTypingTimer(); // Manually stop the typing indicator
973
+ * app.stopTypingTimer(turnContext)
850
974
  * ```
851
- *
852
975
  */
853
- public stopTypingTimer (): void {
854
- if (this._typingTimer) {
855
- clearTimeout(this._typingTimer)
856
- this._typingTimer = undefined
976
+ public stopTypingTimer (context: TurnContext): void
977
+ public stopTypingTimer (context?: TurnContext): void {
978
+ if (!context) {
979
+ logger.warn('Application.stopTypingTimer() without a context is deprecated. Pass the current TurnContext instead.')
980
+ return
857
981
  }
982
+
983
+ context.turnState.get<TypingTimerState>(TYPING_TIMER_STATE_KEY)?.stop()
858
984
  }
859
985
 
860
986
  /**
@@ -7,7 +7,9 @@ import { Storage } from '../storage'
7
7
  import { AgentApplication } from './agentApplication'
8
8
  import { AgentApplicationOptions } from './agentApplicationOptions'
9
9
  import { AuthorizationOptions } from './auth/types'
10
+ import { ProactiveOptions } from './proactive'
10
11
  import { TurnState } from './turnState'
12
+ import { TypingOptions } from './agentApplicationOptions'
11
13
 
12
14
  /**
13
15
  * Builder class for creating and configuring AgentApplication instances.
@@ -49,10 +51,20 @@ export class AgentApplicationBuilder<TState extends TurnState = TurnState> {
49
51
  * @param startTypingTimer Whether to show typing indicators
50
52
  * @returns This builder instance for chaining
51
53
  */
52
- // public setStartTypingTimer (startTypingTimer: boolean): this {
53
- // this._options.startTypingTimer = startTypingTimer
54
- // return this
55
- // }
54
+ public setStartTypingTimer (startTypingTimer: boolean): this {
55
+ this._options.startTypingTimer = startTypingTimer
56
+ return this
57
+ }
58
+
59
+ /**
60
+ * Configures timing behavior for automatic typing indicators.
61
+ * @param typing Typing timer options
62
+ * @returns This builder instance for chaining
63
+ */
64
+ public withTyping (typing: TypingOptions): this {
65
+ this._options.typing = typing
66
+ return this
67
+ }
56
68
 
57
69
  /**
58
70
  * Sets authentication options for the AgentApplication.
@@ -64,6 +76,16 @@ export class AgentApplicationBuilder<TState extends TurnState = TurnState> {
64
76
  return this
65
77
  }
66
78
 
79
+ /**
80
+ * Configures the proactive messaging subsystem.
81
+ * @param options Proactive options including optional storage backend
82
+ * @returns This builder instance for chaining
83
+ */
84
+ public withProactive (options: ProactiveOptions): this {
85
+ this._options.proactive = options
86
+ return this
87
+ }
88
+
67
89
  /**
68
90
  * Builds and returns a new AgentApplication instance configured with the provided options.
69
91
  * @returns A new AgentApplication instance