@microsoft/agents-hosting 0.5.4-ga4d0401645 → 0.5.16-g6bdf69cc43

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 (106) hide show
  1. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +4 -4
  2. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
  3. package/dist/src/app/agentApplication.d.ts +186 -20
  4. package/dist/src/app/agentApplication.js +235 -33
  5. package/dist/src/app/agentApplication.js.map +1 -1
  6. package/dist/src/app/agentApplicationBuilder.d.ts +1 -1
  7. package/dist/src/app/agentApplicationOptions.d.ts +1 -1
  8. package/dist/src/app/appRoute.d.ts +5 -0
  9. package/dist/src/app/authorization.d.ts +293 -0
  10. package/dist/src/app/authorization.js +375 -0
  11. package/dist/src/app/authorization.js.map +1 -0
  12. package/dist/src/app/index.d.ts +1 -1
  13. package/dist/src/app/index.js +1 -1
  14. package/dist/src/app/index.js.map +1 -1
  15. package/dist/src/app/streaming/citation.d.ts +25 -0
  16. package/dist/src/app/streaming/{AIEntity.js → citation.js} +1 -1
  17. package/dist/src/app/streaming/citation.js.map +1 -0
  18. package/dist/src/app/streaming/{utilities.d.ts → citationUtil.d.ts} +3 -3
  19. package/dist/src/app/streaming/{utilities.js → citationUtil.js} +5 -36
  20. package/dist/src/app/streaming/citationUtil.js.map +1 -0
  21. package/dist/src/app/streaming/streamingResponse.d.ts +3 -4
  22. package/dist/src/app/streaming/streamingResponse.js +24 -22
  23. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  24. package/dist/src/auth/index.d.ts +1 -0
  25. package/dist/src/auth/index.js +1 -0
  26. package/dist/src/auth/index.js.map +1 -1
  27. package/dist/src/auth/jwt-middleware.js.map +1 -1
  28. package/dist/src/auth/msalTokenCredential.d.ts +10 -0
  29. package/dist/src/auth/msalTokenCredential.js +19 -0
  30. package/dist/src/auth/msalTokenCredential.js.map +1 -0
  31. package/dist/src/auth/msalTokenProvider.d.ts +1 -0
  32. package/dist/src/auth/msalTokenProvider.js +15 -0
  33. package/dist/src/auth/msalTokenProvider.js.map +1 -1
  34. package/dist/src/cards/cardFactory.d.ts +2 -2
  35. package/dist/src/cards/cardFactory.js.map +1 -1
  36. package/dist/src/oauth/index.d.ts +1 -4
  37. package/dist/src/oauth/index.js +1 -4
  38. package/dist/src/oauth/index.js.map +1 -1
  39. package/dist/src/oauth/oAuthFlow.d.ts +62 -19
  40. package/dist/src/oauth/oAuthFlow.js +193 -67
  41. package/dist/src/oauth/oAuthFlow.js.map +1 -1
  42. package/dist/src/oauth/userTokenClient.d.ts +37 -7
  43. package/dist/src/oauth/userTokenClient.js +56 -15
  44. package/dist/src/oauth/userTokenClient.js.map +1 -1
  45. package/dist/src/oauth/userTokenClient.types.d.ts +147 -0
  46. package/dist/src/oauth/{oAuthCard.js → userTokenClient.types.js} +1 -1
  47. package/dist/src/oauth/userTokenClient.types.js.map +1 -0
  48. package/package.json +5 -4
  49. package/src/app/adaptiveCards/adaptiveCardsActions.ts +4 -4
  50. package/src/app/agentApplication.ts +248 -33
  51. package/src/app/agentApplicationBuilder.ts +1 -1
  52. package/src/app/agentApplicationOptions.ts +1 -1
  53. package/src/app/appRoute.ts +6 -0
  54. package/src/app/authorization.ts +418 -0
  55. package/src/app/index.ts +1 -1
  56. package/src/app/streaming/citation.ts +29 -0
  57. package/src/app/streaming/{utilities.ts → citationUtil.ts} +3 -35
  58. package/src/app/streaming/streamingResponse.ts +28 -27
  59. package/src/auth/index.ts +1 -0
  60. package/src/auth/jwt-middleware.ts +1 -1
  61. package/src/auth/msalTokenCredential.ts +14 -0
  62. package/src/auth/msalTokenProvider.ts +17 -1
  63. package/src/cards/cardFactory.ts +2 -3
  64. package/src/oauth/index.ts +1 -4
  65. package/src/oauth/oAuthFlow.ts +226 -70
  66. package/src/oauth/userTokenClient.ts +62 -19
  67. package/src/oauth/userTokenClient.types.ts +173 -0
  68. package/dist/src/app/oauth/authorization.d.ts +0 -87
  69. package/dist/src/app/oauth/authorization.js +0 -135
  70. package/dist/src/app/oauth/authorization.js.map +0 -1
  71. package/dist/src/app/streaming/AIEntity.d.ts +0 -36
  72. package/dist/src/app/streaming/AIEntity.js.map +0 -1
  73. package/dist/src/app/streaming/actionCall.d.ts +0 -33
  74. package/dist/src/app/streaming/actionCall.js +0 -7
  75. package/dist/src/app/streaming/actionCall.js.map +0 -1
  76. package/dist/src/app/streaming/clientCitation.d.ts +0 -68
  77. package/dist/src/app/streaming/clientCitation.js +0 -10
  78. package/dist/src/app/streaming/clientCitation.js.map +0 -1
  79. package/dist/src/app/streaming/message.d.ts +0 -106
  80. package/dist/src/app/streaming/message.js +0 -7
  81. package/dist/src/app/streaming/message.js.map +0 -1
  82. package/dist/src/app/streaming/sensitivityUsageInfo.d.ts +0 -40
  83. package/dist/src/app/streaming/sensitivityUsageInfo.js +0 -3
  84. package/dist/src/app/streaming/sensitivityUsageInfo.js.map +0 -1
  85. package/dist/src/app/streaming/utilities.js.map +0 -1
  86. package/dist/src/oauth/oAuthCard.d.ts +0 -27
  87. package/dist/src/oauth/oAuthCard.js.map +0 -1
  88. package/dist/src/oauth/signingResource.d.ts +0 -43
  89. package/dist/src/oauth/signingResource.js +0 -5
  90. package/dist/src/oauth/signingResource.js.map +0 -1
  91. package/dist/src/oauth/tokenExchangeRequest.d.ts +0 -17
  92. package/dist/src/oauth/tokenExchangeRequest.js +0 -5
  93. package/dist/src/oauth/tokenExchangeRequest.js.map +0 -1
  94. package/dist/src/oauth/tokenResponse.d.ts +0 -29
  95. package/dist/src/oauth/tokenResponse.js +0 -25
  96. package/dist/src/oauth/tokenResponse.js.map +0 -1
  97. package/src/app/oauth/authorization.ts +0 -162
  98. package/src/app/streaming/AIEntity.ts +0 -44
  99. package/src/app/streaming/actionCall.ts +0 -37
  100. package/src/app/streaming/clientCitation.ts +0 -102
  101. package/src/app/streaming/message.ts +0 -125
  102. package/src/app/streaming/sensitivityUsageInfo.ts +0 -48
  103. package/src/oauth/oAuthCard.ts +0 -30
  104. package/src/oauth/signingResource.ts +0 -48
  105. package/src/oauth/tokenExchangeRequest.ts +0 -20
  106. package/src/oauth/tokenResponse.ts +0 -43
