UNPKG

@microsoft/botbuilder-m365

Version:

M365 extensions for Microsoft BotBuilder, Alpha release.

151 lines (130 loc) 5.02 kB
/** * @module botbuilder-m365 */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ // TODO: /* eslint-disable security/detect-object-injection */ import { TurnContext, Storage, StoreItems } from 'botbuilder'; import { TurnState, TurnStateEntry, TurnStateManager } from './TurnState'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface DefaultConversationState {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface DefaultUserState {} export interface DefaultTempState { /** * Input passed to an AI prompt */ input: string; /** * Formatted conversation history for embedding in an AI prompt */ history: string; /** * Output returned from an AI prompt or function */ output: string; } /** * Types of Turn State held in MemoryStorage * * @module botbuilder-m365 */ export interface DefaultTurnState< TConversationState extends DefaultConversationState = DefaultConversationState, TUserState extends DefaultUserState = DefaultUserState, TTempState extends DefaultTempState = DefaultTempState > extends TurnState { conversation: TurnStateEntry<TConversationState>; user: TurnStateEntry<TUserState>; temp: TurnStateEntry<TTempState>; } export class DefaultTurnStateManager< TConversationState extends DefaultConversationState = DefaultConversationState, TUserState extends DefaultUserState = DefaultUserState, TTempState extends DefaultTempState = DefaultTempState > implements TurnStateManager<DefaultTurnState<TConversationState, TUserState, TTempState>> { public async loadState( storage: Storage, context: TurnContext ): Promise<DefaultTurnState<TConversationState, TUserState, TTempState>> { // Compute state keys const activity = context.activity; const channelId = activity?.channelId; const botId = activity?.recipient?.id; const conversationId = activity?.conversation?.id; const userId = activity?.from?.id; if (!channelId) { throw new Error('missing context.activity.channelId'); } if (!botId) { throw new Error('missing context.activity.recipient.id'); } if (!conversationId) { throw new Error('missing context.activity.conversation.id'); } if (!userId) { throw new Error('missing context.activity.from.id'); } const conversationKey = `${channelId}/${botId}/conversations/${conversationId}`; const userKey = `${channelId}/${botId}/users/${userId}`; // Read items from storage provider (if configured) const items = storage ? await storage.read([conversationKey, userKey]) : {}; // Map items to state object const state: DefaultTurnState<TConversationState, TUserState, TTempState> = { conversation: new TurnStateEntry(items[conversationKey], conversationKey), user: new TurnStateEntry(items[userKey], userKey), temp: new TurnStateEntry({} as TTempState) }; return state; } public async saveState( storage: Storage, context: TurnContext, state: DefaultTurnState<TConversationState, TUserState, TTempState> ): Promise<void> { // Find changes and deletions let changes: StoreItems | undefined; let deletions: string[] | undefined; for (const key in state) { if (Object.prototype.hasOwnProperty.call(state, key)) { const entry = state[key]; if (entry.storageKey) { if (entry.isDeleted) { // Add to deletion list if (deletions) { deletions.push(entry.storageKey); } else { deletions = [entry.storageKey]; } } else if (entry.hasChanged) { // Add to change set if (!changes) { changes = {}; } changes[entry.storageKey] = entry.value; } } } } // Do we have a storage provider? if (storage) { // Apply changes const promises: Promise<void>[] = []; if (changes) { promises.push(storage.write(changes)); } // Apply deletions if (deletions) { promises.push(storage.delete(deletions)); } // Wait for completion if (promises.length > 0) { await Promise.all(promises); } } } }