UNPKG

85.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4/**
5 * Copyright (c) Microsoft Corporation. All rights reserved.
6 * Licensed under the MIT License.
7 */
8const BB = require("botbuilder");
9const CLMemory_1 = require("./CLMemory");
10const CLDebug_1 = require("./CLDebug");
11const CLStrings_1 = require("./CLStrings");
12const TemplateProvider_1 = require("./TemplateProvider");
13const CLM = require("@conversationlearner/models");
14const Utils = require("./Utils");
15const ClientMemoryManager_1 = require("./Memory/ClientMemoryManager");
16const ConversationLearner_1 = require("./ConversationLearner");
17const InputQueue_1 = require("./Memory/InputQueue");
18const util = require("util");
19const BotState_1 = require("./Memory/BotState");
20const delay = util.promisify(setTimeout);
21var SessionStartFlags;
22(function (SessionStartFlags) {
23 SessionStartFlags[SessionStartFlags["NONE"] = 0] = "NONE";
24 /* Start a teaching session */
25 SessionStartFlags[SessionStartFlags["IN_TEACH"] = 1] = "IN_TEACH";
26 /* Session is an edit and continue with existing turns */
27 SessionStartFlags[SessionStartFlags["IS_EDIT_CONTINUE"] = 2] = "IS_EDIT_CONTINUE";
28})(SessionStartFlags = exports.SessionStartFlags || (exports.SessionStartFlags = {}));
29// tslint:disable-next-line:no-empty
30exports.defaultLogicCallback = () => tslib_1.__awaiter(this, void 0, void 0, function* () { });
31var ActionInputType;
32(function (ActionInputType) {
33 ActionInputType["LOGIC_ONLY"] = "LOGIC_ONLY";
34 ActionInputType["RENDER_ONLY"] = "RENDER_ONLY";
35 ActionInputType["LOGIC_AND_RENDER"] = "LOGIC_AND_RENDER";
36})(ActionInputType || (ActionInputType = {}));
37class CLRunner {
38 constructor(configModelId, maxTimeout, client) {
39 // Used to detect changes in API callbacks / Templates when bot reloaded and UI running
40 this.checksum = null;
41 /* Mapping between user defined API names and functions */
42 this.callbacks = {};
43 this.convertInternalCallbackToCallback = (c) => {
44 const { logic, render } = c, callback = tslib_1.__rest(c, ["logic", "render"]);
45 return callback;
46 };
47 this.configModelId = configModelId;
48 this.maxTimeout = maxTimeout;
49 this.clClient = client;
50 }
51 static Create(configModelId, maxTimeout, client) {
52 // Ok to not provide modelId when just running in training UI.
53 // If not, Use UI_RUNNER_APPID const as lookup value
54 let newRunner = new CLRunner(configModelId, maxTimeout, client);
55 CLRunner.Runners[configModelId || Utils.UI_RUNNER_APPID] = newRunner;
56 // Bot can define multiple CLs. Always run UI on first CL defined in the bot
57 if (!CLRunner.UIRunner) {
58 CLRunner.UIRunner = newRunner;
59 }
60 return newRunner;
61 }
62 // Get CLRunner for the UI
63 static GetRunnerForUI(appId) {
64 // Runner with the appId may not exist if running training UI, if so use the UI Runner
65 if (!appId || !CLRunner.Runners[appId]) {
66 if (CLRunner.UIRunner) {
67 return CLRunner.UIRunner;
68 }
69 else {
70 throw new Error(`Not in UI and requested CLRunner that doesn't exist: ${appId}`);
71 }
72 }
73 return CLRunner.Runners[appId];
74 }
75 botChecksum() {
76 // Create bot checksum is doesn't already exist
77 if (!this.checksum) {
78 const callbacks = Object.values(this.callbacks).map(this.convertInternalCallbackToCallback);
79 const templates = TemplateProvider_1.TemplateProvider.GetTemplates();
80 this.checksum = Utils.botChecksum(callbacks, templates);
81 }
82 return this.checksum;
83 }
84 onTurn(turnContext, next) {
85 return tslib_1.__awaiter(this, void 0, void 0, function* () {
86 const recognizerResult = yield this.recognize(turnContext, true);
87 return next(recognizerResult);
88 });
89 }
90 recognize(turnContext, force) {
91 return tslib_1.__awaiter(this, void 0, void 0, function* () {
92 // Add input to queue
93 const res = yield this.AddInput(turnContext);
94 return res;
95 });
96 }
97 InTrainingUI(turnContext) {
98 return tslib_1.__awaiter(this, void 0, void 0, function* () {
99 if (turnContext.activity.from && turnContext.activity.from.name === Utils.CL_DEVELOPER) {
100 let clMemory = yield CLMemory_1.CLMemory.InitMemory(turnContext);
101 let app = yield clMemory.BotState.GetApp();
102 // If no app selected in UI or no app set in config, or they don't match return true
103 if (!app || !this.configModelId || app.appId !== this.configModelId) {
104 return true;
105 }
106 }
107 return false;
108 });
109 }
110 // Allows Bot developer to start a new Session with initial parameters (never in Teach)
111 BotStartSession(turnContext) {
112 return tslib_1.__awaiter(this, void 0, void 0, function* () {
113 // Set adapter / conversation reference even if from field not set
114 let conversationReference = BB.TurnContext.getConversationReference(turnContext.activity);
115 this.SetAdapter(turnContext.adapter, conversationReference);
116 const activity = turnContext.activity;
117 if (activity.from === undefined || activity.id == undefined) {
118 return;
119 }
120 try {
121 let app = yield this.GetRunningApp(turnContext, false);
122 let clMemory = yield CLMemory_1.CLMemory.InitMemory(turnContext);
123 if (app) {
124 let packageId = (app.livePackageId || app.devPackageId);
125 if (packageId) {
126 const sessionCreateParams = {
127 saveToLog: app.metadata.isLoggingOn !== false,
128 packageId: packageId,
129 initialFilledEntities: []
130 };
131 yield this.StartSessionAsync(clMemory, activity.conversation.id, app.appId, SessionStartFlags.NONE, sessionCreateParams);
132 }
133 }
134 }
135 catch (error) {
136 CLDebug_1.CLDebug.Error(error);
137 }
138 });
139 }
140 SetAdapter(adapter, conversationReference) {
141 this.adapter = adapter;
142 CLDebug_1.CLDebug.InitLogger(adapter, conversationReference);
143 }
144 // Add input to queue. Allows CL to handle out-of-order messages
145 AddInput(turnContext) {
146 return tslib_1.__awaiter(this, void 0, void 0, function* () {
147 // Set adapter / conversation reference even if from field not set
148 let conversationReference = BB.TurnContext.getConversationReference(turnContext.activity);
149 this.SetAdapter(turnContext.adapter, conversationReference);
150 // ConversationUpdate messages are not processed by ConversationLearner
151 // They should be handled in the general bot code
152 if (turnContext.activity.type == "conversationUpdate") {
153 CLDebug_1.CLDebug.Verbose(`Ignoring Conversation update... +${JSON.stringify(turnContext.activity.membersAdded)} -${JSON.stringify(turnContext.activity.membersRemoved)}`);
154 return null;
155 }
156 if (turnContext.activity.from === undefined || turnContext.activity.id == undefined) {
157 return null;
158 }
159 let clMemory = yield CLMemory_1.CLMemory.InitMemory(turnContext);
160 let botState = clMemory.BotState;
161 // If I'm in teach or edit mode process message right away
162 let uiMode = yield botState.getUIMode();
163 if (uiMode !== BotState_1.UIMode.NONE) {
164 return yield this.ProcessInput(turnContext);
165 }
166 // Otherwise I have to queue up messages as user may input them faster than bot responds
167 else {
168 let addInputPromise = util.promisify(InputQueue_1.InputQueue.AddInput);
169 let isReady = yield addInputPromise(botState, turnContext.activity, conversationReference);
170 if (isReady) {
171 let intents = yield this.ProcessInput(turnContext);
172 return intents;
173 }
174 // Message has expired
175 return null;
176 }
177 });
178 }
179 StartSessionAsync(clMemory, conversationId, appId, sessionStartFlags, createParams) {
180 return tslib_1.__awaiter(this, void 0, void 0, function* () {
181 const inTeach = ((sessionStartFlags & SessionStartFlags.IN_TEACH) > 0);
182 let entityList = yield this.clClient.GetEntities(appId);
183 // If not continuing an edited session, call endSession
184 if (!(sessionStartFlags && SessionStartFlags.IS_EDIT_CONTINUE)) {
185 // Default callback will clear the bot memory.
186 // END_SESSION action was never triggered, so SessionEndState.OPEN
187 yield this.CheckSessionEndCallback(clMemory, entityList.entities, CLM.SessionEndState.OPEN);
188 }
189 // check that this works = should it be inside edit continue above
190 // Check if StartSession call is required
191 yield this.CheckSessionStartCallback(clMemory, entityList.entities);
192 let startSessionEntities = yield clMemory.BotMemory.FilledEntitiesAsync();
193 startSessionEntities = [...createParams.initialFilledEntities || [], ...startSessionEntities];
194 const filledEntityMap = CLM.FilledEntityMap.FromFilledEntities(startSessionEntities, entityList.entities);
195 yield clMemory.BotMemory.RestoreFromMapAsync(filledEntityMap);
196 // Start the new session
197 let sessionId;
198 let logDialogId;
199 let startResponse;
200 if (inTeach) {
201 const teachResponse = yield this.clClient.StartTeach(appId, createParams);
202 startResponse = CLM.ModelUtils.ToTeach(teachResponse);
203 sessionId = teachResponse.teachId;
204 logDialogId = null;
205 }
206 else {
207 startResponse = yield this.clClient.StartSession(appId, createParams);
208 sessionId = startResponse.sessionId;
209 logDialogId = startResponse.logDialogId;
210 }
211 // Initizize Bot State
212 yield clMemory.BotState.InitSessionAsync(sessionId, logDialogId, conversationId, sessionStartFlags);
213 CLDebug_1.CLDebug.Verbose(`Started Session: ${sessionId} - ${conversationId}`);
214 return startResponse;
215 });
216 }
217 // Get the currently running app
218 GetRunningApp(turnContext, inEditingUI) {
219 return tslib_1.__awaiter(this, void 0, void 0, function* () {
220 let clMemory = yield CLMemory_1.CLMemory.InitMemory(turnContext);
221 let app = yield clMemory.BotState.GetApp();
222 if (app) {
223 // If I'm not in the editing UI, always use app specified by options
224 if (!inEditingUI && this.configModelId && this.configModelId != app.appId) {
225 // Use config value
226 CLDebug_1.CLDebug.Log(`Switching to app specified in config: ${this.configModelId}`);
227 app = yield this.clClient.GetApp(this.configModelId);
228 yield clMemory.SetAppAsync(app);
229 }
230 }
231 // If I don't have an app, attempt to use one set in config
232 else if (this.configModelId) {
233 CLDebug_1.CLDebug.Log(`Selecting app specified in config: ${this.configModelId}`);
234 app = yield this.clClient.GetApp(this.configModelId);
235 yield clMemory.SetAppAsync(app);
236 }
237 return app;
238 });
239 }
240 // End a teach or log session
241 EndSessionAsync(memory, sessionEndState, data) {
242 return tslib_1.__awaiter(this, void 0, void 0, function* () {
243 let app = yield memory.BotState.GetApp();
244 if (app) {
245 let entityList = yield this.clClient.GetEntities(app.appId);
246 // Default callback will clear the bot memory
247 yield this.CheckSessionEndCallback(memory, entityList.entities, sessionEndState, data);
248 yield memory.BotState.EndSessionAsync();
249 }
250 });
251 }
252 // Process user input
253 ProcessInput(turnContext) {
254 return tslib_1.__awaiter(this, void 0, void 0, function* () {
255 let errComponent = 'ProcessInput';
256 const activity = turnContext.activity;
257 const conversationReference = BB.TurnContext.getConversationReference(activity);
258 // Validate request
259 if (!activity.from || !activity.from.id) {
260 throw new Error(`Attempted to get current session for user, but user was not defined on bot request.`);
261 }
262 try {
263 let inEditingUI = conversationReference.user &&
264 conversationReference.user.name === Utils.CL_DEVELOPER || false;
265 // Validate setup
266 if (!inEditingUI && !this.configModelId) {
267 let msg = 'Must specify modelId in ConversationLearner constructor when not running bot in Editing UI\n\n';
268 CLDebug_1.CLDebug.Error(msg);
269 return null;
270 }
271 if (!ConversationLearner_1.ConversationLearner.options || !ConversationLearner_1.ConversationLearner.options.LUIS_AUTHORING_KEY) {
272 let msg = 'Options must specify luisAuthoringKey. Set the LUIS_AUTHORING_KEY.\n\n';
273 CLDebug_1.CLDebug.Error(msg);
274 return null;
275 }
276 let app = yield this.GetRunningApp(turnContext, inEditingUI);
277 let clMemory = yield CLMemory_1.CLMemory.InitMemory(turnContext);
278 let uiMode = yield clMemory.BotState.getUIMode();
279 if (!app) {
280 let error = "ERROR: AppId not specified. When running in a channel (i.e. Skype) or the Bot Framework Emulator, CONVERSATION_LEARNER_MODEL_ID must be specified in your Bot's .env file or Application Settings on the server";
281 yield this.SendMessage(clMemory, error, activity.id);
282 return null;
283 }
284 let sessionId = yield clMemory.BotState.GetSessionIdAndSetConversationId(activity.conversation.id);
285 // When UI is active inputs are handled via API calls from the Conversation Learner UI
286 if (uiMode !== BotState_1.UIMode.NONE) {
287 return null;
288 }
289 // Check for expired session
290 if (sessionId) {
291 const currentTicks = new Date().getTime();
292 let lastActive = yield clMemory.BotState.GetLastActive();
293 let passedTicks = currentTicks - lastActive;
294 if (passedTicks > this.maxTimeout) {
295 // Parameters for new session
296 const sessionCreateParams = {
297 saveToLog: app.metadata.isLoggingOn,
298 initialFilledEntities: []
299 };
300 // If I'm running in the editing UI I need to retreive the packageId as
301 // may not be running live package
302 if (inEditingUI) {
303 const result = yield this.clClient.GetSession(app.appId, sessionId);
304 sessionCreateParams.packageId = result.packageId;
305 }
306 // End the current session
307 yield this.clClient.EndSession(app.appId, sessionId);
308 yield this.EndSessionAsync(clMemory, CLM.SessionEndState.OPEN);
309 // If I'm not in the UI, reload the App to get any changes (live package version may have been updated)
310 if (!inEditingUI) {
311 if (!this.configModelId) {
312 let error = "ERROR: ModelId not specified. When running in a channel (i.e. Skype) or the Bot Framework Emulator, CONVERSATION_LEARNER_MODEL_ID must be specified in your Bot's .env file or Application Settings on the server";
313 yield this.SendMessage(clMemory, error, activity.id);
314 return null;
315 }
316 app = yield this.clClient.GetApp(this.configModelId);
317 yield clMemory.SetAppAsync(app);
318 if (!app) {
319 let error = "ERROR: Failed to find Model specified by CONVERSATION_LEARNER_MODEL_ID";
320 yield this.SendMessage(clMemory, error, activity.id);
321 return null;
322 }
323 // Update logging state
324 sessionCreateParams.saveToLog = app.metadata.isLoggingOn;
325 }
326 let conversationId = yield clMemory.BotState.GetConversationId();
327 // Start a new session
328 let session = yield this.StartSessionAsync(clMemory, conversationId, app.appId, SessionStartFlags.NONE, sessionCreateParams);
329 sessionId = session.sessionId;
330 }
331 // Otherwise update last access time
332 else {
333 yield clMemory.BotState.SetLastActive(currentTicks);
334 }
335 }
336 // Handle any other non-message input
337 if (activity.type !== "message") {
338 yield InputQueue_1.InputQueue.MessageHandled(clMemory.BotState, activity.id);
339 return null;
340 }
341 // PackageId: Use live package id if not in editing UI, default to devPackage if no active package set
342 let packageId = (inEditingUI ? yield clMemory.BotState.GetEditingPackageForApp(app.appId) : app.livePackageId) || app.devPackageId;
343 if (!packageId) {
344 yield this.SendMessage(clMemory, "ERROR: No PackageId has been set", activity.id);
345 return null;
346 }
347 // If no session for this conversation, create a new one
348 if (!sessionId) {
349 const sessionCreateParams = {
350 saveToLog: app.metadata.isLoggingOn !== false,
351 packageId: packageId,
352 initialFilledEntities: []
353 };
354 let session = yield this.StartSessionAsync(clMemory, activity.conversation.id, app.appId, SessionStartFlags.NONE, sessionCreateParams);
355 sessionId = session.sessionId;
356 }
357 // Process any form data
358 let buttonResponse = yield this.ProcessFormData(activity, clMemory, app.appId);
359 let entities = [];
360 // Generate result
361 errComponent = 'Extract Entities';
362 let userInput = { text: buttonResponse || activity.text || ' ' };
363 let extractResponse = yield this.clClient.SessionExtract(app.appId, sessionId, userInput);
364 entities = extractResponse.definitions.entities;
365 errComponent = 'Score Actions';
366 const scoredAction = yield this.Score(app.appId, sessionId, clMemory, extractResponse.text, extractResponse.predictedEntities, entities, false);
367 return {
368 scoredAction: scoredAction,
369 clEntities: entities,
370 memory: clMemory,
371 inTeach: false,
372 activity: activity
373 };
374 }
375 catch (error) {
376 // Try to end the session, so use can potentially recover
377 try {
378 const clMemory = yield CLMemory_1.CLMemory.InitMemory(turnContext);
379 yield this.EndSessionAsync(clMemory, CLM.SessionEndState.OPEN);
380 }
381 catch (_a) {
382 CLDebug_1.CLDebug.Log(`Failed to End Session`);
383 }
384 CLDebug_1.CLDebug.Error(error, errComponent);
385 return null;
386 }
387 });
388 }
389 ProcessFormData(request, clMemory, appId) {
390 return tslib_1.__awaiter(this, void 0, void 0, function* () {
391 const data = request.value;
392 if (data) {
393 // Get list of all entities
394 let entityList = yield this.clClient.GetEntities(appId);
395 // For each form entry
396 for (let entityName of Object.keys(data)) {
397 // Reserved parameter
398 if (entityName == 'submit') {
399 continue;
400 }
401 // Find the entity
402 let entity = entityList.entities.find((e) => e.entityName == entityName);
403 // If it exists, set it
404 if (entity) {
405 yield clMemory.BotMemory.RememberEntity(entity.entityName, entity.entityId, data[entityName], entity.isMultivalue);
406 }
407 }
408 // If submit type return as a response
409 if (data['submit']) {
410 return data['submit'];
411 }
412 }
413 return null;
414 });
415 }
416 Score(appId, sessionId, memory, text, predictedEntities, allEntities, inTeach, skipEntityDetectionCallBack = false) {
417 return tslib_1.__awaiter(this, void 0, void 0, function* () {
418 // Call LUIS callback
419 let scoreInput = yield this.CallEntityDetectionCallback(text, predictedEntities, memory, allEntities, skipEntityDetectionCallBack);
420 // Call the scorer
421 let scoreResponse = null;
422 if (inTeach) {
423 scoreResponse = yield this.clClient.TeachScore(appId, sessionId, scoreInput);
424 }
425 else {
426 scoreResponse = yield this.clClient.SessionScore(appId, sessionId, scoreInput);
427 }
428 // Get best action
429 let bestAction = scoreResponse.scoredActions[0];
430 // Return the action
431 return bestAction;
432 });
433 }
434 AddCallback(callbackInput) {
435 if (typeof callbackInput.name !== "string" || callbackInput.name.trim().length === 0) {
436 throw new Error(`You attempted to add callback but did not provide a valid name. Name must be non-empty string.`);
437 }
438 if (!callbackInput.logic && !callbackInput.render) {
439 throw new Error(`You attempted to add callback by name: ${callbackInput.name} but did not provide a logic or render function. You must provide at least one of them.`);
440 }
441 const callback = {
442 name: callbackInput.name,
443 logic: exports.defaultLogicCallback,
444 logicArguments: [],
445 isLogicFunctionProvided: false,
446 render: undefined,
447 renderArguments: [],
448 isRenderFunctionProvided: false
449 };
450 if (callbackInput.logic) {
451 callback.logic = callbackInput.logic;
452 callback.logicArguments = this.GetArguments(callbackInput.logic, 1);
453 callback.isLogicFunctionProvided = true;
454 }
455 if (callbackInput.render) {
456 callback.render = callbackInput.render;
457 callback.renderArguments = this.GetArguments(callbackInput.render, 2);
458 callback.isRenderFunctionProvided = true;
459 }
460 this.callbacks[callbackInput.name] = callback;
461 }
462 GetArguments(func, skip = 0) {
463 const STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/gm;
464 const ARGUMENT_NAMES = /([^\s,]+)/g;
465 const fnStr = func.toString().replace(STRIP_COMMENTS, '');
466 const argumentNames = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES) || [];
467 return argumentNames.filter((_, i) => i >= skip);
468 }
469 ProcessPredictedEntities(text, memory, predictedEntities, allEntities) {
470 return tslib_1.__awaiter(this, void 0, void 0, function* () {
471 const predictedEntitiesWithType = predictedEntities.map(pe => {
472 let entity = allEntities.find(e => e.entityId == pe.entityId);
473 if (entity) {
474 return Object.assign({ entityType: entity.entityType }, pe);
475 }
476 else {
477 return Object.assign({ entityType: null }, pe);
478 }
479 });
480 // Update entities in my memory
481 for (let predictedEntity of predictedEntities) {
482 let entity = allEntities.find(e => e.entityId == predictedEntity.entityId);
483 if (!entity) {
484 CLDebug_1.CLDebug.Error(`Could not find entity by id: ${predictedEntity.entityId}`);
485 return;
486 }
487 // Update resolution for entities with resolver type
488 if (entity.resolverType !== undefined
489 && entity.resolverType !== null
490 && (predictedEntity.resolution === undefined || Object.keys(predictedEntity.resolution).length === 0)) {
491 const builtInEntity = predictedEntitiesWithType.find(pe => pe.startCharIndex >= predictedEntity.startCharIndex
492 && pe.endCharIndex <= predictedEntity.endCharIndex
493 && pe.entityType === entity.resolverType);
494 if (builtInEntity) {
495 predictedEntity.resolution = builtInEntity.resolution;
496 predictedEntity.builtinType = builtInEntity.builtinType;
497 }
498 }
499 // If negative entity will have a positive counter entity
500 if (entity.positiveId) {
501 yield memory.ForgetEntity(entity.entityName, predictedEntity.entityText, entity.isMultivalue);
502 }
503 else if (!entity.doNotMemorize) {
504 yield memory.RememberEntity(entity.entityName, entity.entityId, predictedEntity.entityText, entity.isMultivalue, predictedEntity.builtinType, predictedEntity.resolution);
505 }
506 }
507 });
508 }
509 CallEntityDetectionCallback(text, predictedEntities, clMemory, allEntities, skipEntityDetectionCallBack = false) {
510 return tslib_1.__awaiter(this, void 0, void 0, function* () {
511 // Entities before processing
512 let prevMemories = new CLM.FilledEntityMap(yield clMemory.BotMemory.FilledEntityMap());
513 // Update memory with predicted entities
514 yield this.ProcessPredictedEntities(text, clMemory.BotMemory, predictedEntities, allEntities);
515 // If bot has callback and callback should not be skipped, call it
516 if (this.entityDetectionCallback && !skipEntityDetectionCallBack) {
517 let memoryManager = yield this.CreateMemoryManagerAsync(clMemory, allEntities, prevMemories);
518 yield this.entityDetectionCallback(text, memoryManager);
519 // Update Memory
520 yield clMemory.BotMemory.RestoreFromMemoryManagerAsync(memoryManager);
521 }
522 // Get entities from my memory
523 const filledEntities = yield clMemory.BotMemory.FilledEntitiesAsync();
524 let scoreInput = {
525 filledEntities,
526 context: {},
527 maskedActions: []
528 };
529 return scoreInput;
530 });
531 }
532 CreateMemoryManagerAsync(clMemory, allEntities, prevMemories) {
533 return tslib_1.__awaiter(this, void 0, void 0, function* () {
534 let sessionInfo = yield clMemory.BotState.SessionInfoAsync();
535 let curMemories = new CLM.FilledEntityMap(yield clMemory.BotMemory.FilledEntityMap());
536 if (!prevMemories) {
537 prevMemories = curMemories;
538 }
539 return new ClientMemoryManager_1.ClientMemoryManager(prevMemories, curMemories, allEntities, sessionInfo);
540 });
541 }
542 CreateReadOnlyMemoryManagerAsync(clMemory, allEntities, prevMemories) {
543 return tslib_1.__awaiter(this, void 0, void 0, function* () {
544 let sessionInfo = yield clMemory.BotState.SessionInfoAsync();
545 let curMemories = new CLM.FilledEntityMap(yield clMemory.BotMemory.FilledEntityMap());
546 if (!prevMemories) {
547 prevMemories = curMemories;
548 }
549 return new ClientMemoryManager_1.ReadOnlyClientMemoryManager(prevMemories, curMemories, allEntities, sessionInfo);
550 });
551 }
552 GetTurnContext(clMemory) {
553 return tslib_1.__awaiter(this, void 0, void 0, function* () {
554 const getTurnContextForConversationReference = (conversationRef, activity) => {
555 if (!this.adapter) {
556 CLDebug_1.CLDebug.Error('Missing Adapter');
557 throw new Error('Adapter is missing!');
558 }
559 if (!activity) {
560 activity = { type: BB.ActivityTypes.Message };
561 }
562 const incomingActivity = BB.TurnContext.applyConversationReference(activity, conversationRef, true);
563 return new BB.TurnContext(this.adapter, incomingActivity);
564 };
565 // Get conversation ref, so I can generate context and send it back to bot dev
566 let conversationReference = yield clMemory.BotState.GetConversationReverence();
567 if (!conversationReference) {
568 throw new Error('Missing ConversationReference');
569 }
570 let context = clMemory.TurnContext;
571 if (context === undefined) {
572 context = getTurnContextForConversationReference(conversationReference);
573 }
574 return context;
575 });
576 }
577 // Call session start callback, set memory and return list of filled entities coming from callback
578 CheckSessionStartCallback(clMemory, entities) {
579 return tslib_1.__awaiter(this, void 0, void 0, function* () {
580 // If bot has callback, call it
581 if (this.onSessionStartCallback && this.adapter) {
582 let memoryManager = yield this.CreateMemoryManagerAsync(clMemory, entities);
583 // Get conversation ref, so I can generate context and send it back to bot dev
584 let conversationReference = yield clMemory.BotState.GetConversationReverence();
585 if (!conversationReference) {
586 CLDebug_1.CLDebug.Error('Missing ConversationReference');
587 return;
588 }
589 const context = yield this.GetTurnContext(clMemory);
590 if (this.onSessionStartCallback) {
591 try {
592 yield this.onSessionStartCallback(context, memoryManager);
593 yield clMemory.BotMemory.RestoreFromMemoryManagerAsync(memoryManager);
594 }
595 catch (err) {
596 const text = "Exception hit in Bot's OnSessionStartCallback";
597 const message = BB.MessageFactory.text(text);
598 const replayError = new CLM.ReplayErrorAPIException();
599 message.channelData = { clData: { replayError } };
600 yield this.SendMessage(clMemory, message);
601 CLDebug_1.CLDebug.Log(err);
602 }
603 }
604 }
605 });
606 }
607 CheckSessionEndCallback(clMemory, entities, sessionEndState, data) {
608 return tslib_1.__awaiter(this, void 0, void 0, function* () {
609 // If onEndSession hasn't been called yet, call it
610 let needEndSession = yield clMemory.BotState.GetNeedSessionEndCall();
611 if (needEndSession) {
612 // If bot has callback, call it to determine which entities to clear / edit
613 if (this.onSessionEndCallback && this.adapter) {
614 let memoryManager = yield this.CreateMemoryManagerAsync(clMemory, entities);
615 // Get conversation ref, so I can generate context and send it back to bot dev
616 let conversationReference = yield clMemory.BotState.GetConversationReverence();
617 if (!conversationReference) {
618 CLDebug_1.CLDebug.Error('Missing ConversationReference');
619 return;
620 }
621 const context = yield this.GetTurnContext(clMemory);
622 try {
623 let saveEntities = this.onSessionEndCallback
624 ? yield this.onSessionEndCallback(context, memoryManager, sessionEndState, data)
625 : undefined;
626 yield clMemory.BotMemory.ClearAsync(saveEntities);
627 }
628 catch (err) {
629 const text = "Exception hit in Bot's OnSessionEndCallback";
630 const message = BB.MessageFactory.text(text);
631 const replayError = new CLM.ReplayErrorAPIException();
632 message.channelData = { clData: { replayError } };
633 yield this.SendMessage(clMemory, message);
634 CLDebug_1.CLDebug.Log(err);
635 }
636 }
637 // Otherwise just clear the memory
638 else {
639 yield clMemory.BotMemory.ClearAsync();
640 }
641 yield clMemory.BotState.SetNeedSessionEndCall(false);
642 }
643 });
644 }
645 TakeActionAsync(conversationReference, clRecognizeResult, clData) {
646 return tslib_1.__awaiter(this, void 0, void 0, function* () {
647 // Get filled entities from memory
648 let filledEntityMap = yield clRecognizeResult.memory.BotMemory.FilledEntityMap();
649 filledEntityMap = Utils.addEntitiesById(filledEntityMap);
650 // If the action was terminal, free up the mutex allowing queued messages to be processed
651 // Activity won't be present if running in training as messages aren't queued
652 if (clRecognizeResult.scoredAction.isTerminal && clRecognizeResult.activity) {
653 yield InputQueue_1.InputQueue.MessageHandled(clRecognizeResult.memory.BotState, clRecognizeResult.activity.id);
654 }
655 if (!conversationReference.conversation) {
656 throw new Error(`ConversationReference contains no conversation`);
657 }
658 let actionResult;
659 let app = null;
660 let sessionId = null;
661 let replayError = null;
662 const inTeach = clData !== null;
663 switch (clRecognizeResult.scoredAction.actionType) {
664 case CLM.ActionTypes.TEXT: {
665 // This is hack to allow ScoredAction to be accepted as ActionBase
666 // TODO: Remove extra properties from ScoredAction so it only had actionId and up service to return actions definitions of scored/unscored actions
667 // so UI can link the two together instead of having "partial" actions being incorrectly treated as full actions
668 const textAction = new CLM.TextAction(clRecognizeResult.scoredAction);
669 const response = yield this.TakeTextAction(textAction, filledEntityMap);
670 actionResult = {
671 logicResult: undefined,
672 response
673 };
674 break;
675 }
676 case CLM.ActionTypes.API_LOCAL: {
677 const apiAction = new CLM.ApiAction(clRecognizeResult.scoredAction);
678 actionResult = yield this.TakeAPIAction(apiAction, filledEntityMap, clRecognizeResult.memory, clRecognizeResult.clEntities, inTeach, {
679 type: ActionInputType.LOGIC_AND_RENDER
680 });
681 if (inTeach) {
682 if (actionResult.replayError) {
683 replayError = actionResult.replayError;
684 }
685 }
686 else {
687 app = yield clRecognizeResult.memory.BotState.GetApp();
688 if (!app) {
689 throw new Error(`Attempted to get current app before app was set.`);
690 }
691 if (app.metadata.isLoggingOn !== false && actionResult && actionResult.logicResult !== undefined) {
692 if (!conversationReference.conversation) {
693 throw new Error(`Attempted to get session by conversation id, but user was not defined on current conversation`);
694 }
695 sessionId = yield clRecognizeResult.memory.BotState.GetSessionIdAndSetConversationId(conversationReference.conversation.id);
696 if (!sessionId) {
697 throw new Error(`Attempted to get session by conversation id: ${conversationReference.conversation.id} but session was not found`);
698 }
699 yield this.clClient.SessionLogicResult(app.appId, sessionId, apiAction.actionId, actionResult);
700 }
701 }
702 break;
703 }
704 case CLM.ActionTypes.CARD: {
705 const cardAction = new CLM.CardAction(clRecognizeResult.scoredAction);
706 const response = yield this.TakeCardAction(cardAction, filledEntityMap);
707 actionResult = {
708 logicResult: undefined,
709 response
710 };
711 break;
712 }
713 case CLM.ActionTypes.END_SESSION: {
714 app = yield clRecognizeResult.memory.BotState.GetApp();
715 const sessionAction = new CLM.SessionAction(clRecognizeResult.scoredAction);
716 sessionId = yield clRecognizeResult.memory.BotState.GetSessionIdAndSetConversationId(conversationReference.conversation.id);
717 const response = yield this.TakeSessionAction(sessionAction, filledEntityMap, inTeach, clRecognizeResult.memory, sessionId, app);
718 actionResult = {
719 logicResult: undefined,
720 response
721 };
722 break;
723 }
724 default:
725 throw new Error(`Could not find matching renderer for action type: ${clRecognizeResult.scoredAction.actionType}`);
726 }
727 // Convert string actions to activities
728 if (typeof actionResult.response === 'string') {
729 actionResult.response = BB.MessageFactory.text(actionResult.response);
730 }
731 if (actionResult.response && typeof actionResult.response !== 'string' && clData) {
732 actionResult.response.channelData = Object.assign({}, actionResult.response.channelData, { clData: Object.assign({}, clData, { replayError: replayError || undefined }) });
733 }
734 // If action wasn't terminal loop through Conversation Learner again after a short delay
735 if (!clRecognizeResult.inTeach && !clRecognizeResult.scoredAction.isTerminal) {
736 if (app === null) {
737 app = yield clRecognizeResult.memory.BotState.GetApp();
738 }
739 if (!app) {
740 throw new Error(`Attempted to get current app before app was set.`);
741 }
742 if (!conversationReference.conversation) {
743 throw new Error(`Attempted to get session by conversation id, but user was not defined on current conversation`);
744 }
745 if (sessionId == null) {
746 sessionId = yield clRecognizeResult.memory.BotState.GetSessionIdAndSetConversationId(conversationReference.conversation.id);
747 }
748 if (!sessionId) {
749 throw new Error(`Attempted to get session by conversation id: ${conversationReference.conversation.id} but session was not found`);
750 }
751 // send the current response to user before score for the next turn
752 if (actionResult.response != null) {
753 yield this.SendMessage(clRecognizeResult.memory, actionResult.response);
754 }
755 yield delay(100);
756 let bestAction = yield this.Score(app.appId, sessionId, clRecognizeResult.memory, '', [], clRecognizeResult.clEntities, clRecognizeResult.inTeach, true);
757 clRecognizeResult.scoredAction = bestAction;
758 actionResult = yield this.TakeActionAsync(conversationReference, clRecognizeResult, clData);
759 }
760 return actionResult;
761 });
762 }
763 SendIntent(intent, clData = null) {
764 return tslib_1.__awaiter(this, void 0, void 0, function* () {
765 let conversationReference = yield intent.memory.BotState.GetConversationReverence();
766 if (!conversationReference) {
767 CLDebug_1.CLDebug.Error('Missing ConversationReference');
768 return;
769 }
770 if (!this.adapter) {
771 CLDebug_1.CLDebug.Error('Missing Adapter');
772 return;
773 }
774 const actionResult = yield this.TakeActionAsync(conversationReference, intent, clData);
775 if (actionResult.response != null) {
776 const context = yield this.GetTurnContext(intent.memory);
777 yield context.sendActivity(actionResult.response);
778 }
779 return actionResult;
780 });
781 }
782 SendMessage(memory, message, incomingActivityId) {
783 return tslib_1.__awaiter(this, void 0, void 0, function* () {
784 // If requested, pop incoming activity from message queue
785 if (incomingActivityId) {
786 yield InputQueue_1.InputQueue.MessageHandled(memory.BotState, incomingActivityId);
787 }
788 let conversationReference = yield memory.BotState.GetConversationReverence();
789 if (!conversationReference) {
790 CLDebug_1.CLDebug.Error('Missing ConversationReference');
791 return;
792 }
793 if (!this.adapter) {
794 CLDebug_1.CLDebug.Error(`Attempted to send message before adapter was assigned`);
795 return;
796 }
797 const context = yield this.GetTurnContext(memory);
798 yield context.sendActivity(message);
799 });
800 }
801 // TODO: This issue arises because we only save non-null non-empty argument values on the actions
802 // which means callback may accept more arguments than is actually available on the action.arguments
803 // To me, it seems it would make more sense to always have these be same length, but perhaps there is
804 // dependency on action not being defined somewhere else in the application like ActionCreatorEditor
805 GetRenderedArguments(fnArgs, actionArgs, filledEntityMap) {
806 const missingEntityNames = [];
807 const renderedArgumentValues = fnArgs.map(param => {
808 const argument = actionArgs.find(arg => arg.parameter === param);
809 if (!argument) {
810 return '';
811 }
812 try {
813 return argument.renderValue(CLM.getEntityDisplayValueMap(filledEntityMap));
814 }
815 catch (error) {
816 missingEntityNames.push(param);
817 return '';
818 }
819 }, missingEntityNames);
820 if (missingEntityNames.length > 0) {
821 throw new Error(`Missing Entity value(s) for ${missingEntityNames.join(', ')}`);
822 }
823 return renderedArgumentValues;
824 }
825 TakeAPIAction(apiAction, filledEntityMap, clMemory, allEntities, inTeach, actionInput) {
826 return tslib_1.__awaiter(this, void 0, void 0, function* () {
827 // Extract API name and args
828 const callback = this.callbacks[apiAction.name];
829 if (!callback) {
830 return {
831 logicResult: undefined,
832 response: `ERROR: API callback with name "${apiAction.name}" is not defined`
833 };
834 }
835 try {
836 // Invoke Logic part of callback
837 const renderedLogicArgumentValues = this.GetRenderedArguments(callback.logicArguments, apiAction.logicArguments, filledEntityMap);
838 const memoryManager = yield this.CreateMemoryManagerAsync(clMemory, allEntities);
839 let replayError = null;
840 // If we're only doing the render part, used stored values
841 // This happens when replaying dialog to recreated action outputs
842 let logicResult = { logicValue: undefined, changedFilledEntities: [] };
843 if (actionInput.type === ActionInputType.RENDER_ONLY) {
844 let storedResult = actionInput.logicResult;
845 logicResult = storedResult || logicResult;
846 // Logic result holds delta from before after logic callback, use it to update memory
847 memoryManager.curMemories.UpdateFilledEntities(logicResult.changedFilledEntities, allEntities);
848 // Update memory with changes from logic callback
849 yield clMemory.BotMemory.RestoreFromMemoryManagerAsync(memoryManager);
850 }
851 else {
852 try {
853 // create a copy of the map before calling into logic api
854 // the copy of map is created because the passed infilledEntityMap contains "filledEntities by Id" too
855 // and this causes issues when calculating changedFilledEntities.
856 const entityMapBeforeCall = new CLM.FilledEntityMap(yield clMemory.BotMemory.FilledEntityMap());
857 // Store logic callback value
858 const logicObject = yield callback.logic(memoryManager, ...renderedLogicArgumentValues);
859 logicResult.logicValue = JSON.stringify(logicObject);
860 // Update memory with changes from logic callback
861 yield clMemory.BotMemory.RestoreFromMemoryManagerAsync(memoryManager);
862 // Store changes to filled entities
863 logicResult.changedFilledEntities = CLM.ModelUtils.changedFilledEntities(entityMapBeforeCall, memoryManager.curMemories);
864 }
865 catch (error) {
866 let botAPIError = { APIError: error.stack || error.message || error };
867 logicResult.logicValue = JSON.stringify(botAPIError);
868 replayError = new CLM.ReplayErrorAPIException();
869 }
870 }
871 // Render the action unless only doing logic part
872 if (actionInput.type === ActionInputType.LOGIC_ONLY) {
873 return {
874 logicResult,
875 response: null,
876 replayError: replayError || undefined
877 };
878 }
879 else {
880 let response = null;
881 let logicAPIError = Utils.GetLogicAPIError(logicResult);
882 // If there was an api Error show card to user
883 if (logicAPIError) {
884 const title = `Exception hit in Bot's API Callback: '${apiAction.name}'`;
885 response = this.RenderErrorCard(title, logicAPIError.APIError);
886 }
887 else if (logicResult.logicValue && !callback.render) {
888 const title = `Malformed API Callback: '${apiAction.name}'`;
889 response = this.RenderErrorCard(title, "Logic portion of callback returns a value, but no Render portion defined");
890 replayError = new CLM.ReplayErrorAPIMalformed();
891 }
892 else {
893 // Invoke Render part of callback
894 const renderedRenderArgumentValues = this.GetRenderedArguments(callback.renderArguments, apiAction.renderArguments, filledEntityMap);
895 const readOnlyMemoryManager = yield this.CreateReadOnlyMemoryManagerAsync(clMemory, allEntities);
896 let logicObject = logicResult.logicValue ? JSON.parse(logicResult.logicValue) : undefined;
897 if (callback.render) {
898 response = yield callback.render(logicObject, readOnlyMemoryManager, ...renderedRenderArgumentValues);
899 }
900 if (response && !Utils.IsCardValid(response)) {
901 const title = `Malformed API Callback '${apiAction.name}'`;
902 const error = `Return value in Render function must be a string or BotBuilder Activity`;
903 response = this.RenderErrorCard(title, error);
904 replayError = new CLM.ReplayErrorAPIBadCard();
905 }
906 // If response is empty, but we're in teach session return a placeholder card in WebChat so they can click it to edit
907 // Otherwise return the response as is.
908 if (!response && inTeach) {
909 response = this.RenderAPICard(callback, renderedLogicArgumentValues);
910 }
911 }
912 return {
913 logicResult,
914 response,
915 replayError: replayError || undefined
916 };
917 }
918 }
919 catch (err) {
920 const title = `Exception hit in Bot's API Callback: '${apiAction.name}'`;
921 const message = this.RenderErrorCard(title, err.stack || err.message || "");
922 const replayError = new CLM.ReplayErrorAPIException();
923 return {
924 logicResult: undefined,
925 response: message,
926 replayError
927 };
928 }
929 });
930 }
931 TakeTextAction(textAction, filledEntityMap) {
932 return tslib_1.__awaiter(this, void 0, void 0, function* () {
933 return Promise.resolve(textAction.renderValue(CLM.getEntityDisplayValueMap(filledEntityMap)));
934 });
935 }
936 TakeCardAction(cardAction, filledEntityMap) {
937 return tslib_1.__awaiter(this, void 0, void 0, function* () {
938 try {
939 const entityDisplayValues = CLM.getEntityDisplayValueMap(filledEntityMap);
940 const renderedArguments = cardAction.renderArguments(entityDisplayValues);
941 const missingEntities = renderedArguments.filter(ra => ra.value === null);
942 if (missingEntities.length > 0) {
943 return `ERROR: Missing Entity value(s) for ${missingEntities.map(me => me.parameter).join(', ')}`;
944 }
945 const form = yield TemplateProvider_1.TemplateProvider.RenderTemplate(cardAction.templateName, renderedArguments);
946 if (form == null) {
947 return CLDebug_1.CLDebug.Error(`Missing Template: ${cardAction.templateName}`);
948 }
949 const attachment = BB.CardFactory.adaptiveCard(form);
950 const message = BB.MessageFactory.attachment(attachment);
951 message.text = undefined;
952 return message;
953 }
954 catch (error) {
955 let msg = CLDebug_1.CLDebug.Error(error, 'Failed to Render Template');
956 return msg;
957 }
958 });
959 }
960 TakeSessionAction(sessionAction, filledEntityMap, inTeach, clMemory, sessionId, app) {
961 return tslib_1.__awaiter(this, void 0, void 0, function* () {
962 // Get any context from the action
963 let content = sessionAction.renderValue(CLM.getEntityDisplayValueMap(filledEntityMap));
964 // If inTeach, show something to user in WebChat so they can edit
965 if (inTeach) {
966 let payload = sessionAction.renderValue(CLM.getEntityDisplayValueMap(filledEntityMap));
967 let card = {
968 type: "AdaptiveCard",
969 version: "1.0",
970 body: [
971 {
972 type: "TextBlock",
973 text: `EndSession: *${payload}*`
974 }
975 ]
976 };
977 const attachment = BB.CardFactory.adaptiveCard(card);
978 const message = BB.MessageFactory.attachment(attachment);
979 return message;
980 }
981 // If I'm not in Teach end session.
982 // (In Teach EndSession is handled in ScoreFeedback to keep session alive for TeachScoreFeedback)
983 else {
984 // End the current session (if in replay will be no sessionId or app)
985 if (app && sessionId) {
986 yield this.clClient.EndSession(app.appId, sessionId);
987 yield this.EndSessionAsync(clMemory, CLM.SessionEndState.COMPLETED, content);
988 }
989 }
990 return null;
991 });
992 }
993 // Returns true if Action is available given Entities in Memory
994 isActionAvailable(action, filledEntities) {
995 for (let entityId of action.requiredEntities) {
996 let found = filledEntities.find(e => e.entityId == entityId);
997 if (!found || found.values.length === 0) {
998 return false;
999 }
1000 }
1001 for (let entityId of action.negativeEntities) {
1002 let found = filledEntities.find(e => e.entityId == entityId);
1003 if (found && found.values.length > 0) {
1004 return false;
1005 }
1006 }
1007 return true;
1008 }
1009 // Convert list of filled entities into a filled entity map lookup table
1010 CreateFilledEntityMap(filledEntities, entityList) {
1011 let filledEntityMap = new CLM.FilledEntityMap();
1012 for (let filledEntity of filledEntities) {
1013 let entity = entityList.entities.find(e => e.entityId == filledEntity.entityId);
1014 if (entity) {
1015 filledEntityMap.map[entity.entityName] = filledEntity;
1016 filledEntityMap.map[entity.entityId] = filledEntity;
1017 }
1018 }
1019 return filledEntityMap;
1020 }
1021 /**
1022 * Identify any validation issues
1023 * Missing Entities
1024 * Missing Actions
1025 * Unavailable Actions
1026 */
1027 DialogValidationErrors(trainDialog, entities, actions) {
1028 let validationErrors = [];
1029 for (let round of trainDialog.rounds) {
1030 let userText = round.extractorStep.textVariations[0].text;
1031 let filledEntities = round.scorerSteps[0] && round.scorerSteps[0].input ? round.scorerSteps[0].input.filledEntities : [];
1032 // Check that entities exist
1033 for (let filledEntity of filledEntities) {
1034 if (!entities.find(e => e.entityId == filledEntity.entityId)) {
1035 validationErrors.push(`Missing Entity for "${CLM.filledEntityValueAsString(filledEntity)}"`);
1036 }
1037 }
1038 for (let scorerStep of round.scorerSteps) {
1039 let labelAction = scorerStep.labelAction;
1040 // Check that action exists
1041 let selectedAction = actions.find(a => a.actionId == labelAction);
1042 if (!selectedAction) {
1043 validationErrors.push(`Missing Action response for "${userText}"`);
1044 }
1045 else {
1046 // Check action availability
1047 if (!this.isActionAvailable(selectedAction, scorerStep.input.filledEntities)) {
1048 validationErrors.push(`Selected Action in unavailable in response to "${userText}"`);
1049 }
1050 }
1051 }
1052 }
1053 // Make errors unique using Set operator
1054 validationErrors = [...new Set(validationErrors)];
1055 return validationErrors;
1056 }
1057 /** Return a list of trainDialogs that are invalid for the given set of entities and actions */
1058 validateTrainDialogs(appDefinition) {
1059 let invalidTrainDialogIds = [];
1060 for (let trainDialog of appDefinition.trainDialogs) {
1061 // Ignore train dialogs that are already invalid
1062 if (trainDialog.validity !== CLM.Validity.INVALID) {
1063 let validationErrors = this.DialogValidationErrors(trainDialog, appDefinition.entities, appDefinition.actions);
1064 if (validationErrors.length > 0) {
1065 invalidTrainDialogIds.push(trainDialog.trainDialogId);
1066 }
1067 }
1068 }
1069 return invalidTrainDialogIds;
1070 }
1071 /** Populate prebuilt information in predicted entities given filled entity array */
1072 PopulatePrebuilts(predictedEntities, filledEntities) {
1073 for (let pe of predictedEntities) {
1074 let filledEnt = filledEntities.find(fe => fe.entityId === pe.entityId);
1075 if (filledEnt) {
1076 let value = filledEnt.values.find(v => v.userText === pe.entityText);
1077 if (value) {
1078 pe.resolution = value.resolution;
1079 if (value.builtinType) {
1080 pe.builtinType = value.builtinType;
1081 }
1082 }
1083 }
1084 }
1085 }
1086 /**
1087 * Provide empty FilledEntity for any missing entities so they can still be rendered
1088 */
1089 PopulateMissingFilledEntities(action, filledEntityMap, allEntities, bidirectional) {
1090 // For backwards compatibiliity need to check requieredEntities too. In new version all in requiredEntitiesFromPayload
1091 const allRequiredEntities = [...action.requiredEntities, ...action.requiredEntitiesFromPayload];
1092 let missingEntities = [];
1093 allRequiredEntities.forEach((entityId) => {
1094 let entity = allEntities.find(e => e.entityId === entityId);
1095 if (entity) {
1096 if (!filledEntityMap.map[entity.entityName]) {
1097 // Add an empty filledEntity if requried and has no values
1098 let filledEntity = {
1099 entityId: entityId,
1100 values: []
1101 };
1102 filledEntityMap.map[entity.entityId] = filledEntity;
1103 if (bidirectional) {
1104 filledEntityMap.map[entity.entityName] = filledEntity;
1105 }
1106 missingEntities.push(entity.entityName);
1107 }
1108 else {
1109 const filledEntity = filledEntityMap.map[entity.entityName];
1110 if (filledEntity && filledEntity.values.length == 0) {
1111 missingEntities.push(entity.entityName);
1112 }
1113 }
1114 }
1115 else {
1116 throw new Error(`ENTITY ${entityId} DOES NOT EXIST`);
1117 }
1118 });
1119 return missingEntities;
1120 }
1121 /**
1122 * Initialize memory for replay
1123 */
1124 InitReplayMemory(clMemory, trainDialog, allEntities) {
1125 return tslib_1.__awaiter(this, void 0, void 0, function* () {
1126 // Reset the memory
1127 yield clMemory.BotMemory.ClearAsync();
1128 // Call start sesssion for initial entities
1129 yield this.CheckSessionStartCallback(clMemory, allEntities);
1130 let startSessionEntities = yield clMemory.BotMemory.FilledEntitiesAsync();
1131 startSessionEntities = [...trainDialog.initialFilledEntities || [], ...startSessionEntities];
1132 let map = CLM.FilledEntityMap.FromFilledEntities(startSessionEntities, allEntities);
1133 yield clMemory.BotMemory.RestoreFromMapAsync(map);
1134 });
1135 }
1136 /**
1137 * Replay a TrainDialog, calling EntityDetection callback and API Logic,
1138 * recalculating FilledEntities along the way
1139 */
1140 ReplayTrainDialogLogic(trainDialog, clMemory, cleanse) {
1141 return tslib_1.__awaiter(this, void 0, void 0, function* () {
1142 if (!trainDialog || !trainDialog.rounds) {
1143 return trainDialog;
1144 }
1145 // Copy train dialog
1146 let newTrainDialog = JSON.parse(JSON.stringify(trainDialog));
1147 let entities = trainDialog.definitions ? trainDialog.definitions.entities : [];
1148 let actions = trainDialog.definitions ? trainDialog.definitions.actions : [];
1149 let entityList = { entities };
1150 yield this.InitReplayMemory(clMemory, newTrainDialog, entities);
1151 for (let round of newTrainDialog.rounds) {
1152 // Call entity detection callback with first text Variation
1153 let textVariation = round.extractorStep.textVariations[0];
1154 let predictedEntities = CLM.ModelUtils.ToPredictedEntities(textVariation.labelEntities);
1155 // Call EntityDetectionCallback and populate filledEntities with the result
1156 let scoreInput;
1157 let botAPIError = null;
1158 try {
1159 scoreInput = yield this.CallEntityDetectionCallback(textVariation.text, predictedEntities, clMemory, entities);
1160 }
1161 catch (err) {
1162 // Hit exception in Bot's Entity Detection Callback
1163 // Use existing memory before callback
1164 const filledEntities = yield clMemory.BotMemory.FilledEntitiesAsync();
1165 scoreInput = {
1166 filledEntities,
1167 context: {},
1168 maskedActions: []
1169 };
1170 // Create error to show to user
1171 let errMessage = `${CLStrings_1.CLStrings.ENTITYCALLBACK_EXCEPTION} ${err.message}`;
1172 botAPIError = { APIError: errMessage };
1173 }
1174 // Use scorer step to populate pre-built data (when)
1175 if (round.scorerSteps && round.scorerSteps.length > 0) {
1176 this.PopulatePrebuilts(predictedEntities, scoreInput.filledEntities);
1177 round.scorerSteps[0].input.filledEntities = scoreInput.filledEntities;
1178 // Go through each scorer step
1179 for (let [scoreIndex, scorerStep] of round.scorerSteps.entries()) {
1180 let curAction = actions.filter((a) => a.actionId === scorerStep.labelAction)[0];
1181 if (curAction) {
1182 let filledEntityMap = yield clMemory.BotMemory.FilledEntityMap();
1183 // Provide empty FilledEntity for missing entities
1184 if (!cleanse) {
1185 this.PopulateMissingFilledEntities(curAction, filledEntityMap, entities, false);
1186 }
1187 round.scorerSteps[scoreIndex].input.filledEntities = filledEntityMap.FilledEntities();
1188 // Run logic part of APIAction to update the FilledEntities
1189 if (curAction.actionType === CLM.ActionTypes.API_LOCAL) {
1190 const apiAction = new CLM.ApiAction(curAction);
1191 const actionInput = {
1192 type: ActionInputType.LOGIC_ONLY
1193 };
1194 // Calculate and store new logic result
1195 const filledIdMap = filledEntityMap.EntityMapToIdMap();
1196 let actionResult = yield this.TakeAPIAction(apiAction, filledIdMap, clMemory, entityList.entities, true, actionInput);
1197 round.scorerSteps[scoreIndex].logicResult = actionResult.logicResult;
1198 }
1199 else if (curAction.actionType === CLM.ActionTypes.END_SESSION) {
1200 const sessionAction = new CLM.SessionAction(curAction);
1201 yield this.TakeSessionAction(sessionAction, filledEntityMap, true, clMemory, null, null);
1202 }
1203 }
1204 // If ran into API error inject into first scorer step so it gets displayed to the user
1205 if (botAPIError && scoreIndex === 0) {
1206 round.scorerSteps[scoreIndex].logicResult = { logicValue: JSON.stringify(botAPIError), changedFilledEntities: [] };
1207 }
1208 }
1209 }
1210 else {
1211 // Otherwise create a dummy scorer step with the filled entities
1212 const scorerStep = {
1213 input: {
1214 filledEntities: yield clMemory.BotMemory.FilledEntitiesAsync(),
1215 context: {},
1216 maskedActions: []
1217 },
1218 labelAction: undefined,
1219 logicResult: undefined,
1220 scoredAction: undefined
1221 };
1222 if (!round.scorerSteps) {
1223 round.scorerSteps = [];
1224 }
1225 round.scorerSteps.push(scorerStep);
1226 }
1227 }
1228 // When editing, may need to run Scorer or Extrator on TrainDialog with invalid rounds
1229 //This cleans up the TrainDialog removing bad data so the extractor can run
1230 if (cleanse) {
1231 // Remove rounds with two user inputs in a row (they'll have a dummy scorer round)
1232 newTrainDialog.rounds = newTrainDialog.rounds.filter(r => {
1233 return !r.scorerSteps[0] || r.scorerSteps[0].labelAction != undefined;
1234 });
1235 }
1236 return newTrainDialog;
1237 });
1238 }
1239 /**
1240 * Get Activities generated by trainDialog.
1241 * Return any errors in TrainDialog
1242 * NOTE: Will set bot memory to state at end of history
1243 */
1244 GetHistory(trainDialog, userName, userId, clMemory) {
1245 return tslib_1.__awaiter(this, void 0, void 0, function* () {
1246 let entities = trainDialog.definitions ? trainDialog.definitions.entities : [];
1247 let actions = trainDialog.definitions ? trainDialog.definitions.actions : [];
1248 let entityList = { entities };
1249 let prevMemories = [];
1250 if (!trainDialog || !trainDialog.rounds) {
1251 return null;
1252 }
1253 yield this.InitReplayMemory(clMemory, trainDialog, entities);
1254 let excludedEntities = entities.filter(e => e.doNotMemorize).map(e => e.entityId);
1255 let activities = [];
1256 let replayError = null;
1257 let replayErrors = [];
1258 let curAction = null;
1259 for (let [roundNum, round] of trainDialog.rounds.entries()) {
1260 let filledEntities = round.scorerSteps[0] && round.scorerSteps[0].input ? round.scorerSteps[0].input.filledEntities : [];
1261 // VALIDATION
1262 replayError = null;
1263 // Check that non-multivalue isn't labelled twice
1264 for (let tv of round.extractorStep.textVariations) {
1265 let usedEntities = [];
1266 for (let labelEntity of tv.labelEntities) {
1267 // If already used, make sure it's multi-value
1268 if (usedEntities.find(e => e === labelEntity.entityId)) {
1269 let entity = entities.find(e => e.entityId == labelEntity.entityId);
1270 if (entity && !entity.isMultivalue
1271 && (entity.entityType === CLM.EntityType.LUIS || entity.entityType === CLM.EntityType.LOCAL)) {
1272 replayError = replayError || new CLM.EntityUnexpectedMultivalue(entity.entityName);
1273 replayErrors.push(replayError);
1274 }
1275 }
1276 // Otherwise add to list of used entities
1277 else {
1278 usedEntities.push(labelEntity.entityId);
1279 }
1280 }
1281 }
1282 // Check that entities exist in text variations
1283 for (let tv of round.extractorStep.textVariations) {
1284 for (let labelEntity of tv.labelEntities) {
1285 if (!entities.find(e => e.entityId == labelEntity.entityId)) {
1286 replayError = new CLM.ReplayErrorEntityUndefined(labelEntity.entityId);
1287 replayErrors.push();
1288 }
1289 }
1290 }
1291 // Check that entities exist in filled entities
1292 for (let filledEntity of filledEntities) {
1293 if (!entities.find(e => e.entityId == filledEntity.entityId)) {
1294 replayError = new CLM.ReplayErrorEntityUndefined(CLM.filledEntityValueAsString(filledEntity));
1295 replayErrors.push();
1296 }
1297 }
1298 // Check for double user inputs
1299 if (roundNum != trainDialog.rounds.length - 1 &&
1300 (round.scorerSteps.length === 0 || !round.scorerSteps[0].labelAction)) {
1301 replayError = new CLM.ReplayErrorTwoUserInputs();
1302 replayErrors.push(replayError);
1303 }
1304 // Check for user input when previous action wasn't wait
1305 if (curAction && !curAction.isTerminal) {
1306 replayError = new CLM.ReplayErrorInputAfterNonWait();
1307 replayErrors.push(replayError);
1308 }
1309 // Generate activity. Add markdown to highlight labelled entities
1310 let userText = CLM.ModelUtils.textVariationToMarkdown(round.extractorStep.textVariations[0], excludedEntities);
1311 let userActivity = CLM.ModelUtils.InputToActivity(userText, userName, userId, roundNum);
1312 userActivity.channelData.clData.replayError = replayError;
1313 userActivity.channelData.clData.activityIndex = activities.length;
1314 userActivity.textFormat = 'markdown';
1315 activities.push(userActivity);
1316 // Save memory before this step (used to show changes in UI)
1317 prevMemories = yield clMemory.BotMemory.DumpMemory();
1318 let textVariation = round.extractorStep.textVariations[0];
1319 let predictedEntities = CLM.ModelUtils.ToPredictedEntities(textVariation.labelEntities);
1320 // Use scorer step to populate pre-built data (when)
1321 if (round.scorerSteps.length > 0) {
1322 this.PopulatePrebuilts(predictedEntities, round.scorerSteps[0].input.filledEntities);
1323 }
1324 for (let [scoreIndex, scorerStep] of round.scorerSteps.entries()) {
1325 let labelAction = scorerStep.labelAction;
1326 // Scorer rounds w/o labelActions may exist to store extraction result for rendering
1327 if (labelAction) {
1328 let scoreFilledEntities = scorerStep.input.filledEntities;
1329 // VALIDATION
1330 replayError = null;
1331 // Check that action exists
1332 let selectedAction = actions.find(a => a.actionId == labelAction);
1333 if (!selectedAction) {
1334 replayError = new CLM.ReplayErrorActionUndefined(userText);
1335 replayErrors.push(replayError);
1336 }
1337 else {
1338 // Check action availability
1339 if (!this.isActionAvailable(selectedAction, scoreFilledEntities)) {
1340 replayError = new CLM.ReplayErrorActionUnavailable(userText);
1341 replayErrors.push(replayError);
1342 }
1343 }
1344 // Check that action (if not first) is after a wait action
1345 if (scoreIndex > 0) {
1346 const lastScoredAction = round.scorerSteps[scoreIndex - 1].labelAction;
1347 let lastAction = actions.find(a => a.actionId == lastScoredAction);
1348 if (lastAction && lastAction.isTerminal) {
1349 replayError = new CLM.ReplayErrorActionAfterWait();
1350 replayErrors.push(replayError);
1351 }
1352 }
1353 // Generate bot response
1354 curAction = actions.filter((a) => a.actionId === labelAction)[0];
1355 let botResponse;
1356 // Check for exceptions on API call (specificaly EntityDetectionCallback)
1357 let logicAPIError = Utils.GetLogicAPIError(scorerStep.logicResult);
1358 if (logicAPIError) {
1359 replayError = new CLM.ReplayErrorAPIException();
1360 replayErrors.push(replayError);
1361 let actionName = "";
1362 if (curAction.actionType === CLM.ActionTypes.API_LOCAL) {
1363 const apiAction = new CLM.ApiAction(curAction);
1364 actionName = `${apiAction.name}`;
1365 }
1366 const title = `Exception hit in Bot's API Callback:${actionName}`;
1367 const response = this.RenderErrorCard(title, logicAPIError.APIError);
1368 botResponse = {
1369 logicResult: undefined,
1370 response
1371 };
1372 }
1373 else if (!curAction) {
1374 botResponse = {
1375 logicResult: undefined,
1376 response: CLDebug_1.CLDebug.Error(`Can't find Action Id ${labelAction}`)
1377 };
1378 }
1379 else {
1380 // Create map with names and ids
1381 let filledEntityMap = this.CreateFilledEntityMap(scoreFilledEntities, entityList);
1382 // Fill in missing entities with a warning
1383 const missingEntities = this.PopulateMissingFilledEntities(curAction, filledEntityMap, entities, true);
1384 // Entity required for Action isn't filled in
1385 if (missingEntities.length > 0) {
1386 replayError = replayError || new CLM.ReplayErrorEntityEmpty(missingEntities);
1387 replayErrors.push(replayError);
1388 }
1389 // Set memory from map with names only (since not calling APIs)
1390 let memoryMap = CLM.FilledEntityMap.FromFilledEntities(scoreFilledEntities, entities);
1391 yield clMemory.BotMemory.RestoreFromMapAsync(memoryMap);
1392 if (curAction.actionType === CLM.ActionTypes.CARD) {
1393 const cardAction = new CLM.CardAction(curAction);
1394 botResponse = {
1395 logicResult: undefined,
1396 response: yield this.TakeCardAction(cardAction, filledEntityMap)
1397 };
1398 }
1399 else if (curAction.actionType === CLM.ActionTypes.API_LOCAL) {
1400 const apiAction = new CLM.ApiAction(curAction);
1401 const actionInput = {
1402 type: ActionInputType.RENDER_ONLY,
1403 logicResult: scorerStep.logicResult
1404 };
1405 botResponse = yield this.TakeAPIAction(apiAction, filledEntityMap, clMemory, entityList.entities, true, actionInput);
1406 if (!this.callbacks[apiAction.name]) {
1407 replayError = new CLM.ReplayErrorAPIUndefined(apiAction.name);
1408 replayErrors.push(replayError);
1409 }
1410 else if (botResponse.replayError) {
1411 replayError = botResponse.replayError;
1412 replayErrors.push(botResponse.replayError);
1413 }
1414 }
1415 else if (curAction.actionType === CLM.ActionTypes.TEXT) {
1416 const textAction = new CLM.TextAction(curAction);
1417 try {
1418 botResponse = {
1419 logicResult: undefined,
1420 response: yield this.TakeTextAction(textAction, filledEntityMap)
1421 };
1422 }
1423 catch (error) {
1424 // Payload is invalid
1425 replayError = new CLM.ReplayErrorEntityUndefined("");
1426 replayErrors.push(replayError);
1427 botResponse = {
1428 logicResult: undefined,
1429 response: JSON.parse(textAction.payload).text // Show raw text
1430 };
1431 }
1432 }
1433 else if (curAction.actionType === CLM.ActionTypes.END_SESSION) {
1434 const sessionAction = new CLM.SessionAction(curAction);
1435 botResponse = {
1436 logicResult: undefined,
1437 response: yield this.TakeSessionAction(sessionAction, filledEntityMap, true, clMemory, null, null)
1438 };
1439 }
1440 else {
1441 throw new Error(`Cannot construct bot response for unknown action type: ${curAction.actionType}`);
1442 }
1443 }
1444 let validWaitAction;
1445 if (curAction && !curAction.isTerminal) {
1446 if (round.scorerSteps.length === scoreIndex + 1) {
1447 validWaitAction = false;
1448 }
1449 else {
1450 validWaitAction = true;
1451 }
1452 }
1453 let clBotData = {
1454 senderType: CLM.SenderType.Bot,
1455 roundIndex: roundNum,
1456 scoreIndex,
1457 validWaitAction: validWaitAction,
1458 replayError,
1459 activityIndex: activities.length
1460 };
1461 let botActivity = null;
1462 let botAccount = { id: `BOT-${userId}`, name: CLM.CL_USER_NAME_ID, role: BB.RoleTypes.Bot, aadObjectId: '' };
1463 if (typeof botResponse.response == 'string') {
1464 botActivity = {
1465 id: CLM.ModelUtils.generateGUID(),
1466 from: botAccount,
1467 type: 'message',
1468 text: botResponse.response,
1469 channelData: { clData: clBotData }
1470 };
1471 }
1472 else if (botResponse) {
1473 botActivity = botResponse.response;
1474 botActivity.id = CLM.ModelUtils.generateGUID();
1475 botActivity.from = botAccount;
1476 botActivity.channelData = { clData: clBotData };
1477 }
1478 if (botActivity) {
1479 activities.push(botActivity);
1480 }
1481 }
1482 }
1483 }
1484 let memories = yield clMemory.BotMemory.DumpMemory();
1485 let hasRounds = trainDialog.rounds.length > 0;
1486 let hasScorerRound = (hasRounds && trainDialog.rounds[trainDialog.rounds.length - 1].scorerSteps.length > 0);
1487 let dialogMode = CLM.DialogMode.Scorer;
1488 // If I have no rounds, I'm waiting for input
1489 if (!hasRounds) {
1490 dialogMode = CLM.DialogMode.Wait;
1491 }
1492 else if (curAction) {
1493 // If last action is session end
1494 if (curAction.actionType === CLM.ActionTypes.END_SESSION) {
1495 dialogMode = CLM.DialogMode.EndSession;
1496 }
1497 // If I have a scorer round, wait
1498 else if (curAction.isTerminal && hasScorerRound) {
1499 dialogMode = CLM.DialogMode.Wait;
1500 }
1501 }
1502 // Calculate last extract response from text variations
1503 let uiScoreInput;
1504 if (hasRounds) {
1505 // Note: Could potentially just send back extractorStep and calculate extractResponse on other end
1506 let textVariations = trainDialog.rounds[trainDialog.rounds.length - 1].extractorStep.textVariations;
1507 let extractResponses = CLM.ModelUtils.ToExtractResponses(textVariations);
1508 let trainExtractorStep = trainDialog.rounds[trainDialog.rounds.length - 1].extractorStep;
1509 uiScoreInput = {
1510 trainExtractorStep: trainExtractorStep,
1511 extractResponse: extractResponses[0]
1512 };
1513 }
1514 // Make errors unique using Set operator
1515 replayErrors = [...new Set(replayErrors)];
1516 let teachWithHistory = {
1517 teach: undefined,
1518 scoreInput: undefined,
1519 scoreResponse: undefined,
1520 uiScoreInput: uiScoreInput,
1521 extractResponse: undefined,
1522 lastAction: curAction,
1523 history: activities,
1524 memories: memories,
1525 prevMemories: prevMemories,
1526 dialogMode: dialogMode,
1527 replayErrors: replayErrors
1528 };
1529 return teachWithHistory;
1530 });
1531 }
1532 // Generate a card to show for an API action w/o output
1533 RenderAPICard(callback, args) {
1534 let card = {
1535 type: "AdaptiveCard",
1536 version: "1.0",
1537 body: [
1538 {
1539 type: "Container",
1540 items: [
1541 {
1542 type: "TextBlock",
1543 text: `${callback.name}(${args.join(',')})`,
1544 wrap: true
1545 }
1546 ]
1547 }
1548 ]
1549 };
1550 const attachment = BB.CardFactory.adaptiveCard(card);
1551 const message = BB.MessageFactory.attachment(attachment);
1552 message.text = "API Call:";
1553 return message;
1554 }
1555 // Generate a card to show for an API action w/o output
1556 RenderErrorCard(title, error) {
1557 let card = {
1558 type: "AdaptiveCard",
1559 version: "1.0",
1560 body: [
1561 {
1562 type: "Container",
1563 items: [
1564 {
1565 type: "TextBlock",
1566 text: error,
1567 wrap: true
1568 }
1569 ]
1570 }
1571 ]
1572 };
1573 const attachment = BB.CardFactory.adaptiveCard(card);
1574 const message = BB.MessageFactory.attachment(attachment);
1575 message.text = title;
1576 return message;
1577 }
1578}
1579/* Lookup table for CLRunners. One CLRunner per CL Model */
1580CLRunner.Runners = {};
1581exports.CLRunner = CLRunner;
1582//# sourceMappingURL=CLRunner.js.map
\No newline at end of file