@microsoft/agents-hosting 0.5.4-ga4d0401645 → 0.5.12-g2d752e9b13

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 (77) 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.js +1 -1
  4. package/dist/src/app/agentApplication.js.map +1 -1
  5. package/dist/src/app/oauth/authorization.d.ts +1 -0
  6. package/dist/src/app/oauth/authorization.js +3 -4
  7. package/dist/src/app/oauth/authorization.js.map +1 -1
  8. package/dist/src/app/streaming/citation.d.ts +25 -0
  9. package/dist/src/app/streaming/{AIEntity.js → citation.js} +1 -1
  10. package/dist/src/app/streaming/citation.js.map +1 -0
  11. package/dist/src/app/streaming/{utilities.d.ts → citationUtil.d.ts} +3 -3
  12. package/dist/src/app/streaming/{utilities.js → citationUtil.js} +5 -36
  13. package/dist/src/app/streaming/citationUtil.js.map +1 -0
  14. package/dist/src/app/streaming/streamingResponse.d.ts +3 -4
  15. package/dist/src/app/streaming/streamingResponse.js +24 -22
  16. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  17. package/dist/src/cards/cardFactory.d.ts +2 -2
  18. package/dist/src/cards/cardFactory.js.map +1 -1
  19. package/dist/src/oauth/index.d.ts +1 -4
  20. package/dist/src/oauth/index.js +1 -4
  21. package/dist/src/oauth/index.js.map +1 -1
  22. package/dist/src/oauth/oAuthFlow.d.ts +9 -10
  23. package/dist/src/oauth/oAuthFlow.js +35 -38
  24. package/dist/src/oauth/oAuthFlow.js.map +1 -1
  25. package/dist/src/oauth/userTokenClient.d.ts +37 -7
  26. package/dist/src/oauth/userTokenClient.js +56 -15
  27. package/dist/src/oauth/userTokenClient.js.map +1 -1
  28. package/dist/src/oauth/userTokenClient.types.d.ts +147 -0
  29. package/dist/src/oauth/{oAuthCard.js → userTokenClient.types.js} +1 -1
  30. package/dist/src/oauth/userTokenClient.types.js.map +1 -0
  31. package/package.json +3 -3
  32. package/src/app/adaptiveCards/adaptiveCardsActions.ts +4 -4
  33. package/src/app/agentApplication.ts +1 -1
  34. package/src/app/oauth/authorization.ts +6 -8
  35. package/src/app/streaming/citation.ts +29 -0
  36. package/src/app/streaming/{utilities.ts → citationUtil.ts} +3 -35
  37. package/src/app/streaming/streamingResponse.ts +28 -27
  38. package/src/cards/cardFactory.ts +2 -3
  39. package/src/oauth/index.ts +1 -4
  40. package/src/oauth/oAuthFlow.ts +39 -45
  41. package/src/oauth/userTokenClient.ts +62 -19
  42. package/src/oauth/userTokenClient.types.ts +173 -0
  43. package/dist/src/app/streaming/AIEntity.d.ts +0 -36
  44. package/dist/src/app/streaming/AIEntity.js.map +0 -1
  45. package/dist/src/app/streaming/actionCall.d.ts +0 -33
  46. package/dist/src/app/streaming/actionCall.js +0 -7
  47. package/dist/src/app/streaming/actionCall.js.map +0 -1
  48. package/dist/src/app/streaming/clientCitation.d.ts +0 -68
  49. package/dist/src/app/streaming/clientCitation.js +0 -10
  50. package/dist/src/app/streaming/clientCitation.js.map +0 -1
  51. package/dist/src/app/streaming/message.d.ts +0 -106
  52. package/dist/src/app/streaming/message.js +0 -7
  53. package/dist/src/app/streaming/message.js.map +0 -1
  54. package/dist/src/app/streaming/sensitivityUsageInfo.d.ts +0 -40
  55. package/dist/src/app/streaming/sensitivityUsageInfo.js +0 -3
  56. package/dist/src/app/streaming/sensitivityUsageInfo.js.map +0 -1
  57. package/dist/src/app/streaming/utilities.js.map +0 -1
  58. package/dist/src/oauth/oAuthCard.d.ts +0 -27
  59. package/dist/src/oauth/oAuthCard.js.map +0 -1
  60. package/dist/src/oauth/signingResource.d.ts +0 -43
  61. package/dist/src/oauth/signingResource.js +0 -5
  62. package/dist/src/oauth/signingResource.js.map +0 -1
  63. package/dist/src/oauth/tokenExchangeRequest.d.ts +0 -17
  64. package/dist/src/oauth/tokenExchangeRequest.js +0 -5
  65. package/dist/src/oauth/tokenExchangeRequest.js.map +0 -1
  66. package/dist/src/oauth/tokenResponse.d.ts +0 -29
  67. package/dist/src/oauth/tokenResponse.js +0 -25
  68. package/dist/src/oauth/tokenResponse.js.map +0 -1
  69. package/src/app/streaming/AIEntity.ts +0 -44
  70. package/src/app/streaming/actionCall.ts +0 -37
  71. package/src/app/streaming/clientCitation.ts +0 -102
  72. package/src/app/streaming/message.ts +0 -125
  73. package/src/app/streaming/sensitivityUsageInfo.ts +0 -48
  74. package/src/oauth/oAuthCard.ts +0 -30
  75. package/src/oauth/signingResource.ts +0 -48
  76. package/src/oauth/tokenExchangeRequest.ts +0 -20
  77. package/src/oauth/tokenResponse.ts +0 -43
