public override async Task <string> CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions options, CancellationToken cancellationToken)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            // Create the storage key based on the SkillConversationIdFactoryOptions
            var conversationReference = options.Activity.GetConversationReference();
            var storageKey            = $"{conversationReference.Conversation.Id}-{options.BotFrameworkSkill.Id}-{conversationReference.ChannelId}-skillconvo";

            // Create the SkillConversationReference
            var skillConversationReference = new SkillConversationReference
            {
                ConversationReference = conversationReference,
                OAuthScope            = options.FromBotOAuthScope,
            };

            // Store the SkillConversationReference
            var skillConversationInfo = new Dictionary <string, object> {
                { storageKey, JObject.FromObject(skillConversationReference) }
            };
            await _storage.WriteAsync(skillConversationInfo, cancellationToken).ConfigureAwait(false);

            // Return the storageKey (that will be also used as the conversation ID to call the skill)
            return(storageKey);
        }
Example #2
0
        /// <summary>
        /// Uses the SkillConversationIdFactory to create or retrieve a Skill Conversation Id, and sends the activity.
        /// </summary>
        /// <typeparam name="T">The type of body in the InvokeResponse.</typeparam>
        /// <param name="originatingAudience">The oauth audience scope, used during token retrieval. (Either https://api.botframework.com or bot app id.)</param>
        /// <param name="fromBotId">The MicrosoftAppId of the bot sending the activity.</param>
        /// <param name="toSkill">The skill to create the conversation Id for.</param>
        /// <param name="callbackUrl">The callback Url for the skill host.</param>
        /// <param name="activity">The activity to send.</param>
        /// <param name="cancellationToken">A cancellation token.</param>
        /// <returns>Async task with invokeResponse.</returns>
        public async Task <InvokeResponse <T> > PostActivityAsync <T>(string originatingAudience, string fromBotId, BotFrameworkSkill toSkill, Uri callbackUrl, Activity activity, CancellationToken cancellationToken)
        {
            string skillConversationId;

            try
            {
                var options = new SkillConversationIdFactoryOptions
                {
                    FromBotOAuthScope = originatingAudience,
                    FromBotId         = fromBotId,
                    Activity          = activity,
                    BotFrameworkSkill = toSkill
                };
                skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(options, cancellationToken).ConfigureAwait(false);
            }
            catch (NotImplementedException)
            {
                // Attempt to create the ID using deprecated method.
#pragma warning disable 618 // Keeping this for backward compat, this catch should be removed when the deprecated method is removed.
                skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(activity.GetConversationReference(), cancellationToken).ConfigureAwait(false);

#pragma warning restore 618
            }

            return(await PostActivityAsync <T>(fromBotId, toSkill.AppId, toSkill.SkillEndpoint, callbackUrl, skillConversationId, activity, cancellationToken).ConfigureAwait(false));
        }
Example #3
0
        public SkillHandlerTests()
        {
            _claimsIdentity = new ClaimsIdentity();
            _claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _botId));
            _claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AppIdClaim, _skillId));
            _claimsIdentity.AddClaim(new Claim(AuthenticationConstants.ServiceUrlClaim, "http://testbot.com/api/messages"));
            _conversationReference = new ConversationReference
            {
                Conversation = new ConversationAccount(id: Guid.NewGuid().ToString("N")),
                ServiceUrl   = "http://testbot.com/api/messages"
            };

            var activity = (Activity)Activity.CreateMessageActivity();

            activity.ApplyConversationReference(_conversationReference);
            var skill = new BotFrameworkSkill()
            {
                AppId         = _skillId,
                Id            = "skill",
                SkillEndpoint = new Uri("http://testbot.com/api/messages")
            };

            var options = new SkillConversationIdFactoryOptions
            {
                FromBotOAuthScope = _botId,
                FromBotId         = _botId,
                Activity          = activity,
                BotFrameworkSkill = skill
            };

            _conversationId = _testConversationIdFactory.CreateSkillConversationIdAsync(options, CancellationToken.None).Result;
        }
            public async Task <string> CreateAndApplyConversationIdAsync(Activity activity)
            {
                activity.ApplyConversationReference(new ConversationReference
                {
                    Conversation = new ConversationAccount(id: TestBotId),
                    ServiceUrl   = TestBotEndpoint
                });

                var skill = new BotFrameworkSkill
                {
                    AppId         = TestSkillId,
                    Id            = "skill",
                    SkillEndpoint = new Uri(TestSkillEndpoint)
                };

                var options = new SkillConversationIdFactoryOptions
                {
                    FromBotOAuthScope = TestBotId,
                    FromBotId         = TestBotId,
                    Activity          = activity,
                    BotFrameworkSkill = skill
                };

                return(await ConversationIdFactory.CreateSkillConversationIdAsync(options, CancellationToken.None));
            }
