UNPKG

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