@@ -219,10 +219,10 @@ export class AdaptiveCardsActions<TState extends TurnState> {
219
219
  }
220
220
  }
221
221
 
222
- await context.sendActivity({
222
+ await context.sendActivity(Activity.fromObject({
223
223
  value: { body: response, status: 200 } as InvokeResponse,
224
224
  type: ActivityTypes.InvokeResponse
225
- } as Activity)
225
+ }))
226
226
  }
227
227
  },
228
228
  true
@@ -313,8 +313,8 @@ function createSearchSelector (dataset: string | RegExp | RouteSelector): RouteS
313
313
  }
314
314
 
315
315
  async function sendInvokeResponse (context: TurnContext, response: AdaptiveCardInvokeResponse) {
316
- await context.sendActivity({
316
+ await context.sendActivity(Activity.fromObject({
317
317
  value: { body: response, status: 200 } as InvokeResponse,
318
318
  type: ActivityTypes.InvokeResponse
319
- } as Activity)
319
+ }))
320
320
  }
@@ -741,7 +741,7 @@ export class AgentApplication<TState extends TurnState> {
741
741
  const k = keyword.toString().toLocaleLowerCase()
742
742
  return (context: TurnContext) => {
743
743
  if (context?.activity?.type === ActivityTypes.Message && context.activity.text) {
744
- return Promise.resolve(context.activity.text.toLocaleLowerCase().indexOf(k) >= 0)
744
+ return Promise.resolve(context.activity.text.toLocaleLowerCase() === k)
745
745
  } else {
746
746
  return Promise.resolve(false)
747
747
  }
@@ -7,8 +7,7 @@ import { TurnContext } from '../../turnContext'
7
7
  import { debug } from '../../logger'
8
8
  import { TurnState } from '../turnState'
9
9
  import { Storage } from '../../storage'
10
- import { OAuthFlow, TokenRequestStatus, TokenResponse } from '../../oauth'
11
- import { UserState } from '../../state'
10
+ import { OAuthFlow, TokenResponse } from '../../oauth'
12
11
 
13
12
  const logger = debug('agents:authorization')
14
13
 
@@ -48,11 +47,10 @@ export class Authorization {
48
47
  * @param {AuthorizationHandlers} authHandlers - Configuration for OAuth providers
49
48
  * @throws {Error} If storage is null/undefined or no auth handlers are provided
50
49
  */
51
- constructor (storage: Storage, authHandlers: AuthorizationHandlers) {
50
+ constructor (private storage: Storage, authHandlers: AuthorizationHandlers) {
52
51
  if (storage === undefined || storage === null) {
53
52
  throw new Error('Storage is required for UserAuthorization')
54
53
  }
55
- const userState = new UserState(storage)
56
54
  if (authHandlers === undefined || Object.keys(authHandlers).length === 0) {
57
55
  throw new Error('The authorization does not have any auth handlers')
58
56
  }
@@ -66,7 +64,7 @@ export class Authorization {
66
64
  currentAuthHandler.title = currentAuthHandler.title ?? process.env[ah + '_connectionTitle'] as string
67
65
  currentAuthHandler.text = currentAuthHandler.text ?? process.env[ah + '_connectionText'] as string
68
66
  currentAuthHandler.auto = currentAuthHandler.auto ?? process.env[ah + '_connectionAuto'] === 'true'
69
- currentAuthHandler.flow = new OAuthFlow(userState, currentAuthHandler.name, null!, currentAuthHandler.title, currentAuthHandler.text)
67
+ currentAuthHandler.flow = new OAuthFlow(this.storage, currentAuthHandler.name, null!, currentAuthHandler.title, currentAuthHandler.text)
70
68
  }
71
69
  logger.info('Authorization handlers configured with', this._authHandlers.length, 'handlers')
72
70
  }
@@ -93,18 +91,18 @@ export class Authorization {
93
91
  public async beginOrContinueFlow (context: TurnContext, state: TurnState, authHandlerId?: string) : Promise<TokenResponse> {
94
92
  logger.info('beginOrContinueFlow for authHandlerId:', authHandlerId)
95
93
  const flow = this.resolverHandler(authHandlerId).flow!
96
- let tokenResponse: TokenResponse
94
+ let tokenResponse: TokenResponse | undefined
97
95
  if (flow.state!.flowStarted === false) {
98
96
  tokenResponse = await flow.beginFlow(context)
99
97
  } else {
100
98
  tokenResponse = await flow.continueFlow(context)
101
- if (tokenResponse.status === TokenRequestStatus.Success) {
99
+ if (tokenResponse && tokenResponse.token) {
102
100
  if (this._signInHandler) {
103
101
  await this._signInHandler(context, state, authHandlerId)
104
102
  }
105
103
  }
106
104
  }
107
- return tokenResponse
105
+ return tokenResponse!
108
106
  }
109
107
 
110
108
  /**
@@ -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) {
@@ -13,8 +13,7 @@ import { O365ConnectorCard } from './o365ConnectorCard'
13
13
  import { ThumbnailCard } from './thumbnailCard'
14
14
  import { VideoCard } from './videoCard'
15
15
  import { CardImage } from './cardImage'
16
- import { OAuthCard } from '../oauth/oAuthCard'
17
- import { SigningResource } from '../oauth/signingResource'
16
+ import { OAuthCard, SignInResource } from '../oauth/userTokenClient.types'
18
17
  import { SigninCard } from './signinCard'
19
18
 
20
19
  /**
@@ -218,7 +217,7 @@ export class CardFactory {
218
217
  * @param signingResource The signing resource.
219
218
  * @returns The OAuth card attachment.
220
219
  */
221
- static oauthCard (connectionName: string, title: string, text: string, signingResource: SigningResource) : Attachment {
220
+ static oauthCard (connectionName: string, title: string, text: string, signingResource: SignInResource) : Attachment {
222
221
  const card: Partial<OAuthCard> = {
223
222
  buttons: [{
224
223
  type: ActionTypes.Signin,
@@ -1,6 +1,3 @@
1
- export * from './oAuthCard'
2
- export * from './signingResource'
3
1
  export * from './userTokenClient'
4
- export * from './tokenExchangeRequest'
5
2
  export * from './oAuthFlow'
6
- export * from './tokenResponse'
3
+ export * from './userTokenClient.types'
@@ -4,20 +4,18 @@ import { debug } from './../logger'
4
4
  import { ActivityTypes, Attachment } from '@microsoft/agents-activity'
5
5
  import {
6
6
  CardFactory,
7
- AgentStatePropertyAccessor,
8
- UserState,
9
7
  TurnContext,
8
+ Storage,
10
9
  MessageFactory,
11
- TokenExchangeRequest,
12
- UserTokenClient
13
10
  } from '../'
14
- import { TokenRequestStatus, TokenResponse } from './tokenResponse'
11
+ import { UserTokenClient } from './userTokenClient'
12
+ import { TokenExchangeRequest, TokenResponse } from './userTokenClient.types'
15
13
 
16
14
  const logger = debug('agents:oauth-flow')
17
15
 
18
- export class FlowState {
19
- public flowStarted: boolean = false
20
- public flowExpires: number = 0
16
+ export interface FlowState {
17
+ flowStarted: boolean,
18
+ flowExpires: number
21
19
  }
22
20
 
23
21
  interface TokenVerifyState {
@@ -35,12 +33,7 @@ export class OAuthFlow {
35
33
  /**
36
34
  * The current state of the OAuth flow.
37
35
  */
38
- state: FlowState | null
39
-
40
- /**
41
- * The accessor for managing the flow state in user state.
42
- */
43
- flowStateAccessor: AgentStatePropertyAccessor<FlowState | null>
36
+ state: FlowState
44
37
 
45
38
  /**
46
39
  * The ID of the token exchange request, used to deduplicate requests.
@@ -66,9 +59,8 @@ export class OAuthFlow {
66
59
  * Creates a new instance of OAuthFlow.
67
60
  * @param userState The user state.
68
61
  */
69
- constructor (userState: UserState, absOauthConnectionName: string, tokenClient?: UserTokenClient, cardTitle?: string, cardText?: string) {
70
- this.state = new FlowState()
71
- this.flowStateAccessor = userState.createProperty('flowState')
62
+ constructor (private storage: Storage, absOauthConnectionName: string, tokenClient?: UserTokenClient, cardTitle?: string, cardText?: string) {
63
+ this.state = { flowExpires: 0, flowStarted: false }
72
64
  this.absOauthConnectionName = absOauthConnectionName
73
65
  this.userTokenClient = tokenClient ?? null!
74
66
  this.cardTitle = cardTitle ?? this.cardTitle
@@ -97,7 +89,7 @@ export class OAuthFlow {
97
89
  * @param context The turn context.
98
90
  * @returns A promise that resolves to the user token.
99
91
  */
100
- public async beginFlow (context: TurnContext): Promise<TokenResponse> {
92
+ public async beginFlow (context: TurnContext): Promise<TokenResponse | undefined> {
101
93
  this.state = await this.getUserState(context)
102
94
  if (this.absOauthConnectionName === '') {
103
95
  throw new Error('connectionName is not set')
@@ -105,27 +97,20 @@ export class OAuthFlow {
105
97
  logger.info('Starting OAuth flow for connectionName:', this.absOauthConnectionName)
106
98
  await this.initializeTokenClient(context)
107
99
 
108
- const tokenResponse = await this.userTokenClient.getUserToken(this.absOauthConnectionName, context.activity.channelId!, context.activity.from?.id!)
109
- if (tokenResponse?.status === TokenRequestStatus.Success) {
100
+ const act = context.activity
101
+ const output = await this.userTokenClient.getTokenOrSignInResource(act.from?.id!, this.absOauthConnectionName, act.channelId!, act.getConversationReference(), act.relatesTo!, undefined!)
102
+ if (output && output.tokenResponse) {
110
103
  this.state.flowStarted = false
111
104
  this.state.flowExpires = 0
112
- await this.flowStateAccessor.set(context, this.state)
113
- logger.info('User token retrieved successfully from service')
114
- return tokenResponse
105
+ await this.storage.write({ [this.getFlowStateKey(context)]: this.state })
106
+ return output.tokenResponse
115
107
  }
116
-
117
- const authConfig = context.adapter.authConfig
118
- const signingResource = await this.userTokenClient.getSignInResource(authConfig.clientId!, this.absOauthConnectionName, context.activity.getConversationReference(), context.activity.relatesTo)
119
- const oCard: Attachment = CardFactory.oauthCard(this.absOauthConnectionName, this.cardTitle, this.cardText, signingResource)
108
+ const oCard: Attachment = CardFactory.oauthCard(this.absOauthConnectionName, this.cardTitle, this.cardText, output.signInResource)
120
109
  await context.sendActivity(MessageFactory.attachment(oCard))
121
110
  this.state.flowStarted = true
122
111
  this.state.flowExpires = Date.now() + 30000
123
- await this.flowStateAccessor.set(context, this.state)
124
- logger.info('OAuth begin flow completed, waiting for user to sign in')
125
- return {
126
- token: undefined,
127
- status: TokenRequestStatus.InProgress
128
- }
112
+ await this.storage.write({ [this.getFlowStateKey(context)]: this.state })
113
+ return undefined
129
114
  }
130
115
 
131
116
  /**
@@ -140,7 +125,7 @@ export class OAuthFlow {
140
125
  logger.warn('Flow expired')
141
126
  this.state!.flowStarted = false
142
127
  await context.sendActivity(MessageFactory.text('Sign-in session expired. Please try again.'))
143
- return { status: TokenRequestStatus.Expired, token: undefined }
128
+ return { token: undefined }
144
129
  }
145
130
  const contFlowActivity = context.activity
146
131
  if (contFlowActivity.type === ActivityTypes.Message) {
@@ -161,22 +146,22 @@ export class OAuthFlow {
161
146
  logger.info('Continuing OAuth flow with tokenExchange')
162
147
  const tokenExchangeRequest = contFlowActivity.value as TokenExchangeRequest
163
148
  if (this.tokenExchangeId === tokenExchangeRequest.id) { // dedupe
164
- return { status: TokenRequestStatus.InProgress, token: undefined }
149
+ return { token: undefined }
165
150
  }
166
151
  this.tokenExchangeId = tokenExchangeRequest.id!
167
152
  const userTokenResp = await this.userTokenClient?.exchangeTokenAsync(contFlowActivity.from?.id!, this.absOauthConnectionName, contFlowActivity.channelId!, tokenExchangeRequest)
168
- if (userTokenResp?.status === TokenRequestStatus.Success) {
153
+ if (userTokenResp && userTokenResp.token) {
169
154
  logger.info('Token exchanged')
170
155
  this.state!.flowStarted = false
171
- await this.flowStateAccessor.set(context, this.state)
156
+ await this.storage.write({ [this.getFlowStateKey(context)]: this.state })
172
157
  return userTokenResp
173
158
  } else {
174
159
  logger.warn('Token exchange failed')
175
160
  this.state!.flowStarted = true
176
- return { status: TokenRequestStatus.Failed, token: undefined }
161
+ return { token: undefined }
177
162
  }
178
163
  }
179
- return { status: TokenRequestStatus.Failed, token: undefined }
164
+ return { token: undefined }
180
165
  }
181
166
 
182
167
  /**
@@ -189,7 +174,7 @@ export class OAuthFlow {
189
174
  await this.initializeTokenClient(context)
190
175
  await this.userTokenClient?.signOut(context.activity.from?.id as string, this.absOauthConnectionName, context.activity.channelId as string)
191
176
  this.state!.flowExpires = 0
192
- await this.flowStateAccessor.set(context, this.state)
177
+ this.storage.write({ [this.getFlowStateKey(context)]: this.state })
193
178
  logger.info('User signed out successfully')
194
179
  }
195
180
 
@@ -199,10 +184,9 @@ export class OAuthFlow {
199
184
  * @returns A promise that resolves to the user state.
200
185
  */
201
186
  private async getUserState (context: TurnContext) {
202
- let userProfile: FlowState | null = await this.flowStateAccessor.get(context, null)
203
- if (userProfile === null) {
204
- userProfile = new FlowState()
205
- }
187
+ const key = this.getFlowStateKey(context)
188
+ const data = await this.storage.read([key])
189
+ const userProfile: FlowState = data[key] ?? { flowStarted: false, flowExpires: 0 }
206
190
  return userProfile
207
191
  }
208
192
 
@@ -210,7 +194,17 @@ export class OAuthFlow {
210
194
  if (this.userTokenClient === undefined || this.userTokenClient === null) {
211
195
  const scope = 'https://api.botframework.com'
212
196
  const accessToken = await context.adapter.authProvider.getAccessToken(context.adapter.authConfig, scope)
213
- this.userTokenClient = new UserTokenClient(accessToken)
197
+ this.userTokenClient = new UserTokenClient(accessToken, context.adapter.authConfig.clientId!)
198
+ }
199
+ }
200
+
201
+ private getFlowStateKey (context: TurnContext): string {
202
+ const channelId = context.activity.channelId
203
+ const conversationId = context.activity.conversation?.id
204
+ const userId = context.activity.from?.id
205
+ if (!channelId || !conversationId || !userId) {
206
+ throw new Error('ChannelId, conversationId, and userId must be set in the activity')
214
207
  }
208
+ return `oauth/${channelId}/${conversationId}/${userId}/flowState`
215
209
  }
216
210
  }
@@ -2,12 +2,10 @@
2
2
  // Licensed under the MIT License.
3
3
 
4
4
  import axios, { AxiosInstance } from 'axios'
5
- import { SigningResource } from './signingResource'
6
5
  import { ConversationReference } from '@microsoft/agents-activity'
7
6
  import { debug } from '../logger'
8
- import { TokenExchangeRequest } from './tokenExchangeRequest'
9
7
  import { normalizeTokenExchangeState } from '../activityWireCompat'
10
- import { TokenRequestStatus, TokenResponse } from './tokenResponse'
8
+ import { AadResourceUrls, SignInResource, TokenExchangeRequest, TokenOrSinginResourceResponse, TokenResponse, TokenStatus } from './userTokenClient.types'
11
9
  import { getProductInfo } from '../getProductInfo'
12
10
 
13
11
  const logger = debug('agents:user-token-client')
@@ -17,12 +15,14 @@ const logger = debug('agents:user-token-client')
17
15
  */
18
16
  export class UserTokenClient {
19
17
  client: AxiosInstance
20
-
18
+ msAppId: string
21
19
  /**
22
20
  * Creates a new instance of UserTokenClient.
23
21
  * @param token The token to use for authentication.
22
+ * @param msAppId The Microsoft application ID.
24
23
  */
25
- constructor (token: string) {
24
+ constructor (token: string, appId: string) {
25
+ this.msAppId = appId
26
26
  const baseURL = 'https://api.botframework.com'
27
27
  const axiosInstance = axios.create({
28
28
  baseURL,
@@ -47,15 +47,12 @@ export class UserTokenClient {
47
47
  try {
48
48
  const params = { connectionName, channelId, userId, code }
49
49
  const response = await this.client.get('/api/usertoken/GetToken', { params })
50
- return { ...response.data, status: TokenRequestStatus.Success }
50
+ return response.data as TokenResponse
51
51
  } catch (error: any) {
52
52
  if (error.response?.status !== 404) {
53
53
  logger.error(error)
54
54
  }
55
- return {
56
- status: TokenRequestStatus.Failed,
57
- token: undefined
58
- }
55
+ return { token: undefined }
59
56
  }
60
57
  }
61
58
 
@@ -81,24 +78,24 @@ export class UserTokenClient {
81
78
 
82
79
  /**
83
80
  * Gets the sign-in resource.
84
- * @param appId The application ID.
85
- * @param cnxName The connection name.
81
+ * @param msAppId The application ID.
82
+ * @param connectionName The connection name.
86
83
  * @param activity The activity.
87
84
  * @returns A promise that resolves to the signing resource.
88
85
  */
89
- async getSignInResource (appId: string, cnxName: string, conversationReference: ConversationReference, relatesTo?: ConversationReference) : Promise<SigningResource> {
86
+ async getSignInResource (msAppId: string, connectionName: string, conversation: ConversationReference, relatesTo?: ConversationReference) : Promise<SignInResource> {
90
87
  try {
91
88
  const tokenExchangeState = {
92
- connectionName: cnxName,
93
- conversation: conversationReference,
89
+ connectionName,
90
+ conversation,
94
91
  relatesTo,
95
- msAppId: appId
92
+ msAppId
96
93
  }
97
94
  const tokenExchangeStateNormalized = normalizeTokenExchangeState(tokenExchangeState)
98
95
  const state = Buffer.from(JSON.stringify(tokenExchangeStateNormalized)).toString('base64')
99
96
  const params = { state }
100
97
  const response = await this.client.get('/api/botsignin/GetSignInResource', { params })
101
- return response.data as SigningResource
98
+ return response.data as SignInResource
102
99
  } catch (error: any) {
103
100
  logger.error(error)
104
101
  throw error
@@ -117,10 +114,56 @@ export class UserTokenClient {
117
114
  try {
118
115
  const params = { userId, connectionName, channelId }
119
116
  const response = await this.client.post('/api/usertoken/exchange', tokenExchangeRequest, { params })
120
- return { ...response.data, status: TokenRequestStatus.Success }
117
+ return response.data as TokenResponse
121
118
  } catch (error: any) {
122
119
  logger.error(error)
123
- return { status: TokenRequestStatus.Failed, token: undefined }
120
+ return { token: undefined }
124
121
  }
125
122
  }
123
+
124
+ /**
125
+ * Gets the token or sign-in resource.
126
+ * @param userId The user ID.
127
+ * @param connectionName The connection name.
128
+ * @param channelId The channel ID.
129
+ * @param conversation The conversation reference.
130
+ * @param relatesTo The related conversation reference.
131
+ * @param code The code.
132
+ * @param finalRedirect The final redirect URL.
133
+ * @param fwdUrl The forward URL.
134
+ * @returns A promise that resolves to the token or sign-in resource response.
135
+ */
136
+ async getTokenOrSignInResource (userId: string, connectionName: string, channelId: string, conversation: ConversationReference, relatesTo: ConversationReference, code: string, finalRedirect: string = '', fwdUrl: string = '') : Promise<TokenOrSinginResourceResponse> {
137
+ const state = Buffer.from(JSON.stringify({ conversation, relatesTo, connectionName, msAppId: this.msAppId })).toString('base64')
138
+ const params = { userId, connectionName, channelId, state, code, finalRedirect, fwdUrl }
139
+ const response = await this.client.get('/api/usertoken/GetTokenOrSignInResource', { params })
140
+ return response.data as TokenOrSinginResourceResponse
141
+ }
142
+
143
+ /**
144
+ * Gets the token status.
145
+ * @param userId The user ID.
146
+ * @param channelId The channel ID.
147
+ * @param include The optional include parameter.
148
+ * @returns A promise that resolves to the token status.
149
+ */
150
+ async getTokenStatus (userId: string, channelId: string, include: string = null!): Promise<TokenStatus[]> {
151
+ const params = { userId, channelId, include }
152
+ const response = await this.client.get('/api/usertoken/GetTokenStatus', { params })
153
+ return response.data as TokenStatus[]
154
+ }
155
+
156
+ /**
157
+ * Gets the AAD tokens.
158
+ * @param userId The user ID.
159
+ * @param connectionName The connection name.
160
+ * @param channelId The channel ID.
161
+ * @param resourceUrls The resource URLs.
162
+ * @returns A promise that resolves to the AAD tokens.
163
+ */
164
+ async getAadTokens (userId: string, connectionName: string, channelId: string, resourceUrls: AadResourceUrls) : Promise<Record<string, TokenResponse>> {
165
+ const params = { userId, connectionName, channelId }
166
+ const response = await this.client.post('/api/usertoken/GetAadTokens', resourceUrls, { params })
167
+ return response.data as Record<string, TokenResponse>
168
+ }
126
169
  }