Example #5
0
        public async Task ShouldCreateCorrectConversationId()
        {
            var claimsIdentity = new ClaimsIdentity();

            claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _botId));
            claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AppIdClaim, _skillId));
            claimsIdentity.AddClaim(new Claim(AuthenticationConstants.ServiceUrlClaim, "http://testbot.com/api/messages"));
            var conversationReference = new ConversationReference
            {
                Conversation = new ConversationAccount(id: Guid.NewGuid().ToString("N")),
                ServiceUrl   = "http://testbot.com/api/messages"
            };

            var activity = (Activity)Activity.CreateMessageActivity();

            activity.ApplyConversationReference(conversationReference);
            var skill = new BotFrameworkSkill()
            {
                AppId         = _skillId,
                Id            = "skill",
                SkillEndpoint = new Uri("http://testbot.com/api/messages")
            };

            var options = new SkillConversationIdFactoryOptions
            {
                FromBotOAuthScope = _botId,
                FromBotId         = _botId,
                Activity          = activity,
                BotFrameworkSkill = skill
            };

            var conversationId = await _idFactory.CreateSkillConversationIdAsync(options, CancellationToken.None);

            Assert.IsNotNull(conversationId);
        }
Example #6
0
        private async Task SendToSkill(ITurnContext turnContext, BotFrameworkSkill targetSkill, CancellationToken cancellationToken)
        {
            // NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill
            // will have access to current accurate state.
            await _conversationState.SaveChangesAsync(turnContext, force : true, cancellationToken : cancellationToken);

            // Create a conversationId to interact with the skill and send the activity
            var options = new SkillConversationIdFactoryOptions
            {
                FromBotOAuthScope = turnContext.TurnState.Get <string>(BotAdapter.OAuthScopeKey),
                FromBotId         = _botId,
                Activity          = turnContext.Activity,
                BotFrameworkSkill = targetSkill
            };
            var skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(options, cancellationToken);

            using var client = _auth.CreateBotFrameworkClient();

            // route the activity to the skill
            var response = await client.PostActivityAsync(_botId, targetSkill.AppId, targetSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, skillConversationId, turnContext.Activity, cancellationToken);

            // Check response status
            if (!(response.Status >= 200 && response.Status <= 299))
            {
                throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {response.Status}). \r\n {response.Body}");
            }
        }
Example #7
0
            public override Task <string> CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions options, CancellationToken cancellationToken = default)
            {
                var key = (options.Activity.Conversation.Id + options.Activity.ServiceUrl).GetHashCode().ToString(CultureInfo.InvariantCulture);

                _conversationRefs.GetOrAdd(key, new SkillConversationReference
                {
                    ConversationReference = options.Activity.GetConversationReference(),
                    OAuthScope            = options.FromBotOAuthScope
                });
                return(Task.FromResult(key));
            }
