@microsoft/agents-hosting-extensions-teams
Version:
Microsoft 365 Agents SDK for JavaScript. Teams extensions
325 lines (311 loc) • 14.7 kB
text/typescript
import { Activity, ActivityTypes } from '@microsoft/agents-activity'
import { AgentApplication, RouteHandler, RouteSelector, TurnContext, TurnState } from '@microsoft/agents-hosting'
import { z } from 'zod'
import { TaskModuleResponse } from '../taskModule'
import { MessagingExtensionActionResponse } from './messagingExtensionActionResponse'
import { MessagingExtensionQuery, messagingExtensionQueryZodSchema } from './messagingExtensionQuery'
import { MessagingExtensionResponse } from './messagingExtensionResponse'
import { MessagingExtensionResult } from './messagingExtensionResult'
import { MessagingExtensionAction } from './messagingExtensionAction'
export type RouteQueryHandler<TState extends TurnState> = (context: TurnContext, state: TState, query: MessagingExtensionQuery) => Promise<MessagingExtensionResult>
export type SelectItemHandler<TState extends TurnState> = (context: TurnContext, state: TState, item: unknown) => Promise<MessagingExtensionResult>
export type QueryLinkHandler<TState extends TurnState> = (context: TurnContext, state: TState, url: string) => Promise<MessagingExtensionResult>
export type FetchTaskHanlder<TState extends TurnState> = (context: TurnContext, state: TState) => Promise<TaskModuleResponse>
export type SubmitActionHanlder<TState extends TurnState> = (context: TurnContext, state: TState, data: unknown) => Promise<MessagingExtensionActionResponse>
export type MessagePreviewEditHandler<TState extends TurnState> = (context: TurnContext, state: TState, activity: Activity) => Promise<MessagingExtensionActionResponse>
export type MessagePreviewSendHandler<TState extends TurnState> = (context: TurnContext, state: TState, activity: Activity) => Promise<void>
export type ConfigureSettingsHandler<TState extends TurnState> = (context: TurnContext, state: TState, settings: unknown) => Promise<void>
export type CardButtonClickedHandler<TState extends TurnState> = (context: TurnContext, state: TState, cardData: unknown) => Promise<void>
/**
* Class that exposes Teams messaging extension-related events.
* Provides an organized way to handle messaging extension operations in Microsoft Teams.
*/
export class MessageExtension<TState extends TurnState> {
_app: AgentApplication<TState>
/**
* Creates a new instance of the MessageExtension class.
* @param app - The agent application
*/
constructor (app: AgentApplication<TState>) {
this._app = app
}
/**
* Handles queries from messaging extensions.
* @param handler - The handler to call when a query is received
* @returns this (for method chaining)
*/
onQuery (handler: RouteQueryHandler<TState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/query'
)
}
const routeHandler : RouteHandler<TState> = async (context: TurnContext, state: TState) => {
const messageExtensionQuery: MessagingExtensionQuery = messagingExtensionQueryZodSchema.parse(context.activity.value)
const parameters: Record<string, unknown> = {}
messageExtensionQuery.parameters?.forEach((param) => {
parameters[param.name!] = param.value
})
const result : MessagingExtensionResult = await handler(context, state, messageExtensionQuery)
const response: MessagingExtensionResponse = { composeExtension: result }
const invokeResponse = new Activity(ActivityTypes.InvokeResponse)
invokeResponse.value = {
status: 200,
body: response
}
context.sendActivity(invokeResponse)
}
this._app.addRoute(routeSel, routeHandler, true) // Invoke requires true
return this
}
onSelectItem (handler: SelectItemHandler<TurnState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/selectItem'
)
}
const routeHandler : RouteHandler<TurnState> = async (context: TurnContext, state: TurnState) => {
const result : MessagingExtensionResult = await handler(context, state, context.activity.value)
const response: MessagingExtensionResponse = { composeExtension: result }
const invokeResponse = new Activity(ActivityTypes.InvokeResponse)
invokeResponse.value = {
status: 200,
body: response
}
await context.sendActivity(invokeResponse)
}
this._app.addRoute(routeSel, routeHandler, true) // Invoke requires true
return this
}
/**
* Handles link queries from messaging extensions.
* @param handler - The handler to call when a link query is received
* @returns this (for method chaining)
*/
onQueryLink (handler: QueryLinkHandler<TurnState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/queryLink'
)
}
const routeHandler : RouteHandler<TurnState> = async (context: TurnContext, state: TurnState) => {
const appBasedLinkQuerySchema = z.object({
url: z.string().url()
})
const query = appBasedLinkQuerySchema.parse(context.activity.value)
const res = await handler(context, state, query.url)
const response: MessagingExtensionResponse = { composeExtension: res }
const invokeResponse = Activity.fromObject({ type: ActivityTypes.InvokeResponse, value: { status: 200, body: response } })
await context.sendActivity(invokeResponse)
}
this._app.addRoute(routeSel, routeHandler, true)
return this
}
/**
* Handles anonymous link queries (for public access) from messaging extensions.
* @param handler - The handler to call when an anonymous link query is received
* @returns this (for method chaining)
*/
onAnonymousQueryLink (handler: QueryLinkHandler<TurnState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/anonymousQueryLink'
)
}
const routeHandler : RouteHandler<TurnState> = async (context: TurnContext, state: TurnState) => {
const appBasedLinkQuerySchema = z.object({
url: z.string().url()
})
const query = appBasedLinkQuerySchema.parse(context.activity.value)
const res = await handler(context, state, query.url)
const response: MessagingExtensionResponse = { composeExtension: res }
const invokeResponse = Activity.fromObject({ type: ActivityTypes.InvokeResponse, value: { status: 200, body: response } })
await context.sendActivity(invokeResponse)
}
this._app.addRoute(routeSel, routeHandler, true)
return this
}
/**
* Handles fetch task requests from messaging extensions.
* @param handler - The handler to call when a fetch task is requested
* @returns this (for method chaining)
*/
onFetchTask (handler: FetchTaskHanlder<TurnState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/fetchTask'
)
}
const routeHandler: RouteHandler<TurnState> = async (context: TurnContext, state: TurnState) => {
const taskModuleResponse: TaskModuleResponse = await handler(context, state)
const invokeResponse = new Activity(ActivityTypes.InvokeResponse)
invokeResponse.value = {
status: 200,
body: taskModuleResponse
}
await context.sendActivity(invokeResponse)
}
this._app.addRoute(routeSel, routeHandler, true)
return this
}
/**
* Handles action submissions from messaging extensions.
* @param handler - The handler to call when an action is submitted
* @returns this (for method chaining)
*/
onSubmitAction (handler: SubmitActionHanlder<TurnState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/submitAction' // && TODO
// (!context.activity.value || !('botMessagePreviewAction' in context.activity.value.))
)
}
const routeHandler: RouteHandler<TurnState> = async (context: TurnContext, state: TurnState) => {
const data = context.activity.value
const response: MessagingExtensionActionResponse = await handler(context, state, data)
const invokeResponse = new Activity(ActivityTypes.InvokeResponse)
invokeResponse.value = {
status: 200,
body: response
}
await context.sendActivity(invokeResponse)
}
this._app.addRoute(routeSel, routeHandler, true) // Invoke requires true
return this
}
/**
* Handles message preview edit actions from messaging extensions.
* @param handler - The handler to call when a message preview edit action is received
* @returns this (for method chaining)
*/
onMessagePreviewEdit (handler: MessagePreviewEditHandler<TurnState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(!!(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/submitAction' &&
context.activity.value &&
// @ts-ignore
context.activity.value['botMessagePreviewAction'] === 'edit'))
}
const routeHandler: RouteHandler<TurnState> = async (context: TurnContext, state: TurnState) => {
const activity = context.activity.value as Activity
const response: MessagingExtensionActionResponse = await handler(context, state, activity)
const invokeResponse = new Activity(ActivityTypes.InvokeResponse)
invokeResponse.value = {
status: 200,
body: response
}
await context.sendActivity(invokeResponse)
}
this._app.addRoute(routeSel, routeHandler, true)
return this
}
/**
* Handles message preview send actions from messaging extensions.
* @param handler - The handler to call when a message preview send action is received
* @returns this (for method chaining)
*/
onMessagePreviewSend (handler: MessagePreviewSendHandler<TurnState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(!!(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/submitAction' &&
context.activity.value &&
// @ts-ignore
context.activity.value['botMessagePreviewAction'] === 'send'))
}
const routeHandler: RouteHandler<TurnState> = async (context: TurnContext, state: TurnState) => {
const msgExtensionAction: MessagingExtensionAction = context.activity.value as MessagingExtensionAction
const activityPreview : Activity = msgExtensionAction.activityPreview?.length! > 0 ? Activity.fromObject(msgExtensionAction.activityPreview![0]) : new Activity(ActivityTypes.Message)
await handler(context, state, activityPreview)
}
this._app.addRoute(routeSel, routeHandler, true)
return this
}
/**
* Handles configuration query setting URL requests from messaging extensions.
* @param handler - The handler to call when a config query setting URL is requested
* @returns this (for method chaining)
*/
onConfigurationQuerySettingUrl (handler: ConfigureSettingsHandler<TurnState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/querySettingUrl'
)
}
const routeHandler: RouteHandler<TurnState> = async (context: TurnContext, state: TurnState) => {
await handler(context, state, context.activity.value)
const invokeResponse = new Activity(ActivityTypes.InvokeResponse)
invokeResponse.value = {
status: 200
}
await context.sendActivity(invokeResponse)
}
this._app.addRoute(routeSel, routeHandler, true)
return this
}
/**
* Handles configuration setting updates from messaging extensions.
* @param handler - The handler to call when configuration settings are updated
* @returns this (for method chaining)
*/
onConfigurationSetting (handler: ConfigureSettingsHandler<TurnState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/setting'
)
}
const routeHandler: RouteHandler<TurnState> = async (context: TurnContext, state: TurnState) => {
await handler(context, state, context.activity.value)
const invokeResponse = new Activity(ActivityTypes.InvokeResponse)
invokeResponse.value = {
status: 200
}
await context.sendActivity(invokeResponse)
}
this._app.addRoute(routeSel, routeHandler, true)
return this
}
/**
* Handles card button click events from messaging extensions.
* @param handler - The handler to call when a card button is clicked
* @returns this (for method chaining)
*/
onCardButtonClicked (handler: CardButtonClickedHandler<TurnState>) {
const routeSel: RouteSelector = (context: TurnContext) => {
return Promise.resolve(
context.activity.type === ActivityTypes.Invoke &&
context.activity.channelId === 'msteams' &&
context.activity.name === 'composeExtension/onCardButtonClicked'
)
}
const routeHandler: RouteHandler<TurnState> = async (context: TurnContext, state: TurnState) => {
await handler(context, state, context.activity.value)
const invokeResponse = new Activity(ActivityTypes.InvokeResponse)
invokeResponse.value = {
status: 200
}
await context.sendActivity(invokeResponse)
}
this._app.addRoute(routeSel, routeHandler, true)
return this
}
}