@microsoft/botbuilder-m365
Version:
M365 extensions for Microsoft BotBuilder, Alpha release.
151 lines (130 loc) • 5.02 kB
text/typescript
/**
* @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);
}
}
}
}