Example #8
0
            public override Task <string> CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions options, CancellationToken cancellationToken)
            {
                var skillConversationReference = new SkillConversationReference
                {
                    ConversationReference = options.Activity.GetConversationReference(),
                    OAuthScope            = options.FromBotOAuthScope
                };
                var key = $"{options.FromBotId}-{options.BotFrameworkSkill.AppId}-{skillConversationReference.ConversationReference.Conversation.Id}-{skillConversationReference.ConversationReference.ChannelId}-skillconvo";

                _conversationRefs.GetOrAdd(key, JsonConvert.SerializeObject(skillConversationReference));
                return(Task.FromResult(key));
            }
            public override Task <string> CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions options, CancellationToken cancellationToken)
            {
                CreationOptions = options;

                var key = _conversationId;

                _conversationRefs.GetOrAdd(key, new SkillConversationReference
                {
                    ConversationReference = options.Activity.GetConversationReference(),
                    OAuthScope            = options.FromBotOAuthScope
                });
                return(Task.FromResult(key));
            }
Example #10
0
        private async Task <string> CreateSkillConversationIdAsync(ITurnContext context, Activity activity, CancellationToken cancellationToken)
        {
            // Create a conversationId to interact with the skill and send the activity
            var conversationIdFactoryOptions = new SkillConversationIdFactoryOptions
            {
                FromBotOAuthScope = context.TurnState.Get <string>(BotAdapter.OAuthScopeKey),
                FromBotId         = DialogOptions.BotId,
                Activity          = activity,
                BotFrameworkSkill = DialogOptions.Skill
            };
            var skillConversationId = await DialogOptions.ConversationIdFactory.CreateSkillConversationIdAsync(conversationIdFactoryOptions, cancellationToken).ConfigureAwait(false);

            return(skillConversationId);
        }
        private async Task <Activity> SendToSkillAsync(ITurnContext context, Activity activity, CancellationToken cancellationToken)
        {
            // Create a conversationId to interact with the skill and send the activity
            var conversationIdFactoryOptions = new SkillConversationIdFactoryOptions
            {
                FromBotOAuthScope = context.TurnState.Get <string>(BotAdapter.OAuthScopeKey),
                FromBotId         = DialogOptions.BotId,
                Activity          = activity,
                BotFrameworkSkill = DialogOptions.Skill
            };
            var skillConversationId = await DialogOptions.ConversationIdFactory.CreateSkillConversationIdAsync(conversationIdFactoryOptions, cancellationToken).ConfigureAwait(false);

            // Always save state before forwarding
            // (the dialog stack won't get updated with the skillDialog and things won't work if you don't)
            var skillInfo = DialogOptions.Skill;
            await DialogOptions.ConversationState.SaveChangesAsync(context, true, cancellationToken).ConfigureAwait(false);

            var response = await DialogOptions.SkillClient.PostActivityAsync <ExpectedReplies>(DialogOptions.BotId, skillInfo.AppId, skillInfo.SkillEndpoint, DialogOptions.SkillHostEndpoint, skillConversationId, activity, cancellationToken).ConfigureAwait(false);

            // Inspect the skill response status
            if (!(response.Status >= 200 && response.Status <= 299))
            {
                throw new HttpRequestException($"Error invoking the skill id: \"{skillInfo.Id}\" at \"{skillInfo.SkillEndpoint}\" (status is {response.Status}). \r\n {response.Body}");
            }

            Activity eocActivity = null;

            if (activity.DeliveryMode == DeliveryModes.ExpectReplies && response.Body.Activities != null && response.Body.Activities.Any())
            {
                // Process replies in the response.Body.
                foreach (var fromSkillActivity in response.Body.Activities)
                {
                    if (fromSkillActivity.Type == ActivityTypes.EndOfConversation)
                    {
                        // Capture the EndOfConversation activity if it was sent from skill
                        eocActivity = fromSkillActivity;
                    }
                    else
                    {
                        // Send the response back to the channel.
                        await context.SendActivityAsync(fromSkillActivity, cancellationToken).ConfigureAwait(false);
                    }
                }
            }

            return(eocActivity);
        }
        /// <summary>
        /// Creates a new <see cref="SkillConversationReference"/>.
        /// </summary>
        /// <param name="options">Creation options to use when creating the <see cref="SkillConversationReference"/>.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>ID of the created <see cref="SkillConversationReference"/>.</returns>
        public override async Task <string> CreateSkillConversationIdAsync(
            SkillConversationIdFactoryOptions options,
            CancellationToken cancellationToken)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            // Create the storage key based on the SkillConversationIdFactoryOptions.
            var conversationReference = options.Activity.GetConversationReference();

            string skillConversationId = string.Join(
                "-",
                options.FromBotId,
                options.BotFrameworkSkill.AppId,
                conversationReference.Conversation.Id,
                conversationReference.ChannelId,
                "skillconvo");

            // Create the SkillConversationReference instance.
            var skillConversationReference = new SkillConversationReference
            {
                ConversationReference = conversationReference,
                OAuthScope            = options.FromBotOAuthScope
            };

            // Store the SkillConversationReference using the skillConversationId as a key.
            var skillConversationInfo = new Dictionary <string, object>
            {
                {
                    skillConversationId, JObject.FromObject(skillConversationReference)
                }
            };

            await _storage.WriteAsync(skillConversationInfo, cancellationToken).ConfigureAwait(false);

            // Return the generated skillConversationId (that will be also used as the conversation ID to call the skill).
            return(skillConversationId);
        }
