@microsoft/agents-hosting 1.5.0-beta.1.g77c44097fe → 1.5.0-beta.12.ga9a2b23c19
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.
- package/dist/package.json +2 -2
- package/dist/src/app/agentApplication.d.ts +31 -9
- package/dist/src/app/agentApplication.js +125 -82
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/agentApplicationBuilder.d.ts +7 -0
- package/dist/src/app/agentApplicationBuilder.js +9 -0
- package/dist/src/app/agentApplicationBuilder.js.map +1 -1
- package/dist/src/app/agentApplicationOptions.d.ts +6 -0
- package/dist/src/app/auth/authorizationManager.d.ts +4 -0
- package/dist/src/app/auth/authorizationManager.js +10 -9
- package/dist/src/app/auth/authorizationManager.js.map +1 -1
- package/dist/src/app/auth/handlers/azureBotAuthorization.js +3 -3
- package/dist/src/app/auth/handlers/azureBotAuthorization.js.map +1 -1
- package/dist/src/app/index.d.ts +1 -0
- package/dist/src/app/index.js +1 -0
- package/dist/src/app/index.js.map +1 -1
- package/dist/src/app/proactive/conversation.d.ts +43 -0
- package/dist/src/app/proactive/conversation.js +67 -0
- package/dist/src/app/proactive/conversation.js.map +1 -0
- package/dist/src/app/proactive/conversationBuilder.d.ts +54 -0
- package/dist/src/app/proactive/conversationBuilder.js +110 -0
- package/dist/src/app/proactive/conversationBuilder.js.map +1 -0
- package/dist/src/app/proactive/conversationReferenceBuilder.d.ts +68 -0
- package/dist/src/app/proactive/conversationReferenceBuilder.js +125 -0
- package/dist/src/app/proactive/conversationReferenceBuilder.js.map +1 -0
- package/dist/src/app/proactive/createConversationOptions.d.ts +30 -0
- package/dist/src/app/proactive/createConversationOptions.js +10 -0
- package/dist/src/app/proactive/createConversationOptions.js.map +1 -0
- package/dist/src/app/proactive/createConversationOptionsBuilder.d.ts +69 -0
- package/dist/src/app/proactive/createConversationOptionsBuilder.js +141 -0
- package/dist/src/app/proactive/createConversationOptionsBuilder.js.map +1 -0
- package/dist/src/app/proactive/index.d.ts +7 -0
- package/dist/src/app/proactive/index.js +26 -0
- package/dist/src/app/proactive/index.js.map +1 -0
- package/dist/src/app/proactive/proactive.d.ts +248 -0
- package/dist/src/app/proactive/proactive.js +271 -0
- package/dist/src/app/proactive/proactive.js.map +1 -0
- package/dist/src/app/proactive/proactiveOptions.d.ts +19 -0
- package/dist/src/app/proactive/proactiveOptions.js +5 -0
- package/dist/src/app/proactive/proactiveOptions.js.map +1 -0
- package/dist/src/errorHelper.js +94 -0
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/turnContext.js +7 -1
- package/dist/src/turnContext.js.map +1 -1
- package/package.json +2 -2
- package/src/app/agentApplication.ts +130 -79
- package/src/app/agentApplicationBuilder.ts +11 -0
- package/src/app/agentApplicationOptions.ts +7 -0
- package/src/app/auth/authorizationManager.ts +14 -9
- package/src/app/auth/handlers/azureBotAuthorization.ts +3 -3
- package/src/app/index.ts +1 -0
- package/src/app/proactive/conversation.ts +87 -0
- package/src/app/proactive/conversationBuilder.ts +139 -0
- package/src/app/proactive/conversationReferenceBuilder.ts +161 -0
- package/src/app/proactive/createConversationOptions.ts +35 -0
- package/src/app/proactive/createConversationOptionsBuilder.ts +181 -0
- package/src/app/proactive/index.ts +10 -0
- package/src/app/proactive/proactive.ts +481 -0
- package/src/app/proactive/proactiveOptions.ts +24 -0
- package/src/errorHelper.ts +108 -0
- package/src/turnContext.ts +6 -1
|
@@ -20,7 +20,10 @@ import { RouteList } from './routeList'
|
|
|
20
20
|
import { TranscriptLoggerMiddleware } from '../transcript'
|
|
21
21
|
import { CloudAdapter } from '../cloudAdapter'
|
|
22
22
|
import { Authorization, UserAuthorization, AuthorizationManager } from './auth'
|
|
23
|
+
import { Proactive } from './proactive'
|
|
23
24
|
import { JwtPayload } from 'jsonwebtoken'
|
|
25
|
+
import { ExceptionHelper } from '@microsoft/agents-activity'
|
|
26
|
+
import { Errors } from '../errorHelper'
|
|
24
27
|
|
|
25
28
|
const logger = debug('agents:app')
|
|
26
29
|
|
|
@@ -76,6 +79,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
76
79
|
private readonly _adapter?: CloudAdapter
|
|
77
80
|
private readonly _authorizationManager?: AuthorizationManager
|
|
78
81
|
private readonly _authorization?: Authorization
|
|
82
|
+
private readonly _proactive?: Proactive<TState>
|
|
79
83
|
private _typingTimer: NodeJS.Timeout | undefined
|
|
80
84
|
protected readonly _extensions: AgentExtension<TState>[] = []
|
|
81
85
|
private readonly _adaptiveCards: AdaptiveCardsActions<TState>
|
|
@@ -130,6 +134,14 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
130
134
|
this._authorization = new UserAuthorization(this._authorizationManager)
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
// Create Proactive whenever proactive options are explicitly configured or a storage
|
|
138
|
+
// backend is available — no explicit `proactive` option is required.
|
|
139
|
+
if (this._options.proactive !== undefined || this._options.storage !== undefined) {
|
|
140
|
+
const proactiveOpts = this._options.proactive ?? {}
|
|
141
|
+
const proactiveStorage = proactiveOpts.storage ?? this._options.storage
|
|
142
|
+
this._proactive = new Proactive<TState>(this, { ...proactiveOpts, storage: proactiveStorage })
|
|
143
|
+
}
|
|
144
|
+
|
|
133
145
|
if (this._options.longRunningMessages && !this._adapter && !this._options.agentAppId) {
|
|
134
146
|
throw new Error('The Application.longRunningMessages property is unavailable because no adapter was configured in the app.')
|
|
135
147
|
}
|
|
@@ -157,6 +169,27 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
157
169
|
return this._authorization
|
|
158
170
|
}
|
|
159
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Gets the proactive messaging subsystem.
|
|
174
|
+
*
|
|
175
|
+
* @throws Error if no storage backend was configured (neither `options.storage` nor
|
|
176
|
+
* `options.proactive.storage`).
|
|
177
|
+
*/
|
|
178
|
+
public get proactive (): Proactive<TState> {
|
|
179
|
+
if (!this._proactive) {
|
|
180
|
+
throw ExceptionHelper.generateException(Error, Errors.ProactivePropertyUnavailable)
|
|
181
|
+
}
|
|
182
|
+
return this._proactive
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Returns `true` if user authorization was configured, without throwing.
|
|
187
|
+
* Used internally by the Proactive subsystem to check whether token acquisition is available.
|
|
188
|
+
*/
|
|
189
|
+
public get hasUserAuthorization (): boolean {
|
|
190
|
+
return this._authorization !== undefined
|
|
191
|
+
}
|
|
192
|
+
|
|
160
193
|
/**
|
|
161
194
|
* Gets the options used to configure the application.
|
|
162
195
|
*
|
|
@@ -583,10 +616,10 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
583
616
|
* While this method is public, it's typically called internally by the `run` method.
|
|
584
617
|
*
|
|
585
618
|
* The method performs the following operations:
|
|
586
|
-
* 1.
|
|
587
|
-
* 2.
|
|
588
|
-
* 3.
|
|
589
|
-
* 4.
|
|
619
|
+
* 1. Handles authentication flows for routes that have auth handlers configured. If NOT authorized, it will not continue with the 2nd step, returning false.
|
|
620
|
+
* 2. Starts typing timer if configured
|
|
621
|
+
* 3. Processes mentions if configured
|
|
622
|
+
* 4. Loads turn state
|
|
590
623
|
* 5. Downloads files if file downloaders are configured
|
|
591
624
|
* 6. Executes before-turn event handlers
|
|
592
625
|
* 7. Routes to appropriate handlers
|
|
@@ -600,76 +633,98 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
600
633
|
* console.log('No handler matched the activity');
|
|
601
634
|
* }
|
|
602
635
|
* ```
|
|
603
|
-
*
|
|
604
636
|
*/
|
|
605
637
|
public async runInternal (turnContext: TurnContext): Promise<boolean> {
|
|
606
|
-
|
|
638
|
+
const { authorized, context } = await this.handleAuthorization(turnContext)
|
|
639
|
+
|
|
640
|
+
if (!authorized) {
|
|
641
|
+
// We don't log a message here because it is handled by the authorization manager and could cause confusion during mid sign-in operations.
|
|
607
642
|
return false
|
|
608
643
|
}
|
|
609
644
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (this._options.startTypingTimer) {
|
|
614
|
-
this.startTypingTimer(context)
|
|
615
|
-
}
|
|
645
|
+
const isLongRunning =
|
|
646
|
+
(turnContext.activity.type === ActivityTypes.Invoke && turnContext.activity.name === 'signin/tokenExchange') ||
|
|
647
|
+
(this._options.longRunningMessages && turnContext.activity.type === ActivityTypes.Message)
|
|
616
648
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
649
|
+
if (isLongRunning) {
|
|
650
|
+
logger.debug('Starting long-running messages for activity:', context.activity.id!)
|
|
651
|
+
this.startLongRunningCall(context, ctx => this.runTurn(ctx))
|
|
652
|
+
return true
|
|
653
|
+
}
|
|
620
654
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
655
|
+
logger.info('Running application with activity:', context.activity.id!)
|
|
656
|
+
return this.runTurn(context)
|
|
657
|
+
}
|
|
624
658
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
659
|
+
/**
|
|
660
|
+
* Determines if the incoming activity is authorized to be processed by the application.
|
|
661
|
+
* @returns An object containing the authorization status and the context (could have the continuation activity) to be used for further processing.
|
|
662
|
+
*/
|
|
663
|
+
private async handleAuthorization (context: TurnContext) {
|
|
664
|
+
if (context.activity.type === ActivityTypes.Typing) {
|
|
665
|
+
return { authorized: true, context }
|
|
666
|
+
}
|
|
628
667
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
668
|
+
return this._authorizationManager?.process(context, async activity => {
|
|
669
|
+
// The incoming activity may come from the storage, so we need to restore the auth handlers.
|
|
670
|
+
// Since the current route may not have auth handlers.
|
|
671
|
+
const route = await this.getRoute(new TurnContext(context.adapter, activity, context.identity))
|
|
672
|
+
return route?.authHandlers ?? []
|
|
673
|
+
}) ?? { authorized: true, context } // If no authorization manager is configured, we assume the activity is authorized.
|
|
674
|
+
}
|
|
635
675
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
676
|
+
/**
|
|
677
|
+
* Executes the turn processing logic for the given context, including routing and handler execution.
|
|
678
|
+
*/
|
|
679
|
+
private async runTurn (context: TurnContext): Promise<boolean> {
|
|
680
|
+
try {
|
|
681
|
+
if (this._options.startTypingTimer) {
|
|
682
|
+
this.startTypingTimer(context)
|
|
683
|
+
}
|
|
640
684
|
|
|
641
|
-
|
|
685
|
+
if (this._options.removeRecipientMention && context.activity.type === ActivityTypes.Message) {
|
|
686
|
+
context.activity.removeRecipientMention()
|
|
687
|
+
}
|
|
642
688
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
689
|
+
if (this._options.normalizeMentions && context.activity.type === ActivityTypes.Message) {
|
|
690
|
+
context.activity.normalizeMentions()
|
|
691
|
+
}
|
|
647
692
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
652
|
-
}
|
|
693
|
+
const { storage, turnStateFactory } = this._options
|
|
694
|
+
const state = turnStateFactory()
|
|
695
|
+
await state.load(context, storage)
|
|
653
696
|
|
|
654
|
-
|
|
655
|
-
await state.save(context, storage)
|
|
656
|
-
return false
|
|
657
|
-
}
|
|
697
|
+
const route = await this.getRoute(context)
|
|
658
698
|
|
|
659
|
-
|
|
699
|
+
if (!route) {
|
|
700
|
+
logger.debug('No matching route found for activity:', context.activity)
|
|
701
|
+
return false
|
|
702
|
+
}
|
|
660
703
|
|
|
661
|
-
|
|
662
|
-
|
|
704
|
+
if (Array.isArray(this._options.fileDownloaders) && this._options.fileDownloaders.length > 0) {
|
|
705
|
+
for (let i = 0; i < this._options.fileDownloaders.length; i++) {
|
|
706
|
+
await this._options.fileDownloaders[i].downloadAndStoreFiles(context, state)
|
|
663
707
|
}
|
|
708
|
+
}
|
|
664
709
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
throw err
|
|
669
|
-
} finally {
|
|
670
|
-
this.stopTypingTimer()
|
|
710
|
+
if (!(await this.callEventHandlers(context, state, this._beforeTurn))) {
|
|
711
|
+
await state.save(context, storage)
|
|
712
|
+
return false
|
|
671
713
|
}
|
|
672
|
-
|
|
714
|
+
|
|
715
|
+
await route.handler(context, state)
|
|
716
|
+
|
|
717
|
+
if (await this.callEventHandlers(context, state, this._afterTurn)) {
|
|
718
|
+
await state.save(context, storage)
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return true
|
|
722
|
+
} catch (err: any) {
|
|
723
|
+
logger.error(err)
|
|
724
|
+
throw err
|
|
725
|
+
} finally {
|
|
726
|
+
this.stopTypingTimer()
|
|
727
|
+
}
|
|
673
728
|
}
|
|
674
729
|
|
|
675
730
|
/**
|
|
@@ -900,35 +955,31 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
900
955
|
}
|
|
901
956
|
|
|
902
957
|
/**
|
|
903
|
-
* Starts a long-running call
|
|
958
|
+
* Starts a long-running call by continuing the conversation asynchronously (fire-and-forget).
|
|
959
|
+
* The current request/response cycle is not blocked; errors are forwarded to the adapter's error handler.
|
|
904
960
|
*
|
|
905
961
|
* @param context - The turn context for the current conversation.
|
|
906
|
-
* @param handler - The handler function to execute.
|
|
907
|
-
* @returns A promise that resolves to the result of the handler.
|
|
962
|
+
* @param handler - The handler function to execute in the continued conversation.
|
|
908
963
|
*/
|
|
909
964
|
protected startLongRunningCall (
|
|
910
965
|
context: TurnContext,
|
|
911
|
-
handler: (context: TurnContext) => Promise<
|
|
912
|
-
)
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
})
|
|
929
|
-
} else {
|
|
930
|
-
return handler(context)
|
|
931
|
-
}
|
|
966
|
+
handler: (context: TurnContext) => Promise<any>
|
|
967
|
+
) {
|
|
968
|
+
const activity = Activity.fromObject(context.activity)
|
|
969
|
+
this.continueConversationAsync(context.identity, activity.getConversationReference(), async (ctx) => {
|
|
970
|
+
try {
|
|
971
|
+
Object.assign(ctx.activity, activity)
|
|
972
|
+
await handler(ctx)
|
|
973
|
+
} catch (err) {
|
|
974
|
+
if (this.adapter.onTurnError && err instanceof Error) {
|
|
975
|
+
await this.adapter.onTurnError(ctx, err)
|
|
976
|
+
} else {
|
|
977
|
+
throw err
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}).catch(err => {
|
|
981
|
+
logger.error(`Unhandled error in long-running call for activity '${activity.type}' (id: ${activity.id}):`, err)
|
|
982
|
+
})
|
|
932
983
|
}
|
|
933
984
|
|
|
934
985
|
/**
|
|
@@ -7,6 +7,7 @@ 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'
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -64,6 +65,16 @@ export class AgentApplicationBuilder<TState extends TurnState = TurnState> {
|
|
|
64
65
|
return this
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Configures the proactive messaging subsystem.
|
|
70
|
+
* @param options Proactive options including optional storage backend
|
|
71
|
+
* @returns This builder instance for chaining
|
|
72
|
+
*/
|
|
73
|
+
public withProactive (options: ProactiveOptions): this {
|
|
74
|
+
this._options.proactive = options
|
|
75
|
+
return this
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
/**
|
|
68
79
|
* Builds and returns a new AgentApplication instance configured with the provided options.
|
|
69
80
|
* @returns A new AgentApplication instance
|
|
@@ -12,6 +12,7 @@ import { TurnState } from './turnState'
|
|
|
12
12
|
import { HeaderPropagationDefinition } from '../headerPropagation'
|
|
13
13
|
import { AuthorizationOptions } from './auth/types'
|
|
14
14
|
import { Connections } from '../auth/connections'
|
|
15
|
+
import { ProactiveOptions } from './proactive'
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Configuration options for creating and initializing an Agent Application.
|
|
@@ -147,4 +148,10 @@ export interface AgentApplicationOptions<TState extends TurnState> {
|
|
|
147
148
|
* identity providers.
|
|
148
149
|
*/
|
|
149
150
|
connections?: Connections
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Optional. Configuration for the proactive messaging subsystem.
|
|
154
|
+
* When provided, `app.proactive` will be available.
|
|
155
|
+
*/
|
|
156
|
+
proactive?: ProactiveOptions
|
|
150
157
|
}
|
|
@@ -29,6 +29,10 @@ interface AuthorizationManagerProcessResult {
|
|
|
29
29
|
* Indicates whether the authorization was successful.
|
|
30
30
|
*/
|
|
31
31
|
authorized: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The context associated with the authorization process.
|
|
34
|
+
*/
|
|
35
|
+
context: TurnContext;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
/**
|
|
@@ -111,13 +115,16 @@ export class AuthorizationManager {
|
|
|
111
115
|
if (active !== undefined && active?.data.activity.conversation?.id !== context.activity.conversation?.id) {
|
|
112
116
|
logger.warn('Discarding the active session due to the conversation has changed during an active sign-in process', active?.data.activity)
|
|
113
117
|
await storage.delete()
|
|
114
|
-
return { authorized: true }
|
|
118
|
+
return { authorized: true, context }
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
const handlers = active?.handlers ?? this.mapHandlers(await getHandlerIds(context.activity) ?? []) ?? []
|
|
118
122
|
|
|
123
|
+
// Create a shallow copy to modify the activity, since the signin process depends on it and we want to ensure the next handler depends on the initial activity, not the modified one.
|
|
124
|
+
const sharedContext = new TurnContext(context)
|
|
125
|
+
|
|
119
126
|
for (const handler of handlers) {
|
|
120
|
-
const status = await this.signin(storage, handler,
|
|
127
|
+
const status = await this.signin(storage, handler, sharedContext, active?.data)
|
|
121
128
|
logger.debug(this.prefix(handler.id, `Sign-in status: ${status}`))
|
|
122
129
|
|
|
123
130
|
if (status === AuthorizationHandlerStatus.IGNORED) {
|
|
@@ -126,17 +133,17 @@ export class AuthorizationManager {
|
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
if (status === AuthorizationHandlerStatus.PENDING) {
|
|
129
|
-
return { authorized: false }
|
|
136
|
+
return { authorized: false, context: sharedContext }
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
if (status === AuthorizationHandlerStatus.REJECTED) {
|
|
133
140
|
await storage.delete()
|
|
134
|
-
return { authorized: false }
|
|
141
|
+
return { authorized: false, context: sharedContext }
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
if (status === AuthorizationHandlerStatus.REVALIDATE) {
|
|
138
145
|
await storage.delete()
|
|
139
|
-
return this.process(
|
|
146
|
+
return this.process(sharedContext, getHandlerIds)
|
|
140
147
|
}
|
|
141
148
|
|
|
142
149
|
if (status !== AuthorizationHandlerStatus.APPROVED) {
|
|
@@ -146,14 +153,12 @@ export class AuthorizationManager {
|
|
|
146
153
|
await storage.delete()
|
|
147
154
|
|
|
148
155
|
if (active) {
|
|
149
|
-
|
|
150
|
-
// This is done like this to avoid losing data that may be set in the turn context.
|
|
151
|
-
(context as any)._activity = Activity.fromObject(active.data.activity)
|
|
156
|
+
(sharedContext as any)._activity = Activity.fromObject(active.data.activity)
|
|
152
157
|
active = undefined
|
|
153
158
|
}
|
|
154
159
|
}
|
|
155
160
|
|
|
156
|
-
return { authorized: true }
|
|
161
|
+
return { authorized: true, context: sharedContext }
|
|
157
162
|
}
|
|
158
163
|
|
|
159
164
|
/**
|
|
@@ -307,7 +307,8 @@ export class AzureBotAuthorization implements AuthorizationHandler {
|
|
|
307
307
|
if (status !== AuthorizationHandlerStatus.IGNORED) {
|
|
308
308
|
return status
|
|
309
309
|
}
|
|
310
|
-
} else if (active.category === Category.SIGNIN) {
|
|
310
|
+
} else if (active.category === Category.SIGNIN && activity.channelId === Channels.Msteams) {
|
|
311
|
+
// Specific to MS Teams, M365 does not send signin/verifyState when user consent is required.
|
|
311
312
|
// This is only for safety in case of unexpected behaviors during the MS Teams sign-in process,
|
|
312
313
|
// e.g., user interrupts the flow by clicking the Consent Cancel button.
|
|
313
314
|
logger.warn(this.prefix('The incoming activity will be revalidated due to a change in the sign-in flow'), activity)
|
|
@@ -550,8 +551,7 @@ export class AzureBotAuthorization implements AuthorizationHandler {
|
|
|
550
551
|
* Sends an InvokeResponse activity if the channel is Microsoft Teams, including Copilot within MS Teams.
|
|
551
552
|
*/
|
|
552
553
|
private sendInvokeResponse <T>(context: TurnContext, response: InvokeResponse<T>) {
|
|
553
|
-
|
|
554
|
-
if (parentChannel !== Channels.Msteams) {
|
|
554
|
+
if (context.activity.channelIdChannel !== Channels.Msteams) {
|
|
555
555
|
return Promise.resolve()
|
|
556
556
|
}
|
|
557
557
|
|
package/src/app/index.ts
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import type { JwtPayload } from 'jsonwebtoken'
|
|
5
|
+
import type { ConversationReference } from '@microsoft/agents-activity'
|
|
6
|
+
import { ExceptionHelper } from '@microsoft/agents-activity'
|
|
7
|
+
import type { TurnContext } from '../../turnContext'
|
|
8
|
+
import { Errors } from '../../errorHelper'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* JWT-like claims identifying the agent for proactive authentication.
|
|
12
|
+
* `aud` (the agent's client ID) is required; all other fields are optional.
|
|
13
|
+
*/
|
|
14
|
+
export interface ConversationClaims {
|
|
15
|
+
aud: string
|
|
16
|
+
azp?: string
|
|
17
|
+
appid?: string
|
|
18
|
+
tid?: string
|
|
19
|
+
[key: string]: string | undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A serializable pair of a `ConversationReference` and the JWT claims needed
|
|
24
|
+
* to authenticate proactive calls on behalf of this agent.
|
|
25
|
+
*
|
|
26
|
+
* Instances are stored in and retrieved from the proactive storage backend.
|
|
27
|
+
* The `identity` getter produces the `JwtPayload` shape expected by
|
|
28
|
+
* `adapter.continueConversation()`.
|
|
29
|
+
*/
|
|
30
|
+
export class Conversation {
|
|
31
|
+
reference: ConversationReference
|
|
32
|
+
claims: ConversationClaims
|
|
33
|
+
|
|
34
|
+
constructor (context: TurnContext)
|
|
35
|
+
constructor (claims: ConversationClaims, reference: ConversationReference)
|
|
36
|
+
constructor (
|
|
37
|
+
contextOrClaims: TurnContext | ConversationClaims,
|
|
38
|
+
reference?: ConversationReference
|
|
39
|
+
) {
|
|
40
|
+
if ('activity' in contextOrClaims) {
|
|
41
|
+
// TurnContext overload
|
|
42
|
+
const context = contextOrClaims as TurnContext
|
|
43
|
+
this.reference = context.activity.getConversationReference()
|
|
44
|
+
const id = context.identity as JwtPayload | undefined
|
|
45
|
+
this.claims = {
|
|
46
|
+
...(id ?? {}),
|
|
47
|
+
aud: Array.isArray(id?.aud) ? (id.aud as string[])[0] : (id?.aud ?? '')
|
|
48
|
+
} as ConversationClaims
|
|
49
|
+
} else {
|
|
50
|
+
// (claims, reference) overload — matches C# parameter order
|
|
51
|
+
this.claims = contextOrClaims as ConversationClaims
|
|
52
|
+
this.reference = reference!
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns a `JwtPayload`-compatible object for passing to
|
|
58
|
+
* `adapter.continueConversation()` as `botAppIdOrIdentity`.
|
|
59
|
+
*/
|
|
60
|
+
get identity (): JwtPayload {
|
|
61
|
+
return this.claims as unknown as JwtPayload
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns a JSON string of `{ reference, claims }` — suitable for use in
|
|
66
|
+
* HTTP request bodies when passing a conversation to another service.
|
|
67
|
+
*/
|
|
68
|
+
toJson (): string {
|
|
69
|
+
return JSON.stringify({ reference: this.reference, claims: this.claims })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Throws if any required field is missing.
|
|
74
|
+
* Called by `Proactive.storeConversation()` before writing to storage.
|
|
75
|
+
*/
|
|
76
|
+
validate (): void {
|
|
77
|
+
if (!this.reference.conversation?.id) {
|
|
78
|
+
throw ExceptionHelper.generateException(Error, Errors.ConversationInvalidId)
|
|
79
|
+
}
|
|
80
|
+
if (!this.reference.serviceUrl) {
|
|
81
|
+
throw ExceptionHelper.generateException(Error, Errors.ConversationInvalidServiceUrl)
|
|
82
|
+
}
|
|
83
|
+
if (!this.claims.aud) {
|
|
84
|
+
throw ExceptionHelper.generateException(Error, Errors.ConversationInvalidAud)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import type { ChannelAccount, ConversationAccount, ConversationReference } from '@microsoft/agents-activity'
|
|
5
|
+
import type { TurnContext } from '../../turnContext'
|
|
6
|
+
import { Conversation, type ConversationClaims } from './conversation'
|
|
7
|
+
import { ConversationReferenceBuilder } from './conversationReferenceBuilder'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fluent builder for the `Conversation` class.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Build from scratch
|
|
15
|
+
* const conv = ConversationBuilder
|
|
16
|
+
* .create('my-client-id', 'msteams')
|
|
17
|
+
* .withUser('user-aad-id')
|
|
18
|
+
* .withConversationId('19:channel@thread.skype')
|
|
19
|
+
* .build()
|
|
20
|
+
*
|
|
21
|
+
* // Build from a live TurnContext
|
|
22
|
+
* const conv = ConversationBuilder.fromContext(turnContext).build()
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export class ConversationBuilder {
|
|
26
|
+
private readonly _agentClientId: string
|
|
27
|
+
private readonly _channelId: string
|
|
28
|
+
private readonly _serviceUrl: string
|
|
29
|
+
private _claims: ConversationClaims
|
|
30
|
+
private _reference: Partial<ConversationReference>
|
|
31
|
+
|
|
32
|
+
private constructor (
|
|
33
|
+
agentClientId: string,
|
|
34
|
+
channelId: string,
|
|
35
|
+
serviceUrl: string,
|
|
36
|
+
claims: ConversationClaims,
|
|
37
|
+
reference: Partial<ConversationReference>
|
|
38
|
+
) {
|
|
39
|
+
this._agentClientId = agentClientId
|
|
40
|
+
this._channelId = channelId
|
|
41
|
+
this._serviceUrl = serviceUrl
|
|
42
|
+
this._claims = claims
|
|
43
|
+
this._reference = reference
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates a new builder for the given agent and channel.
|
|
48
|
+
* @param requestorId Optional: the client ID of the app making the request.
|
|
49
|
+
* Becomes the `appid` claim, used in multi-tenant Azure Bot scenarios.
|
|
50
|
+
*/
|
|
51
|
+
static create (
|
|
52
|
+
agentClientId: string,
|
|
53
|
+
channelId: string,
|
|
54
|
+
serviceUrl?: string,
|
|
55
|
+
requestorId?: string
|
|
56
|
+
): ConversationBuilder {
|
|
57
|
+
const claims: ConversationClaims = { aud: agentClientId }
|
|
58
|
+
if (requestorId) claims.appid = requestorId
|
|
59
|
+
return new ConversationBuilder(agentClientId, channelId, serviceUrl ?? '', claims, ConversationReferenceBuilder.create(agentClientId, channelId, serviceUrl).build())
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a builder pre-populated from a live `TurnContext`.
|
|
64
|
+
* Captures both the conversation reference and the JWT identity claims.
|
|
65
|
+
*/
|
|
66
|
+
static fromContext (context: TurnContext): ConversationBuilder {
|
|
67
|
+
const ref = context.activity.getConversationReference()
|
|
68
|
+
const id = context.identity
|
|
69
|
+
const aud = Array.isArray(id?.aud) ? id.aud[0] : (id?.aud ?? '')
|
|
70
|
+
const claims: ConversationClaims = {
|
|
71
|
+
...(id ?? {}),
|
|
72
|
+
aud,
|
|
73
|
+
} as ConversationClaims
|
|
74
|
+
return new ConversationBuilder(
|
|
75
|
+
aud,
|
|
76
|
+
ref.channelId,
|
|
77
|
+
ref.serviceUrl ?? '',
|
|
78
|
+
claims,
|
|
79
|
+
ref
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Sets `reference.user`. */
|
|
84
|
+
withUser (userId: string, userName?: string): this {
|
|
85
|
+
const user: ChannelAccount = { id: userId, name: userName }
|
|
86
|
+
this._reference = { ...this._reference, user }
|
|
87
|
+
return this
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Sets `reference.conversation.id`. */
|
|
91
|
+
withConversationId (id: string): this {
|
|
92
|
+
const conversation: ConversationAccount = { ...(this._reference.conversation ?? { isGroup: false }), id }
|
|
93
|
+
this._reference = { ...this._reference, conversation }
|
|
94
|
+
return this
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Sets `reference.conversation` from a full `ConversationAccount`. */
|
|
98
|
+
withConversation (account: ConversationAccount): this {
|
|
99
|
+
this._reference = { ...this._reference, conversation: account }
|
|
100
|
+
return this
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Sets `reference.activityId`. */
|
|
104
|
+
withActivityId (activityId: string): this {
|
|
105
|
+
this._reference = { ...this._reference, activityId }
|
|
106
|
+
return this
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Merges a partial `ConversationReference` into the current one.
|
|
111
|
+
* Useful for overlaying externally-provided reference data without losing
|
|
112
|
+
* fields set by earlier builder calls.
|
|
113
|
+
*/
|
|
114
|
+
withReference (ref: Partial<ConversationReference>): this {
|
|
115
|
+
this._reference = { ...this._reference, ...ref }
|
|
116
|
+
return this
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Builds the `Conversation`, auto-filling `serviceUrl` from channel defaults if needed. */
|
|
120
|
+
build (): Conversation {
|
|
121
|
+
const serviceUrl =
|
|
122
|
+
this._serviceUrl ||
|
|
123
|
+
this._reference.serviceUrl ||
|
|
124
|
+
ConversationReferenceBuilder.serviceUrlForChannel(this._channelId)
|
|
125
|
+
|
|
126
|
+
const reference: ConversationReference = {
|
|
127
|
+
conversation: this._reference.conversation ?? { id: '', isGroup: false },
|
|
128
|
+
agent: this._reference.agent ?? { id: this._agentClientId },
|
|
129
|
+
...this._reference,
|
|
130
|
+
// Ensure channelId and serviceUrl always use our resolved values
|
|
131
|
+
channelId: this._channelId,
|
|
132
|
+
serviceUrl,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const conv = new Conversation(this._claims, reference)
|
|
136
|
+
conv.validate()
|
|
137
|
+
return conv
|
|
138
|
+
}
|
|
139
|
+
}
|