UNPKG

ask-cli-x

Version:

Alexa Skills Kit (ASK) Command Line Interfaces

510 lines (509 loc) 28 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getResponseAST = exports.formAnnotatedNer = exports.Counter = exports.getReceivedAST = exports.getAPIAST = exports.argumentToAst = exports.resolveArgumentToAst = exports.getLastExpressionFromContext = exports.getArgumentReference = exports.getArgumentName = exports.getSimulationId = exports.getStatus = exports.getCaption = exports.getErrorMessage = exports.shouldEndSession = void 0; const simulation_response_1 = require("./types/simulation-response"); const cli_data_format_error_1 = require("../../exceptions/cli-data-format-error"); const cli_error_1 = __importDefault(require("../../exceptions/cli-error")); const acdl_1 = require("@alexa/acdl"); const map_expressions_1 = require("./utils/map-expressions"); const acdl_utils_1 = require("./utils/acdl-utils"); function shouldEndSession(response) { var _a, _b, _c, _d, _e; const invocations = (_b = (_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.skillExecutionInfo) === null || _b === void 0 ? void 0 : _b.invocations; if (!invocations) { return false; } for (const invocation of invocations) { if ((_e = (_d = (_c = invocation === null || invocation === void 0 ? void 0 : invocation.invocationResponse) === null || _c === void 0 ? void 0 : _c.body) === null || _d === void 0 ? void 0 : _d.response) === null || _e === void 0 ? void 0 : _e.shouldEndSession) { return true; } } return false; } exports.shouldEndSession = shouldEndSession; function getErrorMessage(response) { var _a, _b; return (_b = (_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.error) === null || _b === void 0 ? void 0 : _b.message; } exports.getErrorMessage = getErrorMessage; function getCaption(response) { var _a, _b; const alexaResponses = (_b = (_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.alexaExecutionInfo) === null || _b === void 0 ? void 0 : _b.alexaResponses; if (!alexaResponses) { return []; } return alexaResponses.map((element) => { var _a; return (_a = element === null || element === void 0 ? void 0 : element.content) === null || _a === void 0 ? void 0 : _a.caption; }); } exports.getCaption = getCaption; function getStatus(response) { return response === null || response === void 0 ? void 0 : response.status; } exports.getStatus = getStatus; function getSimulationId(response) { return response === null || response === void 0 ? void 0 : response.id; } exports.getSimulationId = getSimulationId; // Arguments are in this strange structure/ function getArgumentName(arg) { return Object.keys(arg)[0]; } exports.getArgumentName = getArgumentName; function getArgumentReference(arg) { return arg[getArgumentName(arg)].nameReference; } exports.getArgumentReference = getArgumentReference; function getLastExpressionFromContext(reference, project, module) { const scope = (0, acdl_utils_1.getLastInterationOrCorrectionExpectedBlock)(module); const node = scope && scope.expressions && project.getTypeChecker().lookupName(scope.expressions[scope.expressions.length - 1], reference); return node; } exports.getLastExpressionFromContext = getLastExpressionFromContext; function isCallResponseName(reference, project, module) { const node = project.getTypeChecker().lookupName(module, reference); return node instanceof acdl_1.NameDeclaration && node.expression instanceof acdl_1.Call; } // Look up NameDeclarations using ACDL type checker function tryTypeChecker(reference, project, module) { var _a; const node = getLastExpressionFromContext(reference, project, module); return (_a = node === null || node === void 0 ? void 0 : node.name) === null || _a === void 0 ? void 0 : _a.name; } // Look up properties of visible NameDeclarations using ACDL type checker function tryVisibleNameProperties(reference, project, module) { const checker = project.getTypeChecker(); const scope = (0, acdl_utils_1.getLastInterationOrCorrectionExpectedBlock)(module); const lexicalScope = scope && scope.expressions && checker.getVisibleNames(scope.expressions[scope.expressions.length - 1]); const matches = lexicalScope && lexicalScope .valueSeq() .toArray() .filter(acdl_1.isNameDeclaration) .filter((declaration) => { var _a, _b; return (_b = (_a = checker.getType(declaration)) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.find((property) => property.name === reference); }) .map((declaration) => { var _a; return (_a = declaration.name) === null || _a === void 0 ? void 0 : _a.name; }); return matches && matches.length ? matches[matches.length - 1] : undefined; } /** * Tried to resolve an argument name based on the current state. * 1. Try to find the argument name in the current lexical scope. * 2. Try to find the argument name in lexical scope of last expected block. * 3. Try to map the argument name as a mapped reference. * This happens when the referenced name as different in the ACIR than the current state due to the server not respecting local labels. * For example, a API result name is changed by the server or user. * current state: * type something { prop } * result = myApi() * server response: * type something { prop1 } * server response: result1 = myApi() * myApi2(result1.prop1) -> myApi2(result.prop) * 4. Try to map the argument name as mapped field. * This happens when the referenced name is actual a property of a new top level reference and not a lexical name. * For example, a request/invoke's slot names are referenced at the top level by the server. * type something { prop } * someResult = receive("{prop}") * myApi(prop) -> myApi(someResult.prop) * 5. Try to find the argument name as a property within lexical scope of last expected block. */ function resolveReference(argumentReference, project, module, expressionMap) { var _a; const [reference, property = undefined, ...rest] = argumentReference.split("."); if (isCallResponseName(reference, project, module)) { return [reference, ...(property ? [property] : []), ...rest]; } const variable = tryTypeChecker(reference, project, module); if (variable) { return [variable, ...(property ? [property] : []), ...rest]; } if (expressionMap) { const mappedReference = (0, map_expressions_1.tryMapReference)(reference, expressionMap, property); if (mappedReference) { // The reference pair may be found, but the property given doesn't exist in the mapping. // For now, lets just pass along the original property and let validation catch this. // TODO: Handle the case when a mapped reference doesn't contain the given first segment property or a mapping to the first segment property. const prop = (_a = mappedReference.field) !== null && _a !== void 0 ? _a : property; return [mappedReference.reference, ...(prop ? [prop] : []), ...rest]; } // name wasn't in the lexical scope or a mapped reference, try to lookup the name within a mapped reference. const mappedField = (0, map_expressions_1.tryMapField)(reference, expressionMap); if (mappedField) { return [mappedField.reference, mappedField.field, ...rest]; } } const prop = tryVisibleNameProperties(reference, project, module); if (prop) { return [prop, reference, ...rest]; } throw new cli_data_format_error_1.CliDataFormatError(`Name reference ${argumentReference} could not be resolved to any existing declared variable.`); } function buildReferenceFromSegments(segments) { function inner(segs) { const [head = undefined, ...tail] = segs; if (tail.length === 0) { return new acdl_1.NameReference(new acdl_1.Name(head)); } return new acdl_1.PropertyReference(new acdl_1.Name(head), inner(tail)); } return inner(segments.filter((x) => !!x).reverse()); } // TODO rewrite with tail recursion function resolveArgumentToAst(arg, project, module, expressionMap) { const argumentReference = getArgumentReference(arg); const argumentName = getArgumentName(arg); try { const segments = resolveReference(argumentReference, project, module, expressionMap); return argumentToAst(argumentName, segments); } catch (e) { if (e instanceof cli_data_format_error_1.CliDataFormatError) { throw new cli_data_format_error_1.CliDataFormatError(`Error resolving argument ${argumentName}: ${e.message}`); } throw e; } } exports.resolveArgumentToAst = resolveArgumentToAst; function argumentToAst(name, referenceSegments) { try { if (referenceSegments.length === 0) { throw new cli_data_format_error_1.CliDataFormatError("Argument Reference is empty"); } return new acdl_1.Argument(new acdl_1.Name(name), undefined, buildReferenceFromSegments(referenceSegments)); } catch (e) { if (e instanceof cli_data_format_error_1.CliDataFormatError) { throw new cli_data_format_error_1.CliDataFormatError(`Error resolving argument ${referenceSegments}: ${e.message}`); } throw e; } } exports.argumentToAst = argumentToAst; /** * returns AST for a = b(c=d) */ function getAPIAST(actionName, output, args, project, module, expressionMap) { var _a, _b, _c, _d; const node = getLastExpressionFromContext(actionName, project, module); const call = new acdl_1.Call(new acdl_1.Name(actionName), args ? args.map((arg) => resolveArgumentToAst(arg, project, module, expressionMap)) : undefined); if ((node === null || node === void 0 ? void 0 : node.kind) === "ActionDeclaration" && (((_b = (_a = node.returnType) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.name) === "Nothing" || ((_d = (_c = node.returnType) === null || _c === void 0 ? void 0 : _c.name) === null || _d === void 0 ? void 0 : _d.name) === "Void")) { return call; } return new acdl_1.NameDeclaration(new acdl_1.Name(output), undefined, call); } exports.getAPIAST = getAPIAST; /* * Returns AST for the ACDLs like below: * ``` * type T0 { t1 a t2 b } * x = received<T0>(<DialogAct>, "{utterance}") * ``` */ function getReceivedAST(response, counter, project, module) { try { const slots = response.slots; if (!slots || (slots && slots.length === 0)) { const actArg = new acdl_1.Argument(new acdl_1.Name("act"), undefined, new acdl_1.NameReference(new acdl_1.Name(`${acdl_1.AlexaConversationsNamespace}.${response.dialogAct.type}`))); const utteranceArg = new acdl_1.Argument(new acdl_1.Name("utterance"), undefined, new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaSchema.String), response.input)); return { ast: { content: new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaConversations.received), [actArg, utteranceArg]) }, source: "event", }; } else { const leafScope = (0, acdl_utils_1.getLastInterationOrCorrectionExpectedBlock)(module); const checker = project.getTypeChecker(); const syntheticTypeName = counter.safeVend("TYPE", checker, leafScope !== null && leafScope !== void 0 ? leafScope : module); const syntheticVarName = counter.safeVend("VAR", checker, leafScope !== null && leafScope !== void 0 ? leafScope : module); const actArg = new acdl_1.Argument(new acdl_1.Name("act"), undefined, new acdl_1.NameReference(new acdl_1.Name(acdl_1.AlexaConversationsNamespace + "." + response.dialogAct.type))); const utteranceArg = new acdl_1.Argument(new acdl_1.Name("utterance"), undefined, new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaSchema.String), formAnnotatedNer(response))); const slotData = Object.values( // Reduce the slots to "take first" of the slot slots.reduce((acc, slot) => ({ ...acc, [slot.name]: slot, }), {})) .map((slot) => { if (slot.typeReference && slot.typeReference.match(/(com\.amazon\.alexa\.schema\.)*List<.+>/g)) { const listSlotName = slot.typeReference.substring(slot.typeReference.lastIndexOf("<") + 1, slot.typeReference.indexOf(">")); return { typeProp: new acdl_1.TypeProperty(new acdl_1.Name(slot.name), new acdl_1.TypeReference(new acdl_1.Name(acdl_1.AlexaSchema.List), [new acdl_1.TypeReference(new acdl_1.Name(listSlotName))])), slotName: slot.name, }; } else { const typeRefFilterBuiltin = slot.typeReference.replace("AMAZON.", ""); return { slotName: slot.name, typeProp: new acdl_1.TypeProperty(new acdl_1.Name(slot.name), new acdl_1.TypeReference(new acdl_1.Name(typeRefFilterBuiltin))), }; } }) .filter((item) => !!item); return { source: "event", ast: { typeDecl: new acdl_1.TypeDeclaration(new acdl_1.Name(syntheticTypeName), slotData.map((x) => x.typeProp)), content: new acdl_1.NameDeclaration(new acdl_1.Name(syntheticVarName), undefined, new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaConversations.received), [actArg, utteranceArg], [new acdl_1.TypeReference(new acdl_1.Name(syntheticTypeName))])), }, }; } } catch (e) { throw new cli_data_format_error_1.CliDataFormatError(`Failed to transform EventPrediction to AST. ${e}`); } } exports.getReceivedAST = getReceivedAST; class Counter { constructor(context) { this.context = context; this._VAR_COUNTER = 0; this._TYPE_COUNTER = 0; this._PAYLOAD_TYPE_COUNTER = 0; } vend(prefix) { switch (prefix) { case "VAR": this._VAR_COUNTER += 1; return `${prefix}_${this.context}_${this._VAR_COUNTER}`; case "TYPE": this._TYPE_COUNTER += 1; return `${prefix}_${this.context}_${this._TYPE_COUNTER}`; case "PAYLOAD_TYPE": this._PAYLOAD_TYPE_COUNTER += 1; return `${prefix}_${this.context}_${this._PAYLOAD_TYPE_COUNTER}`; } } /** * // Check if the name already exists, if it does, try again, if not, return it. */ safeVend(prefix, checker, scope) { const name = this.vend(prefix); if (checker.lookupName(scope, name)) { return this.safeVend(prefix, checker, scope); } return name; } } exports.Counter = Counter; function formAnnotatedNer(response) { var _a; const inputAsArray = response.input.split(/\s/); for (const slot of (_a = response.slots) !== null && _a !== void 0 ? _a : []) { if (inputAsArray.length < slot.index.end) { throw new cli_error_1.default(`Service fatal error: the indexEnd for slot ${slot.name} is out of bound.`); } inputAsArray[slot.index.start] = `{${slot.name}|${inputAsArray[slot.index.start]}`; inputAsArray[slot.index.end - 1] = `${inputAsArray[slot.index.end - 1]}}`; } return inputAsArray.join(" "); } exports.formAnnotatedNer = formAnnotatedNer; function getTypeForSegments(segments, checker, module) { const [reference, ...rest] = segments; const scope = (0, acdl_utils_1.getLastInterationOrCorrectionExpectedBlock)(module); const referenceNode = scope && scope.expressions && checker.lookupName(scope.expressions[scope.expressions.length - 1], reference); if (!referenceNode) { throw new cli_data_format_error_1.CliDataFormatError(`Reference name ${reference} not found.`); } const type = checker.getType(referenceNode); if (rest.length === 0) { return type === null || type === void 0 ? void 0 : type.toTypeReference(); } const typeDecl = type === null || type === void 0 ? void 0 : type.declaration; if (!typeDecl || !(typeDecl instanceof acdl_1.TypeDeclaration)) { return; } return getTypeFromSegmentsProperties(typeDecl, rest, checker); } // given a type, recursively retrieve the type of a chain of properties. function getTypeFromSegmentsProperties(type, segments, checker) { var _a; // TODO support list access. Segments are only dot separated today. const [head, ...tail] = segments; const prop = (_a = type.properties) === null || _a === void 0 ? void 0 : _a.find((p) => { var _a; return ((_a = p.name) === null || _a === void 0 ? void 0 : _a.name) === head; }); // If the prop is missing or the type is missing on the prop... return nothing const propType = prop && prop.type; if (tail.length === 0) { return propType; } const propTypeDecl = propType && checker.lookupTypeReference(propType); if (!propTypeDecl || !(propTypeDecl instanceof acdl_1.TypeDeclaration)) { return; } return getTypeFromSegmentsProperties(propTypeDecl, tail, checker); } function getSyntheticTypeLineFromResponseArguments(response, project, module, expressionMap, counter) { if (response.arguments) { const checker = project.getTypeChecker(); const leafNode = (0, acdl_utils_1.getLastInterationOrCorrectionExpectedBlock)(module); const syntheticTypeName = counter.safeVend("PAYLOAD_TYPE", checker, leafNode !== null && leafNode !== void 0 ? leafNode : module); const resolvedArguments = response.arguments.map((argument) => { const segments = resolveReference(getArgumentReference(argument), project, module, expressionMap); return { name: getArgumentName(argument), reference: buildReferenceFromSegments(segments), referenceName: getArgumentReference(argument), segments, }; }); return { typeAST: new acdl_1.TypeDeclaration(new acdl_1.Name(syntheticTypeName), // Map retrieve argument types and build the synthetic type resolvedArguments.map((argument) => { const checker = project.getTypeChecker(); const type = getTypeForSegments(argument.segments, checker, module); if (!type || !(type instanceof acdl_1.TypeReference)) { throw new cli_error_1.default(`Expected ${argument.name}'s reference ${argument.referenceName} to have a type.`); } return new acdl_1.TypeProperty(new acdl_1.Name(argument.name), type); })), payloadType: syntheticTypeName, argumentAST: resolvedArguments.map((argument) => new acdl_1.Argument(new acdl_1.Name(argument.name), undefined, argument.reference)), }; } return; } function getPromptAST(response) { const { prompt, display } = response; if (display) { return new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaConversations.MultiModalResponse), [ new acdl_1.Argument(new acdl_1.Name("apl"), undefined, new acdl_1.NameReference(new acdl_1.Name(display))), new acdl_1.Argument(new acdl_1.Name("apla"), undefined, new acdl_1.NameReference(new acdl_1.Name(prompt))), ]); } else { return new acdl_1.NameReference(new acdl_1.Name(prompt)); } } function getResponseAST(response, caption, project, module, expressionMap, counter) { try { switch (response.type) { case simulation_response_1.PredictionType.ApiAction: { const returnTypeName = getApiReturnType(response.name, project, module); if (!returnTypeName) { throw new cli_error_1.default("Api return type was not found."); } const ast = getAPIAST(response.name, response.output.name, response.arguments, project, module, expressionMap); return { source: "action", ast: { content: ast, }, }; } case simulation_response_1.PredictionType.ResponseAction: { const dialogAct = response.dialogAct; const surfaceForm = caption; const dialogActData = getDialogActAstData(dialogAct); const nextDialogAct = dialogAct.nextDialogAct; let nextDialogActData = nextDialogAct ? getDialogActAstData(nextDialogAct) : undefined; const synthType = dialogActData.includeType && getSyntheticTypeLineFromResponseArguments(response, project, module, expressionMap, counter); const synthTypePayloadArg = synthType ? [new acdl_1.Argument(new acdl_1.Name("payload"), undefined, new acdl_1.Call(new acdl_1.Name(synthType.payloadType), synthType.argumentAST))] : []; const synthTypeDecl = synthType ? { typeDecl: synthType.typeAST } : {}; const responseArguments = [ new acdl_1.Argument(new acdl_1.Name("response"), undefined, getPromptAST(response)), new acdl_1.Argument(new acdl_1.Name("act"), undefined, new acdl_1.Call(new acdl_1.Name(dialogActData.actCallName), dialogActData.actionArguments)), new acdl_1.Argument(new acdl_1.Name("surfaceForm"), undefined, new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaSchema.String), surfaceForm)), ...synthTypePayloadArg, ...(nextDialogActData ? [ new acdl_1.Argument(new acdl_1.Name("nextAct"), undefined, new acdl_1.Call(new acdl_1.Name(nextDialogActData.actCallName), nextDialogActData.actionArguments)), ] : []), ]; return { source: "action", ast: { ...synthTypeDecl, content: new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaConversations.response), responseArguments), }, }; } } } catch (e) { throw new cli_data_format_error_1.CliDataFormatError(`Failed to transform ActionPrediction to AST. ${e}`); } } exports.getResponseAST = getResponseAST; function getApiReturnType(apiName, project, module) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; const checker = project.getTypeChecker(); // Lookup the return type of the api being invoked. const actionDecl = checker.lookupName(module, apiName); // If the return type is a list, get the type argument of the list. if (((_b = (_a = actionDecl === null || actionDecl === void 0 ? void 0 : actionDecl.returnType) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.name) === "List") { const subType = (_f = (_e = (_d = (_c = actionDecl === null || actionDecl === void 0 ? void 0 : actionDecl.returnType) === null || _c === void 0 ? void 0 : _c.arguments) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.name) === null || _f === void 0 ? void 0 : _f.name; const apiReturnTypeDecl = checker.lookupName(actionDecl, subType); return `List<${(_g = apiReturnTypeDecl.name) === null || _g === void 0 ? void 0 : _g.name}>`; } else { const apiReturnType = (_j = (_h = actionDecl === null || actionDecl === void 0 ? void 0 : actionDecl.returnType) === null || _h === void 0 ? void 0 : _h.name) === null || _j === void 0 ? void 0 : _j.name; const apiReturnTypeDecl = checker.lookupName(actionDecl, apiReturnType); return (_k = apiReturnTypeDecl.name) === null || _k === void 0 ? void 0 : _k.name; } } function getDialogActAstData(act) { switch (act.type) { case "ProvideHelp": case "YouAreWelcome": case "Welcome": case "OutOfDomain": case "Bye": case "ReqMore": return { actionArguments: [], includeType: false, actCallName: acdl_1.AlexaConversationsNamespace + "." + act.type, }; case "ConfirmAction": return { actionArguments: [new acdl_1.Argument(new acdl_1.Name("actionName"), undefined, new acdl_1.NameReference(new acdl_1.Name(act.actionName)))], includeType: true, actCallName: acdl_1.AlexaConversations.ConfirmAction, }; case "Notify": return { actionArguments: [ new acdl_1.Argument(new acdl_1.Name("actionName"), undefined, new acdl_1.NameReference(new acdl_1.Name(act.actionName))), ...("success" in act && act.success ? [new acdl_1.Argument(new acdl_1.Name("success"), undefined, new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaSchema.Boolean), act.success))] : []), ], includeType: true, actCallName: acdl_1.AlexaConversations.Notify, }; case "Offer": return { actionArguments: [ new acdl_1.Argument(new acdl_1.Name("actionName"), undefined, new acdl_1.NameReference(new acdl_1.Name(act.actionName))), ...(act.requestArgs ? [ new acdl_1.Argument(new acdl_1.Name("arguments"), undefined, new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaSchema.List), new acdl_1.ListLiteral(act.requestArgs.map((arg, index) => new acdl_1.ListItem(new acdl_1.PropertyReference(new acdl_1.Name(arg), new acdl_1.PropertyReference(new acdl_1.Name("arguments"), new acdl_1.NameReference(new acdl_1.Name(act.actionName)))), index))))), ] : []), ...(act.carryoverArguments ? [ new acdl_1.Argument(new acdl_1.Name("carryoverArguments"), undefined, new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaSchema.List), new acdl_1.ListLiteral(act.carryoverArguments.map((arg, index) => new acdl_1.ListItem(new acdl_1.NameReference(new acdl_1.Name(arg)), index))))), ] : []), ], includeType: true, actCallName: acdl_1.AlexaConversations.Offer, }; case "ConfirmArgs": case "Request": return { actionArguments: [ new acdl_1.Argument(new acdl_1.Name("arguments"), undefined, new acdl_1.Call(new acdl_1.Name(acdl_1.AlexaSchema.List), new acdl_1.ListLiteral(act.arguments.map((arg, index) => new acdl_1.ListItem(new acdl_1.PropertyReference(new acdl_1.Name(arg), new acdl_1.PropertyReference(new acdl_1.Name("arguments"), new acdl_1.NameReference(new acdl_1.Name(act.actionName)))), index))))), ], includeType: true, actCallName: acdl_1.AlexaConversationsNamespace + "." + act.type, }; default: throw new cli_data_format_error_1.CliDataFormatError("Unsupported dialog act" + (act.type ? ` ${act.type}` : "") + " in response. Consider checking your acolade file."); } }