ask-cli-x
Version:
Alexa Skills Kit (ASK) Command Line Interfaces
706 lines (705 loc) • 42.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DialogEvaluateController = exports.DIALOG_STATE_MACHINE = exports.CLI_GEN_NAMESPACE = void 0;
const acdl_1 = require("@alexa/acdl");
const fs_extra_1 = require("fs-extra");
const path_1 = __importDefault(require("path"));
const smapi_client_1 = require("../../clients/smapi-client");
const cli_error_1 = __importDefault(require("../../exceptions/cli-error"));
const cli_retriable_error_1 = require("../../exceptions/cli-retriable-error");
const inputs_1 = require("../../model/dialog/inputs");
const json_view_1 = require("../../view/json-view");
const skill_simulation_controller_1 = require("../skill-simulation-controller");
const acdl_line_parser_1 = require("./utils/acdl-line-parser");
const map_expressions_1 = require("./utils/map-expressions");
const expression_matcher_1 = require("./utils/expression-matcher");
const simulation_response_1 = require("./types/simulation-response");
const simulation_response_parser_1 = require("./simulation-response-parser");
const turn_cursor_1 = require("./utils/turn-cursor");
const acdl_utils_1 = require("./utils/acdl-utils");
const interactions_1 = require("./types/interactions");
const acdl_interaction_module_factory_1 = __importStar(require("./acdl-interaction-module-factory"));
const explain_ast_1 = require("./utils/explain-ast");
const types_1 = require("./types");
exports.CLI_GEN_NAMESPACE = "askcli.interaction";
var DIALOG_STATE_MACHINE;
(function (DIALOG_STATE_MACHINE) {
DIALOG_STATE_MACHINE[DIALOG_STATE_MACHINE["TURN_START"] = 0] = "TURN_START";
DIALOG_STATE_MACHINE[DIALOG_STATE_MACHINE["CORRECTION_START"] = 1] = "CORRECTION_START";
DIALOG_STATE_MACHINE[DIALOG_STATE_MACHINE["CORRECTION_CONTINUE"] = 2] = "CORRECTION_CONTINUE";
DIALOG_STATE_MACHINE[DIALOG_STATE_MACHINE["CORRECTION_COMPLETE"] = 3] = "CORRECTION_COMPLETE";
DIALOG_STATE_MACHINE[DIALOG_STATE_MACHINE["END"] = 4] = "END";
})(DIALOG_STATE_MACHINE = exports.DIALOG_STATE_MACHINE || (exports.DIALOG_STATE_MACHINE = {}));
/**
* Controller for the dialog evaluate mode.
* The class tracks the state of the dialog turn and correction, with event hooks through the flow.
* It tracks the SMAPI interactions and update the states accordingly.
*
* The control flow and the event handler looks like below:
* ```
*
* (loop) - TURN_START // turnView starts
* USER > + onUtteranceInput -> this.simulateUtterance
* ALEXA>
* Response accept? + onTurnConfirm -> this.passTurn
* (loop) - CORRECTION_START // correctionView starts
* -------------------
* In prediction mode:
* prediction: * acdl.printNode -> this.generateAcdl
* accept?: + onPredictionConfirm -> this.passPrediction
* correction: + onCorrectionInput -> this.correctTurn
* (loop end)
*
* Goodbye! - END // loop of turns finish and dialog quits
* ```
*/
class DialogEvaluateController extends skill_simulation_controller_1.SkillSimulationController {
/**
* Constructor for DialogEvaluateController.
* @param {Object} props | config object includes information such as skillId, locale, profile, stage.
*/
constructor(props) {
super(props);
const project = props.project ||
(() => {
const projectConfig = (0, acdl_1.loadProjectConfigSync)(process.cwd(), this.profile);
return (0, acdl_1.loadProjectSync)(projectConfig);
})();
this.initialNewSession = props.newSession === false ? props.newSession : true;
this.timeStamp = this._getFormattedTimeStamp();
this.interactionFilePath = path_1.default.join(process.cwd(), "skill-package", "conversations", `cli-gen-interaction-${this.timeStamp}.acdl`);
this.namespace = `${exports.CLI_GEN_NAMESPACE}_${this.timeStamp}`;
this.moduleFactory = (0, acdl_interaction_module_factory_1.default)(project, this.timeStamp, this.namespace);
}
/**
* Call simulate-skill to get service prediction over a user utterance. There are two cases this method is called:
* 1) Call at the start of each dialog turn, to get the initial prediction from the service, and build it into ACDL and display
* 2) Call after a correction is requested, to refresh the this.turnSimulation, with the latest service response after a prediction gets corrected
* The simulation response is loaded to its POJO during the flow.
*
* @param utterance
*/
async simulateUtterance(evaluateContext, utterance, newSession) {
var _a, _b, _c, _d, _e, _f, _g;
const postSimulationResponse = await this.startSkillSimulation(utterance, newSession);
const simulationResult = await this.getSkillSimulationResult((_a = postSimulationResponse.body) === null || _a === void 0 ? void 0 : _a.id);
const turnSimulation = (0, simulation_response_1.transformSimulationResponse)(simulationResult.body);
const responses = turnSimulation.alexaResponses.map((ar) => ar.content.caption);
const firstResponse = turnSimulation.alexaResponses.length > 0 ? turnSimulation.alexaResponses[0].content.caption : undefined;
const lastConversation = (_b = turnSimulation.conversations) === null || _b === void 0 ? void 0 : _b[((_c = turnSimulation.conversations) === null || _c === void 0 ? void 0 : _c.length) - 1];
const conversationPredictionAst = lastConversation && this.buildInitialPredictions(evaluateContext, lastConversation, firstResponse);
return {
alexaResponses: responses,
conversation: conversationPredictionAst,
utterance: (_g = (_f = (_e = (_d = turnSimulation === null || turnSimulation === void 0 ? void 0 : turnSimulation.conversations) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.event) === null || _f === void 0 ? void 0 : _f.input) !== null && _g !== void 0 ? _g : utterance,
delegationType: turnSimulation.type,
};
}
/**
* An empty actions array indicates the Action has failed even if getSimulation response has successful status code.
*
* @param simulationResult response from getSimulation
* @returns boolean representing whether any conversations object has an empty actions array
*/
actionFailed(conversation) {
return conversation && !conversation.actions.length;
}
/**
* Based on the service prediction, typeDecl or the content will be passed to this.draftInitialBuilder and get compiled.
* The compiled AST will then be used to get printed to ACDL. Result of AST and ACDL will be returned for the View to display.
*
* @returns ACDLLineInfo
*/
buildInitialPredictions(context, lastConversation, firstResponse) {
const [[event, ...actions]] = [lastConversation.event, ...lastConversation.actions].reduce(([interactions, originInteractions], prediction) => {
const originNodes = this.moduleFactory.getAllContentInPredictionAsts(originInteractions);
// Generate AST based on the current state.
const sourceScope = this.moduleFactory.contextToProjectScope(context, originInteractions);
// Expression map from only most recent server prediction
const serverPredictionSimpleExpressionMap = (0, map_expressions_1.simpleExpressionMap)(originNodes, sourceScope.project);
const originInteraction = this.simulationResponseToAST(prediction, serverPredictionSimpleExpressionMap, sourceScope.project, sourceScope.module, new simulation_response_parser_1.Counter(context.interactionBlock.length.toString()), firstResponse);
/**
* Some types might be missing if we dont include the current source node in the project.
*/
const sourceScope2 = this.moduleFactory.contextToProjectScope(context, [...originInteractions, originInteraction]);
return [
[
...interactions,
(currentInteractions, counter) => {
const { project: targetProject, module: targetModule } = this.moduleFactory.contextToProjectScope(context, currentInteractions);
const currentNodes = currentInteractions && this.moduleFactory.getAllContentInPredictionAsts(currentInteractions);
const _counter = counter !== null && counter !== void 0 ? counter : new simulation_response_parser_1.Counter(context.interactionBlock.length.toString());
/**
* A mapping between expression outputs and types used by expressions used to bridge the current
* interaction state and the one returned by future server responses.
*
* If no current interaction group is given, use the source one in the closure only.
*/
const expressionMap = currentNodes
? (0, map_expressions_1.mapExpressions)(originNodes, currentNodes, sourceScope2.project, targetProject)
: (0, map_expressions_1.simpleExpressionMap)(originNodes, sourceScope2.project);
return this.simulationResponseToAST(prediction, expressionMap, targetProject, targetModule, _counter, firstResponse);
},
],
[...originInteractions, originInteraction],
];
}, [[], []]);
return { event, actions };
}
/**
* This handler is triggered when user input Yes after the initial overview of the ACDL prediction is displayed.
* Based on user's decision:
* 1) Place the approved conversation into the greater evaluation context interaction block.
*/
acceptTurn(turnContext) {
const counter = new simulation_response_parser_1.Counter(turnContext.interactionBlock.length.toString());
return {
...turnContext,
interactionBlock: turnContext.last.conversation
? [
...turnContext.interactionBlock,
{
// We don't fully resolve the uncommitted interactions until the last minute.
// This is when we do it for accepted turns.
expressions: this.moduleFactory.resolveTurnInteractions([turnContext.last.conversation.event, ...turnContext.last.conversation.actions], counter),
},
]
: turnContext.interactionBlock,
};
}
/**
* This handler is triggered when user input No after the initial overview of the ACDL prediction is displayed.
* Based on user's decision:
* 1) Correction mode starts, all the state values inside correction mode reset.
*/
async rejectTurn(dialogContext) {
// These properties should not be null at this point, but lets check anyways.
// The DX should not allow correction when there is no conversation data.
const lastConversation = dialogContext.last.conversation;
if (!lastConversation) {
throw new cli_error_1.default("Cannot reject or correct a non-conversation response.");
}
return {
...dialogContext,
turnCorrections: { endPrediction: false, corrections: [] },
turnCursor: {
eventPrediction: lastConversation.event,
},
last: {
...dialogContext.last,
conversation: lastConversation,
},
// initialize the turn interaction group. We have no corrections yet, so maintain one array.
turnInteractionGroup: {
expressions: [],
},
};
}
/**
* This handler is called right before each prediction is going to be displayed.
* Based on this.turnCorrectionStarted, this.acdlbuilder or this.draftBuilderInCorrection will be used to execute the build/compile.
* The AST will again be transformed to ACDL and used for display.
*/
turnToAcdl(correctionContext) {
if ((0, turn_cursor_1.isEndTurnCursor)(correctionContext.turnCursor)) {
return [simulation_response_1.EndTurnPrediction];
}
const ast = (0, turn_cursor_1.predictionAtCursor)(correctionContext.turnCursor);
const resolvedTurn = this.moduleFactory.resolveTurnInteractionForContext(correctionContext, ast);
const { project } = this.moduleFactory.contextToProjectScope(correctionContext, [resolvedTurn]);
return [
...(resolvedTurn.ast.typeDecl ? [this.moduleFactory.printAstToAcdl(resolvedTurn.ast.typeDecl, project)] : []),
this.moduleFactory.printAstToAcdl(resolvedTurn.ast.content, project),
];
}
shouldDisableTurnCorrection(correctionContext) {
return (0, turn_cursor_1.isEventTurnCursor)(correctionContext.turnCursor) && correctionContext.last.delegationType === simulation_response_1.DelegationType.Ingress;
}
updateCorrectionPayload(correctionPayload, project, ast) {
return {
...correctionPayload,
corrections: [
...correctionPayload.corrections,
...(ast.typeDecl ? [this.moduleFactory.astToAcir(project, ast.typeDecl)] : []),
...(correctionPayload.endPrediction ? [] : [this.moduleFactory.astToAcir(project, ast.content)]),
],
};
}
/**
* Handler when user approves a single prediction.
* When user approves a prediction:
* 1) If the turnCursor shows this is already end of the turn, correction mode finishes
* 2) Otherwise the approved prediction will be built and compiled into either this.acdlBuilder or this.draftBuilderInCorrection.
*
* @returns complete
*/
acceptPrediction(correctionContext) {
var _a;
if (this.actionFailed((_a = correctionContext === null || correctionContext === void 0 ? void 0 : correctionContext.last) === null || _a === void 0 ? void 0 : _a.conversation)) {
throw new cli_retriable_error_1.CliRetriableError("Something went wrong. Check your skill's Lambda response.");
}
/* end of turn, all predictions have been corrected/approved within this turn */
// TODO: Should we ask about end turn?
if ((0, turn_cursor_1.isEndTurnCursor)(correctionContext.turnCursor)) {
// All done, nothing else to do, but commit. We'll commit in the correction end state.
return [true, correctionContext];
}
else {
// not complete
const currentTurnAst = (0, turn_cursor_1.predictionAtCursor)(correctionContext.turnCursor);
// Resolve the AST of the current turn before generating ACDL for the corrections array.
const contextWithCorrections = {
...correctionContext,
// if we are already correcting, do nothing.
turnInteractionGroup: (0, interactions_1.isUnresolvedExpressionInteractionGroup)(correctionContext.turnInteractionGroup)
? { expressions: [...correctionContext.turnInteractionGroup.expressions, currentTurnAst] }
: // If we are just starting to correct.
{
actual: correctionContext.turnInteractionGroup.actual,
expected: [...correctionContext.turnInteractionGroup.expected, currentTurnAst],
},
};
const projectScope = this.moduleFactory.contextToProjectScope(contextWithCorrections);
const lastTurn = (0, acdl_interaction_module_factory_1.getLastExpectedFromInteractionGroup)(projectScope.lastGroup);
if (!lastTurn) {
throw new cli_error_1.default("Missing last turn.");
}
return [
false,
{
...contextWithCorrections,
// collect actuals for future possible modifyLastTurn call, and move to next cursor when prediction passes
turnCorrections: this.updateCorrectionPayload(contextWithCorrections.turnCorrections, projectScope.project, lastTurn.ast),
turnCursor: (0, turn_cursor_1.createOrIncrementTurnCursor)(contextWithCorrections.last.conversation, contextWithCorrections.turnCursor),
},
];
}
}
rejectPrediction(correctionContext) {
// The interaction group is building a single list of interaction for the turn, change it into a correction group
if ((0, interactions_1.isUnresolvedExpressionInteractionGroup)(correctionContext.turnInteractionGroup)) {
return {
...correctionContext,
turnInteractionGroup: {
// We resolve the interactions here because we will no longer edit any of the context above this point
// (except for types)
actual: this.moduleFactory.resolveTurnInteractions([correctionContext.last.conversation.event, ...correctionContext.last.conversation.actions],
// This is the last time we will resolve these nodes. Resolve with unique generated names.
new simulation_response_parser_1.Counter(`ACTUAL_${correctionContext.interactionBlock.length}`)),
// Expected is all approved turns up to here.
expected: correctionContext.turnInteractionGroup.expressions,
},
};
}
// If rejected, do nothing.
return correctionContext;
}
/** Commits the result of the correction into the evaluate context and returns it */
commitCorrection(correctionContext) {
const counter = new simulation_response_parser_1.Counter(correctionContext.interactionBlock.length.toString());
return {
interactionBlock: [
...correctionContext.interactionBlock,
this.moduleFactory.resolveInteractionGroup(correctionContext.turnInteractionGroup, counter),
],
};
}
// TODO: Can we move most of the validation to the validateTurn method?
async getCorrectionTurn(correctionContext, corrections) {
const { type, content } = corrections;
if (!(type === null || type === void 0 ? void 0 : type.correction) && !content.correction) {
throw new cli_retriable_error_1.CliRetriableError(`Inputs are empty, please try again.`);
}
const correctedPrediction = !(0, turn_cursor_1.isEndTurnCursor)(correctionContext.turnCursor)
? this.moduleFactory.resolveTurnInteractionForContext(correctionContext, (0, turn_cursor_1.predictionAtCursor)(correctionContext.turnCursor))
: undefined;
const typedeclAst = (type === null || type === void 0 ? void 0 : type.correction)
? this.validateAndParseCorrectionType(type.correction, correctionContext)
: // If the type isn't given, try to assign the type which was previously there.
correctedPrediction === null || correctedPrediction === void 0 ? void 0 : correctedPrediction.ast.typeDecl;
const contentAst = this.validateAndParseCorrectionContent(content.correction, correctedPrediction === null || correctedPrediction === void 0 ? void 0 : correctedPrediction.ast.content);
// Does this need to be in the closure? Could we compute and then put in the closure? This may be useful later...
return () => {
var _a;
return {
// Clone because adding to the interaction later will mutate them.
ast: { typeDecl: typedeclAst === null || typedeclAst === void 0 ? void 0 : typedeclAst.clone(), content: contentAst.clone() },
// Assume the correction is the same type
source: (_a = correctedPrediction === null || correctedPrediction === void 0 ? void 0 : correctedPrediction.source) !== null && _a !== void 0 ? _a : "action",
};
};
}
validateCorrection(project, lastGroup) {
const lastTurn = (0, acdl_interaction_module_factory_1.getLastExpectedFromInteractionGroup)(lastGroup);
if (!lastTurn) {
throw new cli_retriable_error_1.CliRetriableError("Missing last turn.");
}
this.validateTurn(project, lastTurn);
return lastTurn.ast;
}
validateTurn(project, turnInteraction) {
const typeLastTurn = turnInteraction.ast.typeDecl;
const contentLastTurn = turnInteraction.ast.content;
const checker = project.getTypeChecker();
const typeErrors = typeLastTurn && (0, acdl_1.validateTypeDeclaration)(typeLastTurn, checker);
if (typeErrors && typeErrors.length > 0) {
throw new cli_retriable_error_1.CliRetriableError("Invalid ACDL type input. Errors: " + typeErrors.map((e) => e.message).join(","));
}
const contentErrors = (0, acdl_1.validateExpression)(contentLastTurn, checker);
if (contentErrors && contentErrors.length > 0) {
throw new cli_retriable_error_1.CliRetriableError("Invalid ACDL content input. Errors: " +
contentErrors
.map((e) => e.message)
.map((message) => {
const arr = message.split(",");
return arr.filter((value) => !value.includes(this.namespace)).join(",");
})
.join(","));
}
}
/**
* Handler when user provide the corrections (two lines but typeDecl can be optional).
* The user input ACDL lines will be compiled to AST and builder into this.acdlBuilder.
* Then CLI makes modifyLastTurn API call with this.turnCorrectionPayload, and following with a refresh for this.turnSimulation.
* After the correction is succeeded, we calibrate the turnCursor based on the prediction, and update existing variables.
*
* @param correctedAcdl user input for corrections
*/
async updateCorrectionContextWithCorrections(correctionContext, corrections) {
const isEndTurn = (0, inputs_1.isCommand)(corrections);
const updatedCorrectionContext = {
...correctionContext,
turnCorrections: {
endPrediction: isEndTurn,
corrections: correctionContext.turnCorrections.corrections,
},
};
const unresolvedCorrectionTurn = isEndTurn ? [] : [await this.getCorrectionTurn(updatedCorrectionContext, corrections)];
if ((0, interactions_1.isUnresolvedExpressionInteractionGroup)(updatedCorrectionContext.turnInteractionGroup)) {
throw new cli_error_1.default("Unexpected expression interaction group during correction.");
}
const contextWithCorrections = {
...updatedCorrectionContext,
turnInteractionGroup: {
actual: updatedCorrectionContext.turnInteractionGroup.actual,
expected: [...updatedCorrectionContext.turnInteractionGroup.expected, ...unresolvedCorrectionTurn],
},
};
const { project, module, lastGroup } = this.moduleFactory.contextToProjectScope(contextWithCorrections);
const [validatedTurn, updatedCorrectionPayload] = this.updateCorrectionPayloadWithValidatedTurn(contextWithCorrections, project, lastGroup, isEndTurn);
const updatedCorrectionPayloadWithTypes = this.updateCorrectionPayloadWithResolvedTypes(updatedCorrectionPayload, validatedTurn === null || validatedTurn === void 0 ? void 0 : validatedTurn.content, project, module);
try {
await this.modifyLastTurn(updatedCorrectionPayloadWithTypes);
// refresh the prediction sequence after modifyLastTurn
const nextConversation = await this.simulateUtterance(contextWithCorrections, contextWithCorrections.last.utterance, false);
if (this.actionFailed(nextConversation.conversation)) {
throw new cli_retriable_error_1.CliRetriableError("Something went wrong. Check your skill's Lambda response.");
}
if (!(0, types_1.isCorrectionTurnResult)(nextConversation)) {
throw new Error("Something went wrong. The simulation must be contain a conversation.");
}
// Pass end turn when the input is EndTurn command and not a correction object.
const updatedTurnCursor = isEndTurn
? turn_cursor_1.END_TURN_CURSOR
: this.updateCursor(contextWithCorrections, project, nextConversation, validatedTurn === null || validatedTurn === void 0 ? void 0 : validatedTurn.content);
return {
...contextWithCorrections,
last: nextConversation,
turnCorrections: updatedCorrectionPayloadWithTypes,
turnCursor: updatedTurnCursor,
};
}
catch (e) {
if (e instanceof cli_retriable_error_1.RetriableServiceError) {
throw new cli_retriable_error_1.RetriableServiceError(`${e.message}\nPlease try again for the correction.`);
}
else {
throw e;
}
}
}
updateCorrectionPayloadWithResolvedTypes(correctionPayload, correctionContent, project, module) {
var _a;
if (!correctionContent)
return correctionPayload;
const genericArguments = (0, acdl_1.isCall)(correctionContent)
? correctionContent.genericArguments
: (_a = correctionContent.expression) === null || _a === void 0 ? void 0 : _a.genericArguments;
const correctionReferences = genericArguments === null || genericArguments === void 0 ? void 0 : genericArguments.filter(acdl_1.isTypeReference).map((genericArgument) => {
var _a, _b;
const node = ((_a = genericArgument.name) === null || _a === void 0 ? void 0 : _a.name) && (0, simulation_response_parser_1.getLastExpressionFromContext)((_b = genericArgument.name) === null || _b === void 0 ? void 0 : _b.name, project, module);
const acir = node && this.moduleFactory.astToAcir(project, node);
return acir;
});
if (!correctionReferences)
return correctionPayload;
const corrections = [...correctionPayload.corrections, ...correctionReferences];
return {
endPrediction: correctionPayload.endPrediction,
corrections: [...new Map(corrections.map((correction) => [correction.name, correction])).values()],
};
}
updateCorrectionPayloadWithValidatedTurn(contextWithCorrections, project, lastGroup, isEndTurn) {
if (isEndTurn) {
return [undefined, contextWithCorrections.turnCorrections];
}
else {
const validatedTurn = this.validateCorrection(project, lastGroup);
return [validatedTurn, this.updateCorrectionPayload(contextWithCorrections.turnCorrections, project, validatedTurn)];
}
}
validateAndParseCorrectionContent(correctionAcdl, correctedContentAst) {
var _a;
try {
const contentAst = (0, acdl_line_parser_1.parseACDLLine)(correctionAcdl, this.namespace);
if (!(contentAst instanceof acdl_1.Call || contentAst instanceof acdl_1.NameDeclaration)) {
throw new cli_retriable_error_1.CliRetriableError(`Failed to compile the second line, please try again. Input must be based on action call for user turn or response() for Alexa turn.`);
}
if (contentAst instanceof acdl_1.NameDeclaration) {
if (!(0, acdl_utils_1.isNamedCallExpression)(contentAst)) {
throw new cli_retriable_error_1.CliRetriableError(`Failed to compile the second line, please try again. Input must be based on action call for user turn or response() for Alexa turn.`);
}
}
else if (contentAst instanceof acdl_1.Call) {
if (correctedContentAst && correctedContentAst instanceof acdl_1.NameDeclaration && !(((_a = contentAst.name) === null || _a === void 0 ? void 0 : _a.name) === "response")) {
// TODO: Make this more flexible by understanding if the replaced names is not used.
throw new cli_retriable_error_1.CliRetriableError("Second line must be named if the corrected line was named.");
}
}
return contentAst;
}
catch (e) {
if (e instanceof cli_retriable_error_1.CliRetriableError)
throw e;
throw new cli_retriable_error_1.CliRetriableError(`Failed to compile the second line, please try again. ${e}`);
}
}
validateAndParseCorrectionType(correctionAcdl, correctionContext) {
try {
// Validate type correction...
const typedeclAst = (0, acdl_line_parser_1.parseACDLLine)(correctionAcdl, this.namespace);
if (!(typedeclAst instanceof acdl_1.TypeDeclaration)) {
throw new cli_retriable_error_1.CliRetriableError(`Failed to compile the first line, please try again. Input is optional but must declare a new type.`);
}
this.validateNewTypeName(typedeclAst, correctionContext);
return typedeclAst;
}
catch (e) {
if (e instanceof cli_retriable_error_1.CliRetriableError)
throw e;
throw new cli_retriable_error_1.CliRetriableError(`Failed to compile the first line, please try again. ${e}`);
}
}
async modifyLastTurn(currentPayload) {
try {
const res = await this.smapiClient.skill.test.modifyLastTurn({
skillId: this.skillId,
stage: this.stage,
locale: "en-US",
payload: currentPayload,
});
if ((0, smapi_client_1.isSmapiError)(res)) {
let errMsg = (0, json_view_1.toString)(res.body);
if (res.body && res.body.message) {
const violations = res.body.violations ? ` with violation ${res.body.violations.map((v) => v.message).join(", ")}` : "";
errMsg = `${res.body.message}${violations}`;
}
throw new cli_retriable_error_1.RetriableServiceError(`Failed to correct. Service error: ${errMsg}`, res.body);
}
return res;
}
catch (err) {
if (err instanceof cli_retriable_error_1.RetriableServiceError) {
throw err;
}
throw new cli_retriable_error_1.RetriableServiceError(`Failed to correct. Service error: ${err}`);
}
}
// TODO: change this method to format "command outputs" that are read by the view.
// Should encapsulate all needed data, no formatting string.
async handleOutputSpecialCmds(evaluateContext, cmd) {
switch (cmd) {
case inputs_1.SpecialCommand.SAVE:
return await this.handleSaveCommand(evaluateContext);
case inputs_1.SpecialCommand.VARS:
// TOOD: Compute variable names from the context
return this.printVarNames(evaluateContext);
default:
const exhaustive = cmd;
return exhaustive;
}
}
async handleSaveCommand(evaluateContext) {
await this.writeToFile(evaluateContext);
return `On-going interactions saved to ${path_1.default.relative(process.cwd(), this.interactionFilePath)}. Run "askx deploy" to apply any correction to your model.`;
}
printVarNames(evaluateContext) {
var _a, _b;
const { project, lastGroup } = this.moduleFactory.contextToProjectScope(evaluateContext);
const node = lastGroup
? (_a = (0, acdl_interaction_module_factory_1.getLastExpectedFromInteractionGroup)(lastGroup)) === null || _a === void 0 ? void 0 : _a.ast.content
: (_b = (0, acdl_interaction_module_factory_1.getLastExpectedFromInteractionGroup)(evaluateContext.interactionBlock[evaluateContext.interactionBlock.length - 1])) === null || _b === void 0 ? void 0 : _b.ast.content;
const vars = (0, acdl_utils_1.getVisibleLocalVariableNames)(project.getTypeChecker(), node);
if (vars.length === 0) {
return "No variables declared yet.";
}
else {
return `Variables: ${vars.join(", ")}`;
}
}
async writeToFile(evaluationContext) {
const { project, module } = this.moduleFactory.contextToProjectScope(evaluationContext);
await (0, fs_extra_1.outputFile)(this.interactionFilePath, (0, acdl_1.printModule)(module, project.getTypeChecker()));
}
async partitionAndFormatAstLines(context, correctionResult, responses) {
const counter = new simulation_response_parser_1.Counter(context.interactionBlock.length.toString());
const interactions = this.moduleFactory.resolveTurnInteractions([correctionResult.conversation.event, ...correctionResult.conversation.actions], counter);
const { project } = this.moduleFactory.contextToProjectScope(context, interactions);
const acdlLines = interactions.map((interaction) => ({
// Before lines are printed before the partitioned responses
before: interaction.source === "event",
type: interaction.ast.typeDecl && {
acdl: this.moduleFactory.printAstToAcdl(interaction.ast.typeDecl, project),
explanation: (0, explain_ast_1.explainAst)(interaction.ast.typeDecl),
},
content: {
acdl: this.moduleFactory.printAstToAcdl(interaction.ast.content, project),
explanation: (0, explain_ast_1.explainAst)(interaction.ast.content),
},
}));
// Flatten the ACDL lines and
// then partition between lines to be printed before the responses and ones to be printed with the responses
const [beforeLines, adclResponseLines] = acdlLines
.map(({ type, content, before }) => ({ lines: [...(type ? [type] : []), content], before }))
.reduce(([beforeLines, responses], { lines, before }) => {
return [before ? [...beforeLines, ...lines] : beforeLines, !before ? [...responses, ...lines] : responses];
}, [[], []]);
// Partition the ACDL lines with each partition ending with response()
const partitionedAcdlLines = adclResponseLines
.reduce((acc, resp) => {
const [head = [], ...rest] = acc;
// Add a new partition to the start when the response ends with response()
return [
...(resp.acdl.match(/.*response\(.+\).*/g) ? [[]] : []),
// Add the current response to the first partition
[...head, resp],
...rest,
];
}, [])
// New partitions are at the end of the list, reverse to get the right order
.reverse();
return {
// Match a response with the partitioned acdlLines.
// Its possible for the acdlLine partitions or the responses to be longer.
groups: [...Array(Math.max(partitionedAcdlLines.length, responses.length)).keys()].map((i) => ({
responseLine: responses.length > i ? responses[i] : undefined,
acdlLines: partitionedAcdlLines.length > i ? partitionedAcdlLines[i] : undefined,
})),
before: beforeLines,
};
}
simulationResponseToAST(payload, expressionMap, project, module, counter, firstResponse) {
if ((0, simulation_response_1.isEventPrediction)(payload)) {
return (0, simulation_response_parser_1.getReceivedAST)(payload, counter, project, module);
}
else if ((0, simulation_response_1.isActionPrediction)(payload)) {
if (!firstResponse) {
throw new cli_error_1.default("Response AST expects an Alexa Response.");
}
return (0, simulation_response_parser_1.getResponseAST)(payload, this.escapeRegExp(firstResponse), project, module, expressionMap, counter);
}
else {
throw new cli_error_1.default(`Convert unexpected data to AST: ${payload}`);
}
}
escapeRegExp(input) {
return input.replace(/"/g, "\\$&");
}
validateNewTypeName(newTypeDecl, correctionContext) {
var _a;
if (!((_a = newTypeDecl.name) === null || _a === void 0 ? void 0 : _a.name)) {
throw new cli_retriable_error_1.CliRetriableError(`The name for the newly declared type must not be empty.`);
}
const resolvedGroup = this.moduleFactory.resolveInteractionGroup(correctionContext.turnInteractionGroup, new simulation_response_parser_1.Counter(correctionContext.interactionBlock.length.toString()));
// avoid duplicate typeDecl name
// TODO: Do this with the parse instead? Probably
const dupExist = this.moduleFactory
.getAllTypesInInteractionGroups([...correctionContext.interactionBlock, resolvedGroup])
.some((expr) => { var _a, _b; return ((_a = expr.name) === null || _a === void 0 ? void 0 : _a.name) === ((_b = newTypeDecl.name) === null || _b === void 0 ? void 0 : _b.name); });
if (dupExist) {
throw new cli_retriable_error_1.CliRetriableError(`The name for the newly declared type "${(0, acdl_1.getName)(newTypeDecl.name.name)}" is already used, please use another name for your type.`);
}
}
/**
* Calibrate the turnCursor after a correction happens, i.e. try to find the current position with the refreshed simulation response.
* The source of truth is the last correction user made, which must appear in the refreshed simulation response.
* This function iterates through the refreshed this.turnSimulation to find the lastCorrectedAST. Once found, update the turnCursor.
*/
updateCursor(context, project, nextTurn, expectedAst) {
var _a, _b;
try {
// the last expression in correction.expected should be ask.Call
const lastCorrectedAst = expectedAst instanceof acdl_1.NameDeclaration ? expectedAst.expression : expectedAst;
// this shouldn't happen...
if (!lastCorrectedAst) {
throw new cli_error_1.default("Content correction should have an expression.");
}
// When the last corrected AST is an event, set the cursor to the first action.
if ((_b = (_a = lastCorrectedAst.name) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.includes("received")) {
return (0, turn_cursor_1.turnCursorFromIndex)(nextTurn.conversation, 0);
}
// When the last corrected AST is a response, try to find that response in the server response
else {
const matcher = (0, expression_matcher_1.getCallMatcher)(lastCorrectedAst, project);
const [module, group] = this.moduleFactory.contextPlusUnresolvedToModule(context, [
nextTurn.conversation.event,
...nextTurn.conversation.actions,
]);
// group will be present, because we gave it.
const newProject = this.moduleFactory.getProjectWithModule(module);
// Index will already be one higher than the action list because the resolved interactions include the event.
const candidateIndex = ((0, interactions_1.isExpressionInteractionGroup)(group) ? group.expressions : group.expected)
.map((x) => x.ast.content)
.findIndex((c) => matcher(c, newProject));
if (candidateIndex > nextTurn.conversation.actions.length) {
throw new cli_error_1.default(`Unexpected service error. The last correction is not showing in the latest service predictions.`);
}
// all checkpoints passed, we find the lastCorrectedAst and update the turnCursor
return (0, turn_cursor_1.turnCursorFromIndex)(nextTurn.conversation, candidateIndex !== -1 ? candidateIndex : nextTurn.conversation.actions.length - 1);
}
}
catch (e) {
throw new cli_error_1.default(`Unexpected error when calibrate turn cursor: ${e}`);
}
}
_getFormattedTimeStamp() {
return new Date().toISOString().replace(/[:|\-|.]/g, "_");
}
}
exports.DialogEvaluateController = DialogEvaluateController;