UNPKG

@microsoft/teams-ai

Version:

SDK focused on building AI based applications for Microsoft Teams.

346 lines 16.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AdaptiveCards = exports.AdaptiveCardActionExecuteResponseType = exports.ACTION_INVOKE_NAME = void 0; /** * @module teams-ai */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const botbuilder_1 = require("botbuilder"); /** * @private */ exports.ACTION_INVOKE_NAME = `adaptiveCard/action`; /** * @private */ const ACTION_EXECUTE_TYPE = `Action.Execute`; /** * @private */ const DEFAULT_ACTION_SUBMIT_FILTER = 'verb'; /** * @private */ const SEARCH_INVOKE_NAME = `application/search`; /** * @private */ var AdaptiveCardInvokeResponseType; (function (AdaptiveCardInvokeResponseType) { AdaptiveCardInvokeResponseType["ADAPTIVE"] = "application/vnd.microsoft.card.adaptive"; AdaptiveCardInvokeResponseType["MESSAGE"] = "application/vnd.microsoft.activity.message"; AdaptiveCardInvokeResponseType["SEARCH"] = "application/vnd.microsoft.search.searchResponse"; })(AdaptiveCardInvokeResponseType || (AdaptiveCardInvokeResponseType = {})); var AdaptiveCardActionExecuteResponseType; (function (AdaptiveCardActionExecuteResponseType) { /** * The response card will be replaced the current one for the interactor who trigger the action. */ AdaptiveCardActionExecuteResponseType[AdaptiveCardActionExecuteResponseType["REPLACE_FOR_INTERACTOR"] = 0] = "REPLACE_FOR_INTERACTOR"; /** * The response card will be replaced the current one for all users in the chat. */ AdaptiveCardActionExecuteResponseType[AdaptiveCardActionExecuteResponseType["REPLACE_FOR_ALL"] = 1] = "REPLACE_FOR_ALL"; /** * The response card will be sent as a new message for all users in the chat. */ AdaptiveCardActionExecuteResponseType[AdaptiveCardActionExecuteResponseType["NEW_MESSAGE_FOR_ALL"] = 2] = "NEW_MESSAGE_FOR_ALL"; })(AdaptiveCardActionExecuteResponseType || (exports.AdaptiveCardActionExecuteResponseType = AdaptiveCardActionExecuteResponseType = {})); /** * AdaptiveCards class to enable fluent style registration of handlers related to Adaptive Cards. * @template TState Type of the turn state object being persisted. */ class AdaptiveCards { _app; /** * Creates a new instance of the AdaptiveCards class. * @param {Application<TState>} app The top level application class to register handlers with. * @template TState The type of the state object used by the application. */ constructor(app) { this._app = app; } /** * Adds a route to the application for handling Adaptive Card Action.Execute events. * @template TData Optional. Type of the data associated with the action. * @param {string | RegExp | RouteSelector | (string | RegExp | RouteSelector)[]} verb The named action(s) to be handled. * @param {(context: TurnContext, state: TState, data: TData) => Promise<AdaptiveCard | string>} handler The code to execute when the action is triggered. * @param {TurnContext} handler.context The current turn context. * @param {TState} handler.state The current turn state. * @param {TData} handler.data The data associated with the action. * @returns {Application<TState>} The application for chaining purposes. */ actionExecute(verb, handler) { let actionExecuteResponseType = this._app.options.adaptiveCards?.actionExecuteResponseType ?? AdaptiveCardActionExecuteResponseType.REPLACE_FOR_INTERACTOR; (Array.isArray(verb) ? verb : [verb]).forEach((v) => { const selector = createActionExecuteSelector(v); this._app.addRoute(selector, async (context, state) => { // Insure that we're in an Action.Execute as expected const a = context?.activity; if (a?.type !== botbuilder_1.ActivityTypes.Invoke || a?.name !== exports.ACTION_INVOKE_NAME || a?.value?.action?.type !== ACTION_EXECUTE_TYPE) { throw new Error(`Unexpected AdaptiveCards.actionExecute() triggered for activity type: ${a?.type}`); } // Call handler and then check to see if an invoke response has already been added const result = await handler(context, state, a.value?.action?.data ?? {}); if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { // Format invoke response let response; if (typeof result == 'string') { // Return message response = { statusCode: 200, type: AdaptiveCardInvokeResponseType.MESSAGE, value: result }; await sendInvokeResponse(context, response); } else { // Return card if (result.refresh && actionExecuteResponseType !== AdaptiveCardActionExecuteResponseType.NEW_MESSAGE_FOR_ALL) { // Card won't be refreshed with AdaptiveCardActionExecuteResponseType.REPLACE_FOR_INTERACTOR. // So set to AdaptiveCardActionExecuteResponseType.REPLACE_FOR_ALL here. actionExecuteResponseType = AdaptiveCardActionExecuteResponseType.REPLACE_FOR_ALL; } const activity = botbuilder_1.MessageFactory.attachment(botbuilder_1.CardFactory.adaptiveCard(result)); response = { statusCode: 200, type: AdaptiveCardInvokeResponseType.ADAPTIVE, value: result }; if (actionExecuteResponseType === AdaptiveCardActionExecuteResponseType.NEW_MESSAGE_FOR_ALL) { await sendInvokeResponse(context, { statusCode: 200, type: AdaptiveCardInvokeResponseType.MESSAGE, value: 'Your response was sent to the app' }); await context.sendActivity(activity); } else if (actionExecuteResponseType === AdaptiveCardActionExecuteResponseType.REPLACE_FOR_ALL) { activity.id = context.activity.replyToId; await context.updateActivity(activity); await sendInvokeResponse(context, response); } else { await sendInvokeResponse(context, response); } } } }, true); }); return this._app; } /** * Adds a route to the application for handling Adaptive Card Action.Submit events. * @remarks * The route will be added for the specified verb(s) and will be filtered using the * `actionSubmitFilter` option. The default filter is to use the `verb` field. * * For outgoing AdaptiveCards you will need to include the verb's name in the cards Action.Submit. * For example: * * ```JSON * { * "type": "Action.Submit", * "title": "OK", * "data": { * "verb": "ok" * } * } * ``` * @template TData Optional. Type of the data associated with the action. * @param {string | RegExp | RouteSelector | string[] | RegExp[] | RouteSelector[]} verb The named action(s) to be handled. * @param {(context: TurnContext, state: TState, data: TData) => Promise<AdaptiveCard | string>} handler The code to execute when the action is triggered. * @returns {Application} The application for chaining purposes. */ actionSubmit(verb, handler) { const filter = this._app.options.adaptiveCards?.actionSubmitFilter ?? DEFAULT_ACTION_SUBMIT_FILTER; (Array.isArray(verb) ? verb : [verb]).forEach((v) => { const selector = createActionSubmitSelector(v, filter); this._app.addRoute(selector, async (context, state) => { // Insure that we're in an Action.Execute as expected const a = context?.activity; if (a?.type !== botbuilder_1.ActivityTypes.Message || a?.text || typeof a?.value !== 'object') { throw new Error(`Unexpected AdaptiveCards.actionSubmit() triggered for activity type: ${a?.type}`); } // Call handler await handler(context, state, a.value ?? {}); }); }); return this._app; } /** * Adds a route to the application for handling the `Data.Query` request for an `Input.ChoiceSet`. * @param {string | RegExp | RouteSelector | (string | RegExp | RouteSelector)[]} dataset The named dataset(s) to be handled. * @callback handler * @param {Function} handler The code to execute when the query is triggered. * @param {TurnContext} handler.context The current turn context for the handler callback. * @param {TState} handler.state The current turn state for the handler callback. * @param {Query<AdaptiveCardsSearchParams>} handler.query The query parameters for the handler callback. * @returns {this} The application for chaining purposes. */ search(dataset, handler) { (Array.isArray(dataset) ? dataset : [dataset]).forEach((ds) => { const selector = createSearchSelector(ds); this._app.addRoute(selector, async (context, state) => { // Insure that we're in an Action.Execute as expected const a = context?.activity; if (a?.type !== botbuilder_1.ActivityTypes.Invoke || a?.name !== SEARCH_INVOKE_NAME) { throw new Error(`Unexpected AdaptiveCards.search() triggered for activity type: ${a?.type}`); } // Flatten search parameters const query = { count: a?.value?.queryOptions?.top ?? 25, skip: a?.value?.queryOptions?.skip ?? 0, parameters: { queryText: a?.value?.queryText ?? '', dataset: a?.value?.dataset ?? '' } }; // Call handler and then check to see if an invoke response has already been added const results = await handler(context, state, query); if (!context.turnState.get(botbuilder_1.INVOKE_RESPONSE_KEY)) { // Format invoke response const response = { type: AdaptiveCardInvokeResponseType.SEARCH, value: { results: results } }; // Queue up invoke response await context.sendActivity({ value: { body: response, status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } }, true); }); return this._app; } } exports.AdaptiveCards = AdaptiveCards; /** * @param {string | RegExp | RouteSelector} verb The named action to be handled, or a regular expression to match the verb. * @private * @returns {RouteSelector} A function that matches the verb using a RegExp or attempts to match verb. */ function createActionExecuteSelector(verb) { if (typeof verb == 'function') { // Return the passed in selector function return verb; } else if (verb instanceof RegExp) { // Return a function that matches the verb using a RegExp return (context) => { const a = context?.activity; const isInvoke = a?.type == botbuilder_1.ActivityTypes.Invoke && a?.name === exports.ACTION_INVOKE_NAME && a?.value?.action?.type === ACTION_EXECUTE_TYPE; if (isInvoke && typeof a?.value?.action?.verb == 'string') { return Promise.resolve(verb.test(a.value.action.verb)); } else { return Promise.resolve(false); } }; } else { // Return a function that attempts to match verb return (context) => { const a = context?.activity; const isInvoke = a?.type == botbuilder_1.ActivityTypes.Invoke && a?.name === exports.ACTION_INVOKE_NAME && a?.value?.action?.type === ACTION_EXECUTE_TYPE; if (isInvoke && a?.value?.action?.verb === verb) { return Promise.resolve(true); } else { return Promise.resolve(false); } }; } } /** * @param {string | RegExp | RouteSelector} verb The named action to be handled, or a regular expression to match the verb. * @param {RouteSelector} filter Optional. A filter function to further refine the selection. * @private * @returns {RouteSelector} A function that matches the verb using a RegExp or attempts to match verb. */ function createActionSubmitSelector(verb, filter) { if (typeof verb == 'function') { // Return the passed in selector function return verb; } else if (verb instanceof RegExp) { // Return a function that matches the verb using a RegExp return (context) => { const a = context?.activity; const isSubmit = a?.type == botbuilder_1.ActivityTypes.Message && !a?.text && typeof a?.value === 'object'; if (isSubmit && typeof a?.value[filter] == 'string') { return Promise.resolve(verb.test(a.value[filter])); } else { return Promise.resolve(false); } }; } else { // Return a function that attempts to match verb return (context) => { const a = context?.activity; const isSubmit = a?.type == botbuilder_1.ActivityTypes.Message && !a?.text && typeof a?.value === 'object'; return Promise.resolve(isSubmit && a?.value[filter] === verb); }; } } /** * Creates a route selector function for handling Adaptive Card Search.Invoke events. * @param {string | RegExp | RouteSelector} dataset The dataset to match, or a regular expression to match the dataset. * @private * @returns {RouteSelector} A function that matches the dataset using a RegExp or attempts to match dataset. */ function createSearchSelector(dataset) { if (typeof dataset == 'function') { // Return the passed in selector function return dataset; } else if (dataset instanceof RegExp) { // Return a function that matches the dataset using a RegExp return (context) => { const a = context?.activity; const isSearch = a?.type == botbuilder_1.ActivityTypes.Invoke && a?.name === SEARCH_INVOKE_NAME; if (isSearch && typeof a?.value?.dataset == 'string') { return Promise.resolve(dataset.test(a.value.dataset)); } else { return Promise.resolve(false); } }; } else { // Return a function that attempts to match dataset return (context) => { const a = context?.activity; const isSearch = a?.type == botbuilder_1.ActivityTypes.Invoke && a?.name === SEARCH_INVOKE_NAME; return Promise.resolve(isSearch && a?.value?.dataset === dataset); }; } } /** * @param {TurnContext} context - The context of the current turn, providing information about the incoming activity and environment. * @param {AdaptiveCardInvokeResponse} response - The adaptive card invoke response to be sent. * @private */ async function sendInvokeResponse(context, response) { await context.sendActivity({ value: { body: response, status: 200 }, type: botbuilder_1.ActivityTypes.InvokeResponse }); } //# sourceMappingURL=AdaptiveCards.js.map