-
Notifications
You must be signed in to change notification settings - Fork 285
[Skills] add SkillHandler & SkillConversationIdFactoryBase classes #1482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
7ab6cac
add SkillHandler & SkillConversationIdFactoryBase classes
stevengum ef8f303
add OAuthPrompt tests for current implementation
stevengum d7ce9c6
fix bug in SkillHandler, add tests
stevengum 596258b
more tests
stevengum 957ffe6
cache connectorClient in SkillHandler.processActivity(), more tests
stevengum 764246b
add Skills support to OAuthPrompt, refactor skillHelpers, add tests
stevengum ddc197e
add SkillHttpClient
stevengum 28fd181
update botbuilder HttpClients, add tests
stevengum 8b5457c
add BotFrameworkSkill interface
stevengum 8e346e5
Merge branch 'master' into stgum/Skills
stevengum a2d67ec
apply PR Feedback, turn SkillConversationReferenceKey into Symbol
stevengum 9e05927
Merge branch 'stgum/Skills' of https://github.com/microsoft/botbuilde…
stevengum File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/** | ||
* @module botbuilder | ||
*/ | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
export * from './skillConversationIdFactoryBase'; | ||
export * from './skillHandler'; |
36 changes: 36 additions & 0 deletions
36
libraries/botbuilder/src/skills/skillConversationIdFactoryBase.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/** | ||
* @module botbuilder | ||
*/ | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
import { ConversationReference } from 'botbuilder-core'; | ||
|
||
/** | ||
* Defines the methods of a factory that is used to create unique conversation IDs for skill conversations. | ||
*/ | ||
export abstract class SkillConversationIdFactoryBase { | ||
/** | ||
* Creates a conversation ID for a skill conversation based on the caller's ConversationReference. | ||
* @remarks | ||
* It should be possible to use the returned string on a request URL and it should not contain special characters. | ||
* @param conversationReference The skill's caller ConversationReference. | ||
* @returns A unique conversation ID used to communicate with the skill. | ||
*/ | ||
public abstract createSkillConversationId(conversationReference: ConversationReference): Promise<string>; | ||
|
||
/** | ||
* Gets the ConversationReference created using createSkillConversationId() for a skillConversationId. | ||
* @param skillConversationId >A skill conversationId created using createSkillConversationId(). | ||
* @returns The caller's ConversationReference for a skillConversationId. null if not found. | ||
*/ | ||
public abstract getConversationReference(skillConversationId: string): Promise<ConversationReference>; | ||
|
||
/** | ||
* Deletes a ConversationReference. | ||
* @param skillConversationId A skill conversationId created using createSkillConversationId(). | ||
*/ | ||
public abstract deleteConversationReference(skillConversationId: string): Promise<void>; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
/** | ||
* @module botbuilder | ||
*/ | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
import { | ||
Activity, | ||
ActivityHandlerBase, | ||
ActivityTypes, | ||
BotAdapter, | ||
ResourceResponse, | ||
TurnContext | ||
} from 'botbuilder-core'; | ||
import { AuthenticationConfiguration, ICredentialProvider, ClaimsIdentity } from 'botframework-connector'; | ||
|
||
import { ChannelServiceHandler } from '../channelServiceHandler'; | ||
import { SkillConversationIdFactoryBase } from './skillConversationIdFactoryBase'; | ||
import { BotFrameworkAdapter } from '../botFrameworkAdapter'; | ||
|
||
/** | ||
* A Bot Framework Handler for skills. | ||
*/ | ||
export class SkillHandler extends ChannelServiceHandler { | ||
/** | ||
* Initializes a new instance of the SkillHandler class. | ||
* @param adapter An instance of the BotAdapter that will handle the request. | ||
* @param bot The ActivityHandlerBase instance. | ||
* @param conversationIdFactory A SkillConversationIdFactoryBase to unpack the conversation ID and map it to the calling bot. | ||
* @param credentialProvider The credential provider. | ||
* @param authConfig The authentication configuration. | ||
* @param channelService The string indicating if the bot is working in Public Azure or in Azure Government (https://aka.ms/AzureGovDocs). | ||
*/ | ||
constructor( | ||
private readonly adapter: BotAdapter, | ||
private readonly bot: ActivityHandlerBase, | ||
private readonly conversationIdFactory: SkillConversationIdFactoryBase, | ||
credentialProvider: ICredentialProvider, | ||
authConfig: AuthenticationConfiguration, | ||
channelService?: string | ||
) { | ||
super(credentialProvider, authConfig, channelService); | ||
if (!adapter) { | ||
throw new Error('missing adapter.'); | ||
} | ||
if (!conversationIdFactory) { | ||
throw new Error('missing conversationIdFactory.'); | ||
} | ||
} | ||
|
||
/** | ||
* sendToConversation() API for Skill. | ||
* @remarks | ||
* This method allows you to send an activity to the end of a conversation. | ||
* | ||
* This is slightly different from replyToActivity(). | ||
* * sendToConversation(conversationId) - will append the activity to the end | ||
* of the conversation according to the timestamp or semantics of the channel. | ||
* * replyToActivity(conversationId,ActivityId) - adds the activity as a reply | ||
* to another activity, if the channel supports it. If the channel does not | ||
* support nested replies, replyToActivity falls back to sendToConversation. | ||
* | ||
* Use replyToActivity when replying to a specific activity in the conversation. | ||
* | ||
* Use sendToConversation in all other cases. | ||
* @param claimsIdentity ClaimsIdentity for the bot, should have AudienceClaim, AppIdClaim and ServiceUrlClaim. | ||
* @param conversationId Conversation ID. | ||
* @param activity Activity to send. | ||
* @returns A Promise with a ResourceResponse. | ||
*/ | ||
protected async onSendToConversation(claimsIdentity: ClaimsIdentity, conversationId: string, activity: Activity): Promise<ResourceResponse> { | ||
return await this.processActivity(claimsIdentity, conversationId, null, activity); | ||
} | ||
|
||
/** | ||
* replyToActivity() API for Skill. | ||
* @remarks | ||
* This method allows you to reply to an activity. | ||
* | ||
* This is slightly different from sendToConversation(). | ||
* * sendToConversation(conversationId) - will append the activity to the end | ||
* of the conversation according to the timestamp or semantics of the channel. | ||
* * replyToActivity(conversationId,ActivityId) - adds the activity as a reply | ||
* to another activity, if the channel supports it. If the channel does not | ||
* support nested replies, replyToActivity falls back to sendToConversation. | ||
* | ||
* Use replyToActivity when replying to a specific activity in the conversation. | ||
* | ||
* Use sendToConversation in all other cases. | ||
* @param claimsIdentity ClaimsIdentity for the bot, should have AudienceClaim, AppIdClaim and ServiceUrlClaim. | ||
* @param conversationId Conversation ID. | ||
* @param activityId activityId the reply is to. | ||
* @param activity Activity to send. | ||
* @returns A Promise with a ResourceResponse. | ||
*/ | ||
protected async onreplyToActivity(claimsIdentity: ClaimsIdentity, conversationId: string, activityId: string, activity: Activity): Promise<ResourceResponse> { | ||
return await this.processActivity(claimsIdentity, conversationId, activityId, activity); | ||
} | ||
|
||
private static applyEoCToTurnContextActivity(turnContext: TurnContext,endOfConversationActivity: Activity): void { | ||
// transform the turnContext.activity to be an EndOfConversation Activity. | ||
turnContext.activity.type = endOfConversationActivity.type; | ||
turnContext.activity.text = endOfConversationActivity.text; | ||
turnContext.activity.code = endOfConversationActivity.code; | ||
|
||
turnContext.activity.replyToId = endOfConversationActivity.replyToId; | ||
turnContext.activity.value = endOfConversationActivity.value; | ||
turnContext.activity.entities = endOfConversationActivity.entities; | ||
turnContext.activity.localTimestamp = endOfConversationActivity.localTimestamp; | ||
turnContext.activity.timestamp = endOfConversationActivity.timestamp; | ||
turnContext.activity.channelData = endOfConversationActivity.channelData; | ||
} | ||
|
||
private static applyEventToTurnContextActivity(turnContext: TurnContext, eventActivity: Activity): void { | ||
// transform the turnContext.activity to be an Event Activity. | ||
turnContext.activity.type = eventActivity.type; | ||
turnContext.activity.name = eventActivity.name; | ||
turnContext.activity.value = eventActivity.value; | ||
turnContext.activity.relatesTo = eventActivity.relatesTo; | ||
|
||
turnContext.activity.replyToId = eventActivity.replyToId; | ||
turnContext.activity.value = eventActivity.value; | ||
turnContext.activity.entities = eventActivity.entities; | ||
turnContext.activity.localTimestamp = eventActivity.localTimestamp; | ||
turnContext.activity.timestamp = eventActivity.timestamp; | ||
turnContext.activity.channelData = eventActivity.channelData; | ||
} | ||
|
||
private async processActivity(claimsIdentity: ClaimsIdentity, conversationId: string, replyToActivityId: string, activity: Activity): Promise<ResourceResponse> { | ||
const conversationReference = await this.conversationIdFactory.getConversationReference(conversationId); | ||
|
||
if (!conversationReference) { | ||
throw new Error('conversationReference not found.'); | ||
} | ||
|
||
/** | ||
* Callback passed to the BotFrameworkAdapter.createConversation() call. | ||
* This function does the following: | ||
* - Caches the ClaimsIdentity on the TurnContext.turnState | ||
* - Applies the correct ConversationReference to the Activity for sending to the user-router conversation. | ||
* - For EndOfConversation Activities received from the Skill, removes the ConversationReference from the | ||
* ConversationIdFactory | ||
*/ | ||
const callback = async (context: TurnContext): Promise<void> => { | ||
// Cache the ClaimsIdentity on the context so that it's available inside of the bot's logic. | ||
context.turnState.set((context.adapter as BotFrameworkAdapter).BotIdentityKey, claimsIdentity); | ||
|
||
TurnContext.applyConversationReference(activity, conversationReference); | ||
context.activity.id = replyToActivityId; | ||
switch (activity.type) | ||
{ | ||
case ActivityTypes.EndOfConversation: | ||
await this.conversationIdFactory.deleteConversationReference(conversationId); | ||
SkillHandler.applyEoCToTurnContextActivity(context, activity); | ||
await this.bot.run(context); | ||
break; | ||
case ActivityTypes.Event: | ||
SkillHandler.applyEventToTurnContextActivity(context, activity); | ||
await this.bot.run(context); | ||
break; | ||
default: | ||
await context.sendActivity(activity); | ||
break; | ||
} | ||
}; | ||
|
||
await this.adapter.continueConversation(conversationReference, callback); | ||
return { id: uuid() }; | ||
} | ||
} | ||
|
||
// Helper function to generate an UUID. | ||
// Code is from @stevenic: https://github.com/stevenic | ||
function uuid() { | ||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { | ||
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); | ||
return v.toString(16); | ||
}); | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.