@@ -36,15 +36,14 @@ def __init__(self, dialog_options: SkillDialogOptions, dialog_id: str):
36
36
37
37
self .dialog_options = dialog_options
38
38
self ._deliver_mode_state_key = "deliverymode"
39
- self ._sso_connection_name_key = "SkillDialog.SSOConnectionName"
40
39
41
40
async def begin_dialog (self , dialog_context : DialogContext , options : object = None ):
42
F438
41
"""
43
42
Method called when a new dialog has been pushed onto the stack and is being activated.
44
43
:param dialog_context: The dialog context for the current turn of conversation.
45
44
:param options: (Optional) additional argument(s) to pass to the dialog being started.
46
45
"""
47
- dialog_args = SkillDialog ._validate_begin_dialog_args (options )
46
+ dialog_args = self ._validate_begin_dialog_args (options )
48
47
49
48
await dialog_context .context .send_trace_activity (
50
49
f"{ SkillDialog .__name__ } .BeginDialogAsync()" ,
@@ -61,24 +60,22 @@ async def begin_dialog(self, dialog_context: DialogContext, options: object = No
61
60
is_incoming = True ,
62
61
)
63
62
63
+ # Store delivery mode in dialog state for later use.
64
64
dialog_context .active_dialog .state [
65
65
self ._deliver_mode_state_key
66
66
] = dialog_args .activity .delivery_mode
67
67
68
- dialog_context .active_dialog .state [
69
- self ._sso_connection_name_key
70
- ] = dialog_args .connection_name
71
-
72
68
# Send the activity to the skill.
73
- eoc_activity = await self ._send_to_skill (
74
- dialog_context .context , skill_activity , dialog_args .connection_name
75
- )
69
+ eoc_activity = await self ._send_to_skill (dialog_context .context , skill_activity )
76
70
if eoc_activity :
77
71
return await dialog_context .end_dialog (eoc_activity .value )
78
72
79
73
return self .end_of_turn
80
74
81
75
async def continue_dialog (self , dialog_context : DialogContext ):
76
+ if not self ._on_validate_activity (dialog_context .context .activity ):
77
+ return self .end_of_turn
78
+
82
79
await dialog_context .context .send_trace_activity (
83
80
f"{ SkillDialog .__name__ } .continue_dialog()" ,
84
81
label = f"ActivityType: { dialog_context .context .activity .type } " ,
@@ -98,17 +95,13 @@ async def continue_dialog(self, dialog_context: DialogContext):
98
95
99
96
# Create deep clone of the original activity to avoid altering it before forwarding it.
100
97
skill_activity = deepcopy (dialog_context .context .activity )
98
+
101
99
skill_activity .delivery_mode = dialog_context .active_dialog .state [
102
100
self ._deliver_mode_state_key
103
101
]
104
- connection_name = dialog_context .active_dialog .state [
105
- self ._sso_connection_name_key
106
- ]
107
102
108
103
# Just forward to the remote skill
109
- eoc_activity = await self ._send_to_skill (
110
- dialog_context .context , skill_activity , connection_name
111
- )
104
+ eoc_activity = await self ._send_to_skill (dialog_context .context , skill_activity )
112
105
if eoc_activity :
113
106
return await dialog_context .end_dialog (eoc_activity .value )
114
107
@@ -163,8 +156,7 @@ async def end_dialog(
163
156
164
157
await super ().end_dialog (context , instance , reason )
165
158
166
- @staticmethod
167
- def _validate_begin_dialog_args (options : object ) -> BeginSkillDialogOptions :
159
+ def _validate_begin_dialog_args (self , options : object ) -> BeginSkillDialogOptions :
168
160
if not options :
169
161
raise TypeError ("options cannot be None." )
170
162
@@ -182,26 +174,36 @@ def _validate_begin_dialog_args(options: object) -> BeginSkillDialogOptions:
182
174
183
175
return dialog_args
184
176
177
+ def _on_validate_activity (
178
+ self , activity : Activity # pylint: disable=unused-argument
179
+ ) -> bool :
180
+ """
181
+ Validates the activity sent during continue_dialog.
182
+
183
+ Override this method to implement a custom validator for the activity being sent during continue_dialog.
184
+ This method can be used to ignore activities of a certain type if needed.
185
+ If this method returns false, the dialog will end the turn without processing the activity.
186
+ """
187
+ return True
188
+
185
189
async def _send_to_skill (
186
- self , context : TurnContext , activity : Activity , connection_name : str = None
190
+ self , context : TurnContext , activity : Activity
187
191
) -> Activity :
188
- # Create a conversationId to interact with the skill and send the activity
189
- conversation_id_factory_options = SkillConversationIdFactoryOptions (
190
- from_bot_oauth_scope = context .turn_state .get (BotAdapter .BOT_OAUTH_SCOPE_KEY ),
191
- from_bot_id = self .dialog_options .bot_id ,
192
- activity = activity ,
193
- bot_framework_skill = self .dialog_options .skill ,
194
- )
195
-
196
- skill_conversation_id = await self .dialog_options .conversation_id_factory .create_skill_conversation_id (
197
- conversation_id_factory_options
192
+ if activity .type == ActivityTypes .invoke :
193
+ # Force ExpectReplies for invoke activities so we can get the replies right away and send
194
+ # them back to the channel if needed. This makes sure that the dialog will receive the Invoke
195
+ # response from the skill and any other activities sent, including EoC.
196
+ activity .delivery_mode = DeliveryModes .expect_replies
197
+
198
+ skill_conversation_id = await self ._create_skill_conversation_id (
199
+ context , activity
198
200
)
199
201
200
202
# Always save state before forwarding
201
203
# (the dialog stack won't get updated with the skillDialog and things won't work if you don't)
202
- skill_info = self .dialog_options .skill
203
204
await self .dialog_options .conversation_state .save_changes (context , True )
204
205
206
+ skill_info = self .dialog_options .skill
205
207
response = await self .dialog_options .skill_client .post_activity (
206
208
self .dialog_options .bot_id ,
207
209
skill_info .app_id ,
@@ -229,7 +231,7 @@ async def _send_to_skill(
229
231
# Capture the EndOfConversation activity if it was sent from skill
230
232
eoc_activity = from_skill_activity
231
233
elif await self ._intercept_oauth_cards (
232
- context , from_skill_activity , connection_name
234
+ context , from_skill_activity , self . dialog_options . connection_name
233
235
):
234
236
# do nothing. Token exchange succeeded, so no oauthcard needs to be shown to the user
235
237
pass
@@ -239,6 +241,21 @@ async def _send_to_skill(
239
241
240
242
return eoc_activity
241
243
244
+ async def _create_skill_conversation_id (
245
+ self , context : TurnContext , activity : Activity
246
+ ) -> str :
247
+ # Create a conversationId to interact with the skill and send the activity
248
+ conversation_id_factory_options = SkillConversationIdFactoryOptions (
249
+ from_bot_oauth_scope = context .turn_state .get (BotAdapter .BOT_OAUTH_SCOPE_KEY ),
250
+ from_bot_id = self .dialog_options .bot_id ,
251
+ activity = activity ,
252
+ bot_framework_skill = self .dialog_options .skill ,
253
+ )
254
+ skill_conversation_id = await self .dialog_options .conversation_id_factory .create_skill_conversation_id (
255
+ conversation_id_factory_options
256
+ )
257
+ return skill_conversation_id
258
+
242
259
async def _intercept_oauth_cards(
243
260
self , context : TurnContext , activity : Activity , connection_name : str
244
261
):
@@ -248,6 +265,8 @@ async def _intercept_oauth_cards(
248
265
if not connection_name or not isinstance (
249
266
context .adapter , ExtendedUserTokenProvider
250
267
):
268
+ # The adapter may choose not to support token exchange, in which case we fallback to
269
+ # showing an oauth card to the user.
251
270
return False
252
271
253
272
oauth_card_attachment = next (
@@ -273,13 +292,17 @@ async def _intercept_oauth_cards(
273
292
)
274
293
275
294
if result and result .token :
295
+ # If token above is null, then SSO has failed and hence we return false.
296
+ # If not, send an invoke to the skill with the token.
276
297
return await self ._send_token_exchange_invoke_to_skill (
277
298
activity ,
278
299
oauth_card .token_exchange_resource .id ,
279
300
oauth_card .connection_name ,
280
301
result .token ,
281
302
)
282
303
except :
304
+ # Failures in token exchange are not fatal. They simply mean that the user needs
305
+ # to be shown the OAuth card.
283
306
return False
284
307
285
308
return False
@@ -310,4 +333,4 @@ async def _send_token_exchange_invoke_to_skill(
310
333
)
311
334
312
335
# Check response status: true if success, false if failure
313
- return response .status / 100 == 2
336
+ return response .is_successful_status_code ()
0 commit comments