Beispiel #1
0
    async def _send_to_skill(
        self, context: TurnContext, activity: Activity, skill_conversation_id: str
    ) -> Activity:
        if activity.type == ActivityTypes.invoke:
            # Force ExpectReplies for invoke activities so we can get the replies right away and send
            # them back to the channel if needed. This makes sure that the dialog will receive the Invoke
            # response from the skill and any other activities sent, including EoC.
            activity.delivery_mode = DeliveryModes.expect_replies

        # Always save state before forwarding
        # (the dialog stack won't get updated with the skillDialog and things won't work if you don't)
        await self.dialog_options.conversation_state.save_changes(context, True)

        skill_info = self.dialog_options.skill
        response = await self.dialog_options.skill_client.post_activity(
            self.dialog_options.bot_id,
            skill_info.app_id,
            skill_info.skill_endpoint,
            self.dialog_options.skill_host_endpoint,
            skill_conversation_id,
            activity,
        )

        # Inspect the skill response status
        if not 200 <= response.status <= 299:
            raise Exception(
                f'Error invoking the skill id: "{skill_info.id}" at "{skill_info.skill_endpoint}"'
                f" (status is {response.status}). \r\n {response.body}"
            )

        eoc_activity: Activity = None
        if activity.delivery_mode == DeliveryModes.expect_replies and response.body:
            # Process replies in the response.Body.
            response.body: List[Activity]
            response.body = ExpectedReplies().deserialize(response.body).activities

            for from_skill_activity in response.body:
                if from_skill_activity.type == ActivityTypes.end_of_conversation:
                    # Capture the EndOfConversation activity if it was sent from skill
                    eoc_activity = from_skill_activity

                    # The conversation has ended, so cleanup the conversation id
                    await self.dialog_options.conversation_id_factory.delete_conversation_reference(
                        skill_conversation_id
                    )
                elif await self._intercept_oauth_cards(
                    context, from_skill_activity, self.dialog_options.connection_name
                ):
                    # do nothing. Token exchange succeeded, so no oauthcard needs to be shown to the user
                    pass
                else:
                    # Send the response back to the channel.
                    await context.send_activity(from_skill_activity)

        return eoc_activity
Beispiel #2
0
    async def _send_to_skill(
        self,
        context: TurnContext,
        activity: Activity,
    ) -> Activity:
        # Create a conversationId to interact with the skill and send the activity
        conversation_id_factory_options = SkillConversationIdFactoryOptions(
            from_bot_oauth_scope=context.turn_state.get(
                BotAdapter.BOT_OAUTH_SCOPE_KEY),
            from_bot_id=self.dialog_options.bot_id,
            activity=activity,
            bot_framework_skill=self.dialog_options.skill,
        )

        skill_conversation_id = await self.dialog_options.conversation_id_factory.create_skill_conversation_id(
            conversation_id_factory_options)

        # Always save state before forwarding
        # (the dialog stack won't get updated with the skillDialog and things won't work if you don't)
        skill_info = self.dialog_options.skill
        await self.dialog_options.conversation_state.save_changes(
            context, True)

        response = await self.dialog_options.skill_client.post_activity(
            self.dialog_options.bot_id,
            skill_info.app_id,
            skill_info.skill_endpoint,
            self.dialog_options.skill_host_endpoint,
            skill_conversation_id,
            activity,
        )

        # Inspect the skill response status
        if not 200 <= response.status <= 299:
            raise Exception(
                f'Error invoking the skill id: "{skill_info.id}" at "{skill_info.skill_endpoint}"'
                f" (status is {response.status}). \r\n {response.body}")

        eoc_activity: Activity = None
        if activity.delivery_mode == DeliveryModes.expect_replies and response.body:
            # Process replies in the response.Body.
            response.body: List[Activity]
            response.body = ExpectedReplies().deserialize(
                response.body).activities

            for from_skill_activity in response.body:
                if from_skill_activity.type == ActivityTypes.end_of_conversation:
                    # Capture the EndOfConversation activity if it was sent from skill
                    eoc_activity = from_skill_activity
                else:
                    # Send the response back to the channel.
                    await context.send_activity(from_skill_activity)

        return eoc_activity
Beispiel #3
0
    async def __send_to_skill(
        self,
        turn_context: TurnContext,
        delivery_mode: str,
        target_skill: BotFrameworkSkill,
    ):
        # NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill
        # will have access to current accurate state.
        await self._conversation_state.save_changes(turn_context, force=True)

        if delivery_mode == "expectReplies":
            # Clone activity and update its delivery mode.
            activity = copy.copy(turn_context.activity)
            activity.delivery_mode = delivery_mode

            # Route the activity to the skill.
            expect_replies_response = await self._skill_client.post_activity_to_skill(
                self._bot_id,
                target_skill,
                self._skills_config.SKILL_HOST_ENDPOINT,
                activity,
            )

            # Route response activities back to the channel.
            response_activities: ExpectedReplies = (
                ExpectedReplies().deserialize(expect_replies_response.body).activities
            )

            for response_activity in response_activities:
                if response_activity.type == ActivityTypes.end_of_conversation:
                    await self.end_conversation(response_activity, turn_context)

                    # Restart setup dialog.
                    await DialogHelper.run_dialog(
                        self._dialog,
                        turn_context,
                        self._dialog_state_property,
                    )
                else:
                    await turn_context.send_activity(response_activity)

        else:
            # Route the activity to the skill.
            await self._skill_client.post_activity_to_skill(
                self._bot_id,
                target_skill,
                self._skills_config.SKILL_HOST_ENDPOINT,
                turn_context.activity,
            )