@@ -0,0 +1,418 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { TurnContext } from '../turnContext'
7
+ import { debug } from '../logger'
8
+ import { TurnState } from './turnState'
9
+ import { Storage } from '../storage'
10
+ import { OAuthFlow, TokenResponse } from '../oauth'
11
+ import { AuthConfiguration, MsalTokenProvider } from '../auth'
12
+ import jwt, { JwtPayload } from 'jsonwebtoken'
13
+ import { Activity } from '@microsoft/agents-activity'
14
+
15
+ const logger = debug('agents:authorization')
16
+
17
+ /**
18
+ * Interface representing the state of a sign-in process.
19
+ * @interface SignInState
20
+ */
21
+ export interface SignInState {
22
+ /** Optional activity to continue with after sign-in completion. */
23
+ continuationActivity?: Activity,
24
+ /** Identifier of the auth handler being used. */
25
+ handlerId?: string,
26
+ /** Whether the sign-in process has been completed. */
27
+ completed?: boolean
28
+ }
29
+
30
+ /**
31
+ * Interface defining an authorization handler for OAuth flows.
32
+ * @interface AuthHandler
33
+ */
34
+ export interface AuthHandler {
35
+ /** Connection name for the auth provider. */
36
+ name?: string,
37
+ /** The OAuth flow implementation. */
38
+ flow?: OAuthFlow,
39
+ /** Title to display on auth cards/UI. */
40
+ title?: string,
41
+ /** Text to display on auth cards/UI. */
42
+ text?: string,
43
+ }
44
+
45
+ /**
46
+ * Options for configuring user authorization.
47
+ * Contains settings to configure OAuth connections.
48
+ * @interface AuthorizationHandlers
49
+ */
50
+ export interface AuthorizationHandlers extends Record<string, AuthHandler> {}
51
+
52
+ /**
53
+ * Class responsible for managing authorization and OAuth flows.
54
+ * Handles multiple OAuth providers and manages the complete authentication lifecycle.
55
+ *
56
+ * @remarks
57
+ * The Authorization class provides a centralized way to handle OAuth authentication
58
+ * flows within the agent application. It supports multiple authentication handlers,
59
+ * token exchange, on-behalf-of flows, and provides event handlers for success/failure scenarios.
60
+ *
61
+ * Key features:
62
+ * - Multiple OAuth provider support
63
+ * - Token caching and exchange
64
+ * - On-behalf-of (OBO) token flows
65
+ * - Sign-in success/failure event handling
66
+ * - Automatic configuration from environment variables
67
+ *
68
+ * Example usage:
69
+ * ```typescript
70
+ * const auth = new Authorization(storage, {
71
+ * 'microsoft': {
72
+ * name: 'Microsoft',
73
+ * title: 'Sign in with Microsoft',
74
+ * text: 'Please sign in'
75
+ * }
76
+ * });
77
+ *
78
+ * auth.onSignInSuccess(async (context, state) => {
79
+ * await context.sendActivity('Welcome! You are now signed in.');
80
+ * });
81
+ * ```
82
+ */
83
+ export class Authorization {
84
+ /**
85
+ * Dictionary of configured authentication handlers.
86
+ * @public
87
+ */
88
+ authHandlers: AuthorizationHandlers
89
+
90
+ /**
91
+ * Creates a new instance of Authorization.
92
+ *
93
+ * @param storage - The storage system to use for state management.
94
+ * @param authHandlers - Configuration for OAuth providers.
95
+ * @throws {Error} If storage is null/undefined or no auth handlers are provided.
96
+ *
97
+ * @remarks
98
+ * The constructor initializes all configured auth handlers and sets up OAuth flows.
99
+ * It automatically configures handler properties from environment variables if not provided:
100
+ * - Connection name: {handlerId}_connectionName
101
+ * - Connection title: {handlerId}_connectionTitle
102
+ * - Connection text: {handlerId}_connectionText
103
+ *
104
+ * Example usage:
105
+ * ```typescript
106
+ * const auth = new Authorization(storage, {
107
+ * 'microsoft': {
108
+ * name: 'Microsoft',
109
+ * title: 'Sign in with Microsoft'
110
+ * },
111
+ * 'google': {
112
+ * // Will use GOOGLE_connectionName from env vars
113
+ * }
114
+ * });
115
+ * ```
116
+ */
117
+ constructor (private storage: Storage, authHandlers: AuthorizationHandlers) {
118
+ if (storage === undefined || storage === null) {
119
+ throw new Error('Storage is required for UserAuthorization')
120
+ }
121
+ if (authHandlers === undefined || Object.keys(authHandlers).length === 0) {
122
+ throw new Error('The authorization does not have any auth handlers')
123
+ }
124
+ this.authHandlers = authHandlers
125
+ for (const ah in this.authHandlers) {
126
+ if (this.authHandlers![ah].name === undefined && process.env[ah + '_connectionName'] === undefined) {
127
+ throw new Error(`AuthHandler name ${ah}_connectionName not set in autorization and not found in env vars.`)
128
+ }
129
+ const currentAuthHandler = this.authHandlers![ah]
130
+ currentAuthHandler.name = currentAuthHandler.name ?? process.env[ah + '_connectionName'] as string
131
+ currentAuthHandler.title = currentAuthHandler.title ?? process.env[ah + '_connectionTitle'] as string
132
+ currentAuthHandler.text = currentAuthHandler.text ?? process.env[ah + '_connectionText'] as string
133
+ currentAuthHandler.flow = new OAuthFlow(this.storage, currentAuthHandler.name, null!, currentAuthHandler.title, currentAuthHandler.text)
134
+ }
135
+ logger.info('Authorization handlers configured with', Object.keys(this.authHandlers).length, 'handlers')
136
+ }
137
+
138
+ /**
139
+ * Gets the token for a specific auth handler.
140
+ *
141
+ * @param context - The context object for the current turn.
142
+ * @param authHandlerId - ID of the auth handler to use.
143
+ * @returns A promise that resolves to the token response from the OAuth provider.
144
+ * @throws {Error} If the auth handler is not configured.
145
+ * @public
146
+ *
147
+ * @remarks
148
+ * This method retrieves an existing token for the specified auth handler.
149
+ * The token may be cached and will be retrieved from the OAuth provider if needed.
150
+ *
151
+ * Example usage:
152
+ * ```typescript
153
+ * const tokenResponse = await auth.getToken(context, 'microsoft');
154
+ * if (tokenResponse.token) {
155
+ * console.log('User is authenticated');
156
+ * }
157
+ * ```
158
+ */
159
+ public async getToken (context: TurnContext, authHandlerId: string): Promise<TokenResponse> {
160
+ logger.info('getToken from user token service for authHandlerId:', authHandlerId)
161
+ const authHandler = this.getAuthHandlerOrThrow(authHandlerId)
162
+ return await authHandler.flow?.getUserToken(context)!
163
+ }
164
+
165
+ /**
166
+ * Gets the auth handler by ID or throws an error if not found.
167
+ *
168
+ * @param authHandlerId - ID of the auth handler to retrieve.
169
+ * @returns The auth handler instance.
170
+ * @throws {Error} If the auth handler with the specified ID is not configured.
171
+ * @private
172
+ */
173
+ private getAuthHandlerOrThrow (authHandlerId: string): AuthHandler {
174
+ if (!Object.prototype.hasOwnProperty.call(this.authHandlers, authHandlerId)) {
175
+ throw new Error(`AuthHandler with ID ${authHandlerId} not configured`)
176
+ }
177
+ return this.authHandlers[authHandlerId]
178
+ }
179
+
180
+ /**
181
+ * Exchanges a token for a new token with different scopes.
182
+ *
183
+ * @param context - The context object for the current turn.
184
+ * @param scopes - Array of scopes to request for the new token.
185
+ * @param authHandlerId - ID of the auth handler to use.
186
+ * @returns A promise that resolves to the exchanged token response.
187
+ * @throws {Error} If the auth handler is not configured.
188
+ * @public
189
+ *
190
+ * @remarks
191
+ * This method handles token exchange scenarios, particularly for on-behalf-of (OBO) flows.
192
+ * It checks if the current token is exchangeable (e.g., has audience starting with 'api://')
193
+ * and performs the appropriate token exchange using MSAL.
194
+ *
195
+ * Example usage:
196
+ * ```typescript
197
+ * const exchangedToken = await auth.exchangeToken(
198
+ * context,
199
+ * ['https://graph.microsoft.com/.default'],
200
+ * 'microsoft'
201
+ * );
202
+ * ```
203
+ */
204
+ public async exchangeToken (context: TurnContext, scopes: string[], authHandlerId: string): Promise<TokenResponse> {
205
+ logger.info('getToken from user token service for authHandlerId:', authHandlerId)
206
+ const authHandler = this.getAuthHandlerOrThrow(authHandlerId)
207
+ const tokenResponse = await authHandler.flow?.getUserToken(context)!
208
+ if (this.isExchangeable(tokenResponse.token)) {
209
+ return await this.handleObo(context, tokenResponse.token!, scopes)
210
+ }
211
+ return tokenResponse
212
+ }
213
+
214
+ /**
215
+ * Checks if a token is exchangeable for an on-behalf-of flow.
216
+ *
217
+ * @param token - The token to check.
218
+ * @returns True if the token is exchangeable, false otherwise.
219
+ * @private
220
+ */
221
+ private isExchangeable (token: string | undefined): boolean {
222
+ if (!token || typeof token !== 'string') {
223
+ return false
224
+ }
225
+ const payload = jwt.decode(token) as JwtPayload
226
+ return payload?.aud?.indexOf('api://') === 0
227
+ }
228
+
229
+ /**
230
+ * Handles on-behalf-of token exchange using MSAL.
231
+ *
232
+ * @param context - The context object for the current turn.
233
+ * @param token - The token to exchange.
234
+ * @param scopes - Array of scopes to request for the new token.
235
+ * @returns A promise that resolves to the exchanged token response.
236
+ * @private
237
+ */
238
+ private async handleObo (context: TurnContext, token: string, scopes: string[]): Promise<TokenResponse> {
239
+ const msalTokenProvider = new MsalTokenProvider()
240
+ const authConfig: AuthConfiguration = context.adapter.authConfig
241
+ const newToken = await msalTokenProvider.acquireTokenOnBehalfOf(authConfig, scopes, token)
242
+ return { token: newToken }
243
+ }
244
+
245
+ /**
246
+ * Begins or continues an OAuth flow.
247
+ *
248
+ * @param context - The context object for the current turn.
249
+ * @param state - The state object for the current turn.
250
+ * @param authHandlerId - ID of the auth handler to use.
251
+ * @returns A promise that resolves to the token response from the OAuth provider.
252
+ * @throws {Error} If the auth handler is not configured.
253
+ * @public
254
+ *
255
+ * @remarks
256
+ * This method manages the complete OAuth authentication flow:
257
+ * - If no flow is active, it begins a new OAuth flow and shows the sign-in card
258
+ * - If a flow is active, it continues the flow and processes the authentication response
259
+ * - Handles success/failure callbacks and updates the sign-in state accordingly
260
+ *
261
+ * The method automatically manages the sign-in state and continuation activities,
262
+ * allowing the conversation to resume after successful authentication.
263
+ *
264
+ * Example usage:
265
+ * ```typescript
266
+ * const tokenResponse = await auth.beginOrContinueFlow(context, state, 'microsoft');
267
+ * if (tokenResponse && tokenResponse.token) {
268
+ * // User is now authenticated
269
+ * await context.sendActivity('Authentication successful!');
270
+ * }
271
+ * ```
272
+ */
273
+ public async beginOrContinueFlow (context: TurnContext, state: TurnState, authHandlerId: string, secRoute: boolean = true) : Promise<TokenResponse> {
274
+ const authHandler = this.getAuthHandlerOrThrow(authHandlerId)
275
+ logger.info('beginOrContinueFlow for authHandlerId:', authHandlerId)
276
+ const signInState: SignInState | undefined = state.getValue('user.__SIGNIN_STATE_') || { continuationActivity: undefined, handlerId: undefined, completed: false }
277
+ const flow = authHandler.flow!
278
+ let tokenResponse: TokenResponse | undefined
279
+ tokenResponse = await authHandler.flow?.getUserToken(context)
280
+
281
+ if (tokenResponse?.token && tokenResponse.token.length > 0) {
282
+ delete authHandler.flow?.state.eTag
283
+ authHandler.flow!.state.flowStarted = false
284
+ await authHandler.flow?.setFlowState(context, authHandler.flow.state)
285
+ if (secRoute) {
286
+ return tokenResponse!
287
+ }
288
+ }
289
+
290
+ if (flow.state === null || flow.state?.flowStarted === false || flow.state?.flowStarted === undefined) {
291
+ tokenResponse = await flow.beginFlow(context)
292
+ if (secRoute && tokenResponse?.token === undefined) {
293
+ signInState!.continuationActivity = context.activity
294
+ signInState!.handlerId = authHandlerId
295
+ state.setValue('user.__SIGNIN_STATE_', signInState)
296
+ }
297
+ } else {
298
+ tokenResponse = await flow.continueFlow(context)
299
+ if (tokenResponse && tokenResponse.token) {
300
+ if (this._signInSuccessHandler) {
301
+ await this._signInSuccessHandler(context, state, authHandlerId)
302
+ }
303
+ if (secRoute) {
304
+ state.deleteValue('user.__SIGNIN_STATE_')
305
+ }
306
+ } else {
307
+ logger.warn('Failed to complete OAuth flow, no token received')
308
+ if (this._signInFailureHandler) {
309
+ await this._signInFailureHandler(context, state, authHandlerId, 'Failed to complete the OAuth flow')
310
+ }
311
+ // signInState!.completed = false
312
+ // state.setValue('user.__SIGNIN_STATE_', signInState)
313
+ }
314
+ }
315
+ return tokenResponse!
316
+ }
317
+
318
+ /**
319
+ * Signs out the current user.
320
+ *
321
+ * @param context - The context object for the current turn.
322
+ * @param state - The state object for the current turn.
323
+ * @param authHandlerId - Optional ID of the auth handler to use for sign out. If not provided, signs out from all handlers.
324
+ * @returns A promise that resolves when sign out is complete.
325
+ * @throws {Error} If the specified auth handler is not configured.
326
+ * @public
327
+ *
328
+ * @remarks
329
+ * This method clears the user's token and resets the authentication state.
330
+ * If no specific authHandlerId is provided, it signs out from all configured handlers.
331
+ * This ensures complete cleanup of authentication state across all providers.
332
+ *
333
+ * Example usage:
334
+ * ```typescript
335
+ * // Sign out from specific handler
336
+ * await auth.signOut(context, state, 'microsoft');
337
+ *
338
+ * // Sign out from all handlers
339
+ * await auth.signOut(context, state);
340
+ * ```
341
+ */
342
+ async signOut (context: TurnContext, state: TurnState, authHandlerId?: string) : Promise<void> {
343
+ logger.info('signOut for authHandlerId:', authHandlerId)
344
+ if (authHandlerId === undefined) { // aw
345
+ for (const ah in this.authHandlers) {
346
+ const flow = this.authHandlers[ah].flow
347
+ await flow?.signOut(context)
348
+ }
349
+ } else {
350
+ const authHandler = this.getAuthHandlerOrThrow(authHandlerId)
351
+ await authHandler.flow?.signOut(context)
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Private handler for successful sign-in events.
357
+ * @private
358
+ */
359
+ _signInSuccessHandler: ((context: TurnContext, state: TurnState, authHandlerId?: string) => Promise<void>) | null = null
360
+
361
+ /**
362
+ * Sets a handler to be called when sign-in is successfully completed.
363
+ *
364
+ * @param handler - The handler function to call on successful sign-in.
365
+ * @public
366
+ *
367
+ * @remarks
368
+ * This method allows you to register a callback that will be invoked whenever
369
+ * a user successfully completes the authentication process. The handler receives
370
+ * the turn context, state, and the ID of the auth handler that was used.
371
+ *
372
+ * Example usage:
373
+ * ```typescript
374
+ * auth.onSignInSuccess(async (context, state, authHandlerId) => {
375
+ * await context.sendActivity(`Welcome! You signed in using ${authHandlerId}.`);
376
+ * // Perform any post-authentication setup
377
+ * });
378
+ * ```
379
+ */
380
+ public onSignInSuccess (handler: (context: TurnContext, state: TurnState, authHandlerId?: string) => Promise<void>) {
381
+ this._signInSuccessHandler = handler
382
+ }
383
+
384
+ /**
385
+ * Private handler for failed sign-in events.
386
+ * @private
387
+ */
388
+ _signInFailureHandler: ((context: TurnContext, state: TurnState, authHandlerId?: string, errorMessage?: string) => Promise<void>) | null = null
389
+
390
+ /**
391
+ * Sets a handler to be called when sign-in fails.
392
+ *
393
+ * @param handler - The handler function to call on sign-in failure.
394
+ * @public
395
+ *
396
+ * @remarks
397
+ * This method allows you to register a callback that will be invoked whenever
398
+ * a user's authentication attempt fails. The handler receives the turn context,
399
+ * state, auth handler ID, and an optional error message describing the failure.
400
+ *
401
+ * Common failure scenarios include:
402
+ * - User cancels the authentication process
403
+ * - Invalid credentials or expired tokens
404
+ * - Network connectivity issues
405
+ * - OAuth provider errors
406
+ *
407
+ * Example usage:
408
+ * ```typescript
409
+ * auth.onSignInFailure(async (context, state, authHandlerId, errorMessage) => {
410
+ * await context.sendActivity(`Sign-in failed: ${errorMessage || 'Unknown error'}`);
411
+ * await context.sendActivity('Please try signing in again.');
412
+ * });
413
+ * ```
414
+ */
415
+ public onSignInFailure (handler: (context: TurnContext, state: TurnState, authHandlerId?: string, errorMessage?: string) => Promise<void>) {
416
+ this._signInFailureHandler = handler
417
+ }
418
+ }
package/src/app/index.ts CHANGED
@@ -3,7 +3,7 @@ export * from './agentApplicationBuilder'
3
3
  export * from './agentApplicationOptions'
4
4
  export * from './appRoute'
5
5
  export * from './attachmentDownloader'
6
- export * from './oauth/authorization'
6
+ export * from './authorization'
7
7
  export * from './conversationUpdateEvents'
8
8
  export * from './routeHandler'
9
9
  export * from './routeSelector'
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * Citations returned by the model.
8
+ */
9
+ export interface Citation {
10
+ /**
11
+ * The content of the citation.
12
+ */
13
+ content: string;
14
+
15
+ /**
16
+ * The title of the citation.
17
+ */
18
+ title: string | null;
19
+
20
+ /**
21
+ * The URL of the citation.
22
+ */
23
+ url: string | null;
24
+
25
+ /**
26
+ * The filepath of the document.
27
+ */
28
+ filepath: string | null;
29
+ }
@@ -3,48 +3,16 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ClientCitation } from './clientCitation'
6
+ import { ClientCitation } from '@microsoft/agents-activity/src/entity/AIEntity'
7
7
 
8
8
  // import { stringify } from 'yaml'
9
9
 
10
10
  // import { Tokenizer } from './tokenizers'
11
11
 
12
12
  /**
13
- * Utility functions for manipulating .
13
+ * Utility functions for manipulating text and citations.
14
14
  */
15
- export class Utilities {
16
- // /**
17
- // * Converts a value to a string.
18
- // * @remarks
19
- // * Dates are converted to ISO strings and Objects are converted to JSON or YAML, whichever is shorter.
20
- // * @param {Tokenizer} tokenizer Tokenizer to use for encoding.
21
- // * @param {any} value Value to convert.
22
- // * @param {boolean} asJSON Optional. If true objects will always be converted to JSON instead of YAML. Defaults to false.
23
- // * @returns {string} Converted value.
24
- // */
25
- // public static toString (tokenizer: Tokenizer, value: any, asJSON: boolean = false): string {
26
- // if (value === undefined || value === null) {
27
- // return ''
28
- // } else if (typeof value === 'object') {
29
- // if (typeof value.toISOString === 'function') {
30
- // return value.toISOString()
31
- // } else if (asJSON) {
32
- // return JSON.stringify(value)
33
- // } else {
34
- // // Return shorter version of object
35
- // const asYaml = stringify(value)
36
- // const asJSON = JSON.stringify(value)
37
- // if (tokenizer.encode(asYaml).length <= tokenizer.encode(asJSON).length) {
38
- // return asYaml
39
- // } else {
40
- // return asJSON
41
- // }
42
- // }
43
- // } else {
44
- // return value.toString()
45
- // }
46
- // }
47
-
15
+ export class CitationUtil {
48
16
  /**
49
17
  *
50
18
  * Clips the text to a maximum length in case it exceeds the limit.
@@ -3,12 +3,14 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { Activity, Attachment, Entity } from '@microsoft/agents-activity'
6
+ import { Activity, addAIToActivity, Attachment, Entity } from '@microsoft/agents-activity'
7
7
  import { TurnContext } from '../../turnContext'
8
- import { ClientCitation } from './clientCitation'
9
- import { SensitivityUsageInfo } from './sensitivityUsageInfo'
10
- import { Citation } from './message'
11
- import { Utilities } from './utilities'
8
+ import { Citation } from './citation'
9
+ import { CitationUtil } from './citationUtil'
10
+ import { debug } from '../../logger'
11
+ import { ClientCitation, SensitivityUsageInfo } from '@microsoft/agents-activity/src/entity/AIEntity'
12
+
13
+ const logger = debug('agents:streamingResponse')
12
14
 
13
15
  /**
14
16
  * A helper class for streaming responses to the client.
@@ -16,7 +18,7 @@ import { Utilities } from './utilities'
16
18
  * This class is used to send a series of updates to the client in a single response. The expected
17
19
  * sequence of calls is:
18
20
  *
19
- * `sendInformativeUpdate()`, `sendTextChunk()`, `sendTextChunk()`, ..., `endStream()`.
21
+ * `queueInformativeUpdate()`, `queueTextChunk()`, `queueTextChunk()`, ..., `endStream()`.
20
22
  *
21
23
  * Once `endStream()` is called, the stream is considered ended and no further updates can be sent.
22
24
  */
@@ -111,7 +113,7 @@ export class StreamingResponse {
111
113
  this._message += text
112
114
 
113
115
  // If there are citations, modify the content so that the sources are numbers instead of [doc1], [doc2], etc.
114
- this._message = Utilities.formatCitationsResponse(this._message)
116
+ this._message = CitationUtil.formatCitationsResponse(this._message)
115
117
 
116
118
  // Queue the next chunk
117
119
  this.queueNextChunk()
@@ -168,7 +170,7 @@ export class StreamingResponse {
168
170
  appearance: {
169
171
  '@type': 'DigitalDocument',
170
172
  name: citation.title || `Document #${currPos + 1}`,
171
- abstract: Utilities.snippet(citation.content, 477)
173
+ abstract: CitationUtil.snippet(citation.content, 477)
172
174
  }
173
175
  }
174
176
  currPos++
@@ -238,10 +240,11 @@ export class StreamingResponse {
238
240
  // Send final message
239
241
  return Activity.fromObject({
240
242
  type: 'message',
241
- text: this._message,
243
+ text: this._message || 'end strean response',
242
244
  attachments: this._attachments,
243
245
  channelData: {
244
- streamType: 'final'
246
+ streamType: 'final',
247
+ streamSequence: this._nextSequence++
245
248
  } as StreamingChannelData
246
249
  })
247
250
  } else {
@@ -267,8 +270,8 @@ export class StreamingResponse {
267
270
  // If there's no sync in progress, start one
268
271
  if (!this._queueSync) {
269
272
  this._queueSync = this.drainQueue().catch((err) => {
270
- console.error(`Error occured when sending activity while streaming: "${err}".`)
271
- throw err
273
+ logger.error(`Error occured when sending activity while streaming: "${err}".`)
274
+ // throw err
272
275
  })
273
276
  }
274
277
  }
@@ -282,12 +285,10 @@ export class StreamingResponse {
282
285
  // eslint-disable-next-line no-async-promise-executor
283
286
  return new Promise<void>(async (resolve, reject) => {
284
287
  try {
288
+ logger.debug(`Draining queue with ${this._queue.length} activities.`)
285
289
  while (this._queue.length > 0) {
286
- // Get next activity from queue
287
290
  const factory = this._queue.shift()!
288
291
  const activity = factory()
289
-
290
- // Send activity
291
292
  await this.sendActivity(activity)
292
293
  }
293
294
 
@@ -295,7 +296,6 @@ export class StreamingResponse {
295
296
  } catch (err) {
296
297
  reject(err)
297
298
  } finally {
298
- // Queue is empty, mark as idle
299
299
  this._queueSync = undefined
300
300
  }
301
301
  })
@@ -323,7 +323,7 @@ export class StreamingResponse {
323
323
 
324
324
  if (this._citations && this._citations.length > 0 && !this._ended) {
325
325
  // Filter out the citations unused in content.
326
- const currCitations = Utilities.getUsedCitations(this._message, this._citations) ?? undefined
326
+ const currCitations = CitationUtil.getUsedCitations(this._message, this._citations) ?? undefined
327
327
  activity.entities.push({
328
328
  type: 'https://schema.org/Message',
329
329
  '@type': 'Message',
@@ -347,21 +347,22 @@ export class StreamingResponse {
347
347
 
348
348
  // Add in Generated by AI
349
349
  if (this._enableGeneratedByAILabel) {
350
- activity.entities.push({
351
- type: 'https://schema.org/Message',
352
- '@type': 'Message',
353
- '@context': 'https://schema.org',
354
- '@id': '',
355
- additionalType: ['AIGeneratedContent'],
356
- citation: this._citations && this._citations.length > 0 ? this._citations : [],
357
- usageInfo: this._sensitivityLabel
358
- } as unknown as Entity)
350
+ addAIToActivity(activity, this._citations, this._sensitivityLabel)
351
+ // activity.entities.push({
352
+ // type: 'https://schema.org/Message',
353
+ // '@type': 'Message',
354
+ // '@context': 'https://schema.org',
355
+ // '@id': '',
356
+ // additionalType: ['AIGeneratedContent'],
357
+ // citation: this._citations && this._citations.length > 0 ? this._citations : [],
358
+ // usageInfo: this._sensitivityLabel
359
+ // } as unknown as Entity)
359
360
  }
360
361
  }
361
362
 
362
363
  // Send activity
363
364
  const response = await this._context.sendActivity(activity)
364
- // await new Promise((resolve) => setTimeout(resolve, 1500))
365
+ await new Promise((resolve) => setTimeout(resolve, 1500))
365
366
 
366
367
  // Save assigned stream ID
367
368
  if (!this._streamId) {
package/src/auth/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './authConfiguration'
2
2
  export * from './authProvider'
3
3
  export * from './msalTokenProvider'
4
4
  export * from './request'
5
+ export * from './msalTokenCredential'
@@ -43,7 +43,7 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
43
43
 
44
44
  return await new Promise((resolve, reject) => {
45
45
  const verifyOptions: jwt.VerifyOptions = {
46
- issuer: config.issuers,
46
+ issuer: config.issuers as [string, ...string[]],
47
47
  audience: [config.clientId!, 'https://api.botframework.com'],
48
48
  ignoreExpiration: false,
49
49
  algorithms: ['RS256'],
@@ -0,0 +1,14 @@
1
+ import { GetTokenOptions, TokenCredential } from '@azure/core-auth'
2
+ import { AuthConfiguration, MsalTokenProvider } from './'
3
+
4
+ export class MsalTokenCredential implements TokenCredential {
5
+ constructor (private authConfig: AuthConfiguration) {}
6
+ public async getToken (scopes: string[], options?: GetTokenOptions) {
7
+ const scope = scopes[0].substring(0, scopes[0].lastIndexOf('/'))
8
+ const token = await new MsalTokenProvider().getAccessToken(this.authConfig, scope)
9
+ return {
10
+ token,
11
+ expiresOnTimestamp: Date.now() + 10000
12
+ }
13
+ }
14
+ }