Example #13
0
        /// <summary>
        /// Sends an activity to the skill bot.
        /// </summary>
        /// <param name="turnContext">Context for the current turn of conversation.</param>
        /// <param name="deliveryMode">The delivery mode to use when communicating to the skill.</param>
        /// <param name="targetSkill">The skill that will receive the activity.</param>
        /// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
        /// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
        private async Task SendToSkillAsync(ITurnContext turnContext, string deliveryMode, BotFrameworkSkill targetSkill, CancellationToken cancellationToken)
        {
            // NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill
            // will have access to current accurate state.
            await _conversationState.SaveChangesAsync(turnContext, force : true, cancellationToken : cancellationToken);

            // Route the activity to the skill.
            using var client = _auth.CreateBotFrameworkClient();

            // Create a conversationId to interact with the skill and send the activity
            var options = new SkillConversationIdFactoryOptions
            {
                FromBotOAuthScope = turnContext.TurnState.Get <string>(BotAdapter.OAuthScopeKey),
                FromBotId         = _botId,
                Activity          = turnContext.Activity,
                BotFrameworkSkill = targetSkill
            };

            var skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(options, cancellationToken);

            if (deliveryMode == DeliveryModes.ExpectReplies)
            {
                // Clone activity and update its delivery mode.
                var activity = JsonConvert.DeserializeObject <Activity>(JsonConvert.SerializeObject(turnContext.Activity));
                activity.DeliveryMode = deliveryMode;

                // route the activity to the skill
                var expectRepliesResponse = await client.PostActivityAsync(_botId, targetSkill.AppId, targetSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, skillConversationId, activity, cancellationToken);

                // Check response status.
                if (!expectRepliesResponse.IsSuccessStatusCode())
                {
                    throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {expectRepliesResponse.Status}). \r\n {expectRepliesResponse.Body}");
                }

                // Route response activities back to the channel.
                var response           = expectRepliesResponse.Body as JObject;
                var activities         = response["activities"];
                var responseActivities = activities.ToObject <IList <Activity> >();

                foreach (var responseActivity in responseActivities)
                {
                    if (responseActivity.Type == ActivityTypes.EndOfConversation)
                    {
                        await EndConversation(responseActivity, turnContext, cancellationToken);
                    }
                    else
                    {
                        await turnContext.SendActivityAsync(responseActivity, cancellationToken);
                    }
                }
            }
            else
            {
                // Route the activity to the skill.
                var response = await client.PostActivityAsync(_botId, targetSkill.AppId, targetSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, skillConversationId, turnContext.Activity, cancellationToken);

                // Check response status
                if (!response.IsSuccessStatusCode())
                {
                    throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {response.Status}). \r\n {response.Body}");
                }
            }
        }