Beispiel #4
0
    async def test_delivery_mode_expect_replies(self):
        mock_credential_provider = unittest.mock.create_autospec(CredentialProvider)

        settings = BotFrameworkAdapterSettings(
            app_id="bot_id", credential_provider=mock_credential_provider
        )
        adapter = AdapterUnderTest(settings)

        async def callback(context: TurnContext):
            await context.send_activity("activity 1")
            await context.send_activity("activity 2")
            await context.send_activity("activity 3")

        inbound_activity = Activity(
            type=ActivityTypes.message,
            channel_id="emulator",
            service_url="http://tempuri.org/whatever",
            delivery_mode=DeliveryModes.expect_replies,
            text="hello world",
        )

        identity = ClaimsIdentity(
            claims={
                AuthenticationConstants.AUDIENCE_CLAIM: "bot_id",
                AuthenticationConstants.APP_ID_CLAIM: "bot_id",
                AuthenticationConstants.VERSION_CLAIM: "1.0",
            },
            is_authenticated=True,
        )

        invoke_response = await adapter.process_activity_with_identity(
            inbound_activity, identity, callback
        )
        assert invoke_response
        assert invoke_response.status == 200
        activities = ExpectedReplies().deserialize(invoke_response.body).activities
        assert len(activities) == 3
        assert activities[0].text == "activity 1"
        assert activities[1].text == "activity 2"
        assert activities[2].text == "activity 3"
        assert (
            adapter.connector_client_mock.conversations.send_to_conversation.call_count
            == 0
        )
Beispiel #5
0
 async def post_buffered_activity(
     self,
     from_bot_id: str,
     to_bot_id: str,
     to_url: str,
     service_url: str,
     conversation_id: str,
     activity: Activity,
 ) -> [Activity]:
     """
     Helper method to return a list of activities when an Activity is being
     sent with DeliveryMode == expectReplies.
     """
     response = await self.post_activity(from_bot_id, to_bot_id, to_url,
                                         service_url, conversation_id,
                                         activity)
     if not response or (response.status / 100) != 2:
         return []
     return ExpectedReplies().deserialize(response.body).activities
Beispiel #6
0
    async def test_should_not_intercept_oauth_cards_for_empty_connection_name(
            self):
        connection_name = "connectionName"
        first_response = ExpectedReplies(activities=[
            SkillDialogTests.create_oauth_card_attachment_activity(
                "https://test")
        ])

        sequence = 0

        async def post_return():
            nonlocal sequence
            if sequence == 0:
                result = InvokeResponse(body=first_response,
                                        status=HTTPStatus.OK)
            else:
                result = InvokeResponse(status=HTTPStatus.OK)
            sequence += 1
            return result

        mock_skill_client = self._create_mock_skill_client(None, post_return)
        conversation_state = ConversationState(MemoryStorage())

        dialog_options = SkillDialogTests.create_skill_dialog_options(
            conversation_state, mock_skill_client)
        sut = SkillDialog(dialog_options, dialog_id="dialog")
        activity_to_send = SkillDialogTests.create_send_activity()

        client = DialogTestClient(
            "test",
            sut,
            BeginSkillDialogOptions(activity=activity_to_send, ),
            conversation_state=conversation_state,
        )

        client.test_adapter.add_exchangeable_token(connection_name, "test",
                                                   "User1", "https://test",
                                                   "https://test1")

        final_activity = await client.send_activity(
            MessageFactory.text("irrelevant"))
        self.assertIsNotNone(final_activity)
        self.assertEqual(len(final_activity.attachments), 1)
    def _process_turn_results(self, context: TurnContext) -> InvokeResponse:
        # Handle ExpectedReplies scenarios where all activities have been
        # buffered and sent back at once in an invoke response.
        if context.activity.delivery_mode == DeliveryModes.expect_replies:
            return InvokeResponse(
                status=HTTPStatus.OK,
                body=ExpectedReplies(activities=context.buffered_reply_activities),
            )

        # Handle Invoke scenarios where the bot will return a specific body and return code.
        if context.activity.type == ActivityTypes.invoke:
            activity_invoke_response: Activity = context.turn_state.get(
                self._INVOKE_RESPONSE_KEY
            )
            if not activity_invoke_response:
                return InvokeResponse(status=HTTPStatus.NOT_IMPLEMENTED)

            return activity_invoke_response.value

        # No body to return
        return None
Beispiel #8
0
    def _create_mock_skill_client(
        self,
        callback: Callable,
        return_status: Union[Callable, int] = 200,
        expected_replies: List[Activity] = None,
    ) -> BotFrameworkClient:
        mock_client = Mock()
        activity_list = ExpectedReplies(
            activities=expected_replies or [MessageFactory.text("dummy activity")]
        )

        async def mock_post_activity(
            from_bot_id: str,
            to_bot_id: str,
            to_url: str,
            service_url: str,
            conversation_id: str,
            activity: Activity,
        ):
            nonlocal callback, return_status
            if callback:
                await callback(
                    from_bot_id,
                    to_bot_id,
                    to_url,
                    service_url,
                    conversation_id,
                    activity,
                )

            if isinstance(return_status, Callable):
                return await return_status()
            return InvokeResponse(status=return_status, body=activity_list)

        mock_client.post_activity.side_effect = mock_post_activity

        return mock_client