UNPKG

@microsoft/teams-ai

Version:

SDK focused on building AI based applications for Microsoft Teams.

560 lines 30.5 kB
"use strict"; /** * @module teams-ai */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MessageExtensions = exports.MessageExtensionsInvokeNames = void 0; const botbuilder_1 = require("botbuilder"); /** * Names of the invoke activities for Message Extensions. */ var MessageExtensionsInvokeNames; (function (MessageExtensionsInvokeNames) { /** * Name of the invoke activity for anonymous link unfurling. */ MessageExtensionsInvokeNames["ANONYMOUS_QUERY_LINK_INVOKE"] = "composeExtension/anonymousQueryLink"; /** * Name of the invoke activity for fetching task module. */ MessageExtensionsInvokeNames["FETCH_TASK_INVOKE"] = "composeExtension/fetchTask"; /** * Name of the invoke activity for query. */ MessageExtensionsInvokeNames["QUERY_INVOKE"] = "composeExtension/query"; /** * Name of the invoke activity for query link. */ MessageExtensionsInvokeNames["QUERY_LINK_INVOKE"] = "composeExtension/queryLink"; /** * Name of the invoke activity for selecting an item. */ MessageExtensionsInvokeNames["SELECT_ITEM_INVOKE"] = "composeExtension/selectItem"; /** * Name of the invoke activity for submit action. */ MessageExtensionsInvokeNames["SUBMIT_ACTION_INVOKE"] = "composeExtension/submitAction"; /** * Name of the invoke activity for querying configuration settings. */ MessageExtensionsInvokeNames["QUERY_SETTING_URL"] = "composeExtension/querySettingUrl"; /** * Name of the invoke activity for configuring settings. */ MessageExtensionsInvokeNames["CONFIGURE_SETTINGS"] = "composeExtension/setting"; /** * Name of the invoke activity for handling on card button clicked. */ MessageExtensionsInvokeNames["QUERY_CARD_BUTTON_CLICKED"] = "composeExtension/onCardButtonClicked"; })(MessageExtensionsInvokeNames || (exports.MessageExtensionsInvokeNames = MessageExtensionsInvokeNames = {})); /** * MessageExtensions class to enable fluent style registration of handlers related to Message Extensions. * @template TState Type of the turn state object being persisted. */ class MessageExtensions { _app; /** * Creates a new instance of the MessageExtensions class. * @param {Application} app Top level application class to register handlers with. */ constructor(app) { this._app = app; } /** * Registers a handler for a command that performs anonymous link unfurling. * @remarks * The `composeExtension/anonymousQueryLink` INVOKE activity does not contain any sort of command ID, * so only a single select item handler can be registered. * For more information visit https://learn.microsoft.com/microsoftteams/platform/messaging-extensions/how-to/link-unfurling?#enable-zero-install-link-unfurling * @param {(context: TurnContext, state: TState, url: string) => Promise<MessagingExtensionResult>} handler - Function to call when the command is received. The handler should return a `MessagingExtensionResult`. * @param {TurnContext} handler.context - Context for the current turn of conversation with the user. * @param {TState} handler.state - Current state of the turn. * @param {string} handler.url - URL to unfurl. * @returns {Application<TState>} The application for chaining purposes. */ anonymousQueryLink(handler) { const { ANONYMOUS_QUERY_LINK_INVOKE } = MessageExtensionsInvokeNames; const selector = (context) => Promise.resolve(context?.activity?.type == botbuilder_1.ActivityTypes.Invoke && context?.activity.name === ANONYMOUS_QUERY_LINK_INVOKE); this._app.addRoute(selector, async (context, state) => { // Call handler and then check to see if an invoke response has already been added const result = await handler(context, state, context.activity.value?.url ?? ''); if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { // Format invoke response const response = { composeExtension: result }; // Queue up invoke response await context.sendActivity({ value: { body: response, status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } }, true); return this._app; } /** * Registers a handler to process the 'edit' action of a message that's being previewed by the * user prior to sending. * @remarks * This handler is called when the user clicks the 'Edit' button on a message that's being * previewed prior to insertion into the current chat. The handler should return a new * view that allows the user to edit the message. * @param {string | RegExp | RouteSelector | string[] | RegExp[] | RouteSelector[]} commandId - ID of the command(s) to register the handler for. * @param {(context: TurnContext, state: TState, previewActivity: Partial<Activity>) => Promise<MessagingExtensionResult | TaskModuleTaskInfo | string | null | undefined>} handler - Function to call when the command is received. * @param {TurnContext} handler.context - Context for the current turn of conversation with the user. * @param {TState} handler.state - Current state of the turn. * @param {Partial<Activity>} handler.previewActivity - The activity that's being previewed by the user. * @returns {Application<TState>} The application for chaining purposes. */ botMessagePreviewEdit(commandId, handler) { const { SUBMIT_ACTION_INVOKE } = MessageExtensionsInvokeNames; (Array.isArray(commandId) ? commandId : [commandId]).forEach((cid) => { const selector = createTaskSelector(cid, SUBMIT_ACTION_INVOKE, 'edit'); this._app.addRoute(selector, async (context, state) => { // Insure that we're in an invoke as expected if (context?.activity?.type !== botbuilder_1.ActivityTypes.Invoke || context?.activity?.name !== SUBMIT_ACTION_INVOKE || context?.activity?.value?.botMessagePreviewAction !== 'edit') { throw new Error(`Unexpected MessageExtensions.botMessagePreviewEdit() triggered for activity type: ${context?.activity?.type}`); } // Call handler and then check to see if an invoke response has already been added const result = await handler(context, state, context.activity.value?.botActivityPreview[0] ?? {}); await this.returnSubmitActionResponse(context, result); }, true); }); return this._app; } /** * Registers a handler to process the 'send' action of a message that's being previewed by the * user prior to sending. * @remarks * This handler is called when the user clicks the 'Send' button on a message that's being * previewed prior to insertion into the current chat. The handler should complete the flow * by sending the message to the current chat. * @param {string | RegExp | RouteSelector | string[] | RegExp[] | RouteSelector[]} commandId - ID of the command(s) to register the handler for. * @param {(context: TurnContext, state: TState, previewActivity: Partial<Activity>) => Promise<void>} handler - Function to call when the command is received. * @param {TurnContext} handler.context - Context for the current turn of conversation with the user. * @param {TState} handler.state - Current state of the turn. * @param {Partial<Activity>} handler.previewActivity - The activity that's being previewed by the user. * @returns {Application<TState>} The application for chaining purposes. */ botMessagePreviewSend(commandId, handler) { const { SUBMIT_ACTION_INVOKE } = MessageExtensionsInvokeNames; (Array.isArray(commandId) ? commandId : [commandId]).forEach((cid) => { const selector = createTaskSelector(cid, SUBMIT_ACTION_INVOKE, 'send'); this._app.addRoute(selector, async (context, state) => { // Insure that we're in an invoke as expected if (context?.activity?.type !== botbuilder_1.ActivityTypes.Invoke || context?.activity?.name !== SUBMIT_ACTION_INVOKE || context?.activity?.value?.botMessagePreviewAction !== 'send') { throw new Error(`Unexpected MessageExtensions.botMessagePreviewSend() triggered for activity type: ${context?.activity?.type}`); } // Call handler and then check to see if an invoke response has already been added await handler(context, state, context.activity.value?.botActivityPreview[0] ?? {}); // Queue up invoke response if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { await context.sendActivity({ value: { body: {}, status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } }, true); }); return this._app; } /** * Registers a handler to process the initial fetch task for an Action based message extension. * @remarks * Handlers should response with either an initial TaskInfo object or a string containing * a message to display to the user. * @param {string | RegExp | RouteSelector | string[] | RegExp[] | RouteSelector[]} commandId - ID of the command(s) to register the handler for. * @param {(context: TurnContext, state: TState) => Promise<TaskModuleTaskInfo | string>} handler - Function to call when the command is received. * @param {TurnContext} handler.context - Context for the current turn of conversation with the user. * @param {TState} handler.state - Current state of the turn. * @returns {Application<TState>} The application for chaining purposes. */ fetchTask(commandId, handler) { const { FETCH_TASK_INVOKE } = MessageExtensionsInvokeNames; (Array.isArray(commandId) ? commandId : [commandId]).forEach((cid) => { const selector = createTaskSelector(cid, FETCH_TASK_INVOKE); this._app.addRoute(selector, async (context, state) => { // Insure that we're in an invoke as expected if (context?.activity?.type !== botbuilder_1.ActivityTypes.Invoke || context?.activity?.name !== FETCH_TASK_INVOKE) { throw new Error(`Unexpected MessageExtensions.fetchTask() triggered for activity type: ${context?.activity?.type}`); } // Call handler and then check to see if an invoke response has already been added const result = await handler(context, state); if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { // Format invoke response let response; if (typeof result == 'string') { // Return message response = { task: { type: 'message', value: result } }; } else { // Return card response = { task: { type: 'continue', value: result } }; } // Queue up invoke response await context.sendActivity({ value: { body: response, status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } }, true); }); return this._app; } /** * Registers a handler that implements a Search based Message Extension. * @remarks * This handler is called when the user submits a query to a Search based Message Extension. * The handler should return a MessagingExtensionResult containing the results of the query. * @param {string | RegExp | RouteSelector | string[] | RegExp[] | RouteSelector[]} commandId - ID of the command(s) to register the handler for. * @template TParams * @param {(context: TurnContext, state: TState, query: Query<TParams>) => Promise<MessagingExtensionResult>} handler - Function to call when the command is received. * @param {TurnContext} handler.context - Context for the current turn of conversation with the user. * @param {TState} handler.state - Current state of the turn. * @param {Query<TParams>} handler.query - The query parameters that were sent by the client. * @returns {Application<TState>} The application for chaining purposes. */ query(commandId, handler) { const { QUERY_INVOKE } = MessageExtensionsInvokeNames; (Array.isArray(commandId) ? commandId : [commandId]).forEach((cid) => { const selector = createTaskSelector(cid, QUERY_INVOKE); this._app.addRoute(selector, async (context, state) => { // Insure that we're in an invoke as expected if (context?.activity?.type !== botbuilder_1.ActivityTypes.Invoke || context?.activity?.name !== QUERY_INVOKE) { throw new Error(`Unexpected MessageExtensions.query() triggered for activity type: ${context?.activity?.type}`); } // Flatten query options const meQuery = context?.activity?.value ?? {}; const query = { count: meQuery?.queryOptions?.count ?? 25, skip: meQuery?.queryOptions?.skip ?? 0, parameters: {} }; // Flatten query parameters (meQuery.parameters ?? []).forEach((param) => { if (param.name) { query.parameters[param.name] = param.value; } }); // Call handler and then check to see if an invoke response has already been added const result = await handler(context, state, query); if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { // Format invoke response const response = { composeExtension: result }; // Queue up invoke response await context.sendActivity({ value: { body: response, status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } }, true); }); return this._app; } /** * Registers a handler that implements a Link Unfurling based Message Extension. * @param {(context: TurnContext, state: TState, url: string) => Promise<MessagingExtensionResult>} handler - Function to call when the command is received. * @param {TurnContext} handler.context - Context for the current turn of conversation with the user. * @param {TState} handler.state - Current state of the turn. * @param {string} handler.url - The URL that should be unfurled. * @returns {Application<TState>} The application for chaining purposes. */ queryLink(handler) { const { QUERY_LINK_INVOKE } = MessageExtensionsInvokeNames; const selector = (context) => Promise.resolve(context?.activity?.type == botbuilder_1.ActivityTypes.Invoke && context?.activity.name === QUERY_LINK_INVOKE); this._app.addRoute(selector, async (context, state) => { // Call handler and then check to see if an invoke response has already been added const result = await handler(context, state, context.activity.value?.url); if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { // Format invoke response const response = { composeExtension: result }; // Queue up invoke response await context.sendActivity({ value: { body: response, status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } }, true); return this._app; } /** * Registers a handler that implements the logic to handle the tap actions for items returned * by a Search based message extension. * @remarks * The `composeExtension/selectItem` INVOKE activity does not contain any sort of command ID, * so only a single select item handler can be registered. Developers will need to include a * type name of some sort in the preview item they return if they need to support multiple * select item handlers. * @template TItem Optional. Type of the item being selected. * @param {(context: TurnContext, state: TState, item: TItem) => Promise<MessagingExtensionResult>} handler Function to call when the command is received. * @param {TurnContext} handler.context Context for the current turn of conversation with the user. * @param {TState} handler.state Current state of the turn. * @param {TItem} handler.item The item that was selected. * @returns {Application<TState>} The application for chaining purposes. */ selectItem(handler) { const { SELECT_ITEM_INVOKE } = MessageExtensionsInvokeNames; // Define static route selector const selector = (context) => Promise.resolve(context?.activity?.type == botbuilder_1.ActivityTypes.Invoke && context?.activity.name === SELECT_ITEM_INVOKE); // Add route this._app.addRoute(selector, async (context, state) => { // Call handler and then check to see if an invoke response has already been added const result = await handler(context, state, context?.activity?.value ?? {}); if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { // Format invoke response const response = { composeExtension: result }; // Queue up invoke response await context.sendActivity({ value: { body: response, status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } }, true); return this._app; } /** * Registers a handler that implements the submit action for an Action based Message Extension. * @template TData Optional. Type of data being submitted. * @param {string | RegExp | RouteSelector | string[] | RegExp[] | RouteSelector[]} commandId ID of the command(s) to register the handler for. * @param {(context: TurnContext, state: TState, data: TData) => Promise<MessagingExtensionResult | TaskModuleTaskInfo | string | null | undefined>} handler Function to call when the command is received. * @param {TurnContext} handler.context Context for the current turn of conversation with the user. * @param {TState} handler.state Current state of the turn. * @param {TData} handler.data The data that was submitted. * @returns {Application<TState>} The application for chaining purposes. */ submitAction(commandId, handler) { const { SUBMIT_ACTION_INVOKE } = MessageExtensionsInvokeNames; (Array.isArray(commandId) ? commandId : [commandId]).forEach((cid) => { const selector = createTaskSelector(cid, SUBMIT_ACTION_INVOKE); this._app.addRoute(selector, async (context, state) => { // Insure that we're in an invoke as expected if (context?.activity?.type !== botbuilder_1.ActivityTypes.Invoke || context?.activity?.name !== SUBMIT_ACTION_INVOKE) { throw new Error(`Unexpected MessageExtensions.submitAction() triggered for activity type: ${context?.activity?.type}`); } // Call handler and then check to see if an invoke response has already been added const result = await handler(context, state, context.activity.value?.data ?? {}); await this.returnSubmitActionResponse(context, result); }, true); }); return this._app; } /** * Sends the response for a submit action. * @param {TurnContext} context The context object for the current turn of conversation with the user. * @param {MessagingExtensionResult | TaskModuleTaskInfo | string | null | undefined} result The result of the submit action. * @private */ async returnSubmitActionResponse(context, result) { if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { // Format invoke response let response; if (typeof result == 'string') { // Return message response = { task: { type: 'message', value: result } }; } else if (typeof result == 'object' && result != null) { if (result.card) { // Return another task module response = { task: { type: 'continue', value: result } }; } else { // Return card to user response = { composeExtension: result }; } } else { // No action taken response = { composeExtension: undefined }; } // Queue up invoke response await context.sendActivity({ value: { body: response, status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } } /** * Registers a handler that invokes the fetch of the configuration settings for a Message Extension. @remarks * The `composeExtension/querySettingUrl` INVOKE activity does not contain a command ID, so only a single select item handler can be registered. * @param {(context: TurnContext, state: TState) => Promise<MessagingExtensionResult>} handler Function defined by the developer to call when the command is received. * @param {TurnContext} handler.context Context for the current turn of conversation with the user. * @param {TState} handler.state Current state of the turn. * @returns {Application<TState>} The application for chaining purposes. */ queryUrlSetting(handler) { const { QUERY_SETTING_URL } = MessageExtensionsInvokeNames; // Define static route selector const selector = (context) => Promise.resolve(context?.activity?.type == botbuilder_1.ActivityTypes.Invoke && context?.activity.name === QUERY_SETTING_URL); // Add route this._app.addRoute(selector, async (context, state) => { // Call handler and then check to see if an invoke response has already been added const result = await handler(context, state); if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { const response = { composeExtension: result }; await context.sendActivity({ value: { status: 200, body: response }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } }, true); return this._app; } /** * Registers a handler that implements the logic to invoke configuring Message Extension settings * @remarks * The `composeExtension/setting` INVOKE activity does not contain a command ID, so only a single select item handler can be registered. * @template TData Message Extension settings to be configured. * @param {(context: TurnContext, state: TState, settings: TData) => Promise<void>} handler Function defined by the developer to call when the command is received. * @param {TurnContext} handler.context Context for the current turn of conversation with the user. * @param {TState} handler.state Current state of the turn. * @param {TData} handler.settings The configuration settings that was submitted. * @returns {Application<TState>} The application for chaining purposes. */ configureSettings(handler) { const { CONFIGURE_SETTINGS } = MessageExtensionsInvokeNames; // Define static route selector const selector = (context) => Promise.resolve(context?.activity?.type == botbuilder_1.ActivityTypes.Invoke && context?.activity.name === CONFIGURE_SETTINGS); // Add route this._app.addRoute(selector, async (context, state) => { // Call handler and then check to see if an invoke response has already been added await handler(context, state, context.activity.value ?? {}); if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { // Queue up 'empty' invoke response with no body, as only a 200 status code is expected await context.sendActivity({ value: { status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } }, true); return this._app; } /** * Registers a handler that implements the logic when a user has clicked on a button in a Message Extension card. * @remarks * The `composeExtension/onCardButtonClicked` INVOKE activity does not contain any sort of command ID, * so only a single select item handler can be registered. Developers will need to include a * type name of some sort in the preview item they return if they need to support multiple select item handlers. * @template TData Message Extension data passed on invoke. * @param {(context: TurnContext, state: TState, data: TData) => Promise<void>} handler Function defined by the developer to call when the command is received. * @param {TurnContext} handler.context Context for the current turn of conversation with the user. * @param {TState} handler.state Current state of the turn. * @param {TData} handler.data The data that was submitted. * @returns {Application<TState>} The application for chaining purposes. */ handleOnButtonClicked(handler) { const { QUERY_CARD_BUTTON_CLICKED } = MessageExtensionsInvokeNames; // Define static route selector const selector = (context) => Promise.resolve(context?.activity?.type == botbuilder_1.ActivityTypes.Invoke && context?.activity.name === QUERY_CARD_BUTTON_CLICKED); // Add route this._app.addRoute(selector, async (context, state) => { // Call handler and then check to see if an invoke response has already been added await handler(context, state, context.activity.value ?? {}); if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { // Queue up 'empty' invoke response with no body, as only a 200 status code is expected await context.sendActivity({ value: { status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } }, true); return this._app; } } exports.MessageExtensions = MessageExtensions; /** * @private * Creates a route selector function for a task module command. * @param {string | RegExp | RouteSelector} commandId The ID of the command to register the handler for. * @param {string} invokeName The name of the invoke activity. * @param {'edit' | 'send'} botMessagePreviewAction The bot message preview action to match. * @returns {RouteSelector} The route selector function. */ function createTaskSelector(commandId, invokeName, botMessagePreviewAction) { if (typeof commandId == 'function') { // Return the passed in selector function return commandId; } else if (commandId instanceof RegExp) { // Return a function that matches the commandId using a RegExp return (context) => { const isInvoke = context?.activity?.type == botbuilder_1.ActivityTypes.Invoke && context?.activity?.name == invokeName; if (isInvoke && typeof context?.activity?.value?.commandId == 'string' && matchesPreviewAction(context.activity, botMessagePreviewAction)) { return Promise.resolve(commandId.test(context.activity.value.commandId)); } else { return Promise.resolve(false); } }; } else { // Return a function that attempts to match commandId return (context) => { const isInvoke = context?.activity?.type == botbuilder_1.ActivityTypes.Invoke && context?.activity?.name == invokeName; return Promise.resolve(isInvoke && context?.activity?.value?.commandId === commandId && matchesPreviewAction(context.activity, botMessagePreviewAction)); }; } } /** * @private * Checks if the bot message preview action matches the specified action. * @param {Activity} activity The activity to check. * @param {'edit' | 'send'} botMessagePreviewAction The bot message preview action to match. * @returns {boolean} True if the bot message preview action matches, false otherwise. */ function matchesPreviewAction(activity, botMessagePreviewAction) { if (typeof activity?.value?.botMessagePreviewAction == 'string') { return activity.value.botMessagePreviewAction == botMessagePreviewAction; } else { return botMessagePreviewAction == undefined; } } //# sourceMappingURL=MessageExtensions.js.map