├── .commitlintrc.json
├── .eslintignore
├── .eslintrc.json
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ └── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── unit-test.yml
├── .gitignore
├── .npmignore
├── .prettierrc
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── NOTICE
├── README.md
├── demo
├── Common
│ ├── index.ts
│ └── src
│ │ └── DemoRootControl.ts
├── ComponentModeAPL
│ └── src
│ │ ├── buildInteractionModel.ts
│ │ └── index.ts
├── ListControl
│ └── YesNoMaybe
│ │ ├── src
│ │ ├── buildInteractionModel.ts
│ │ ├── index.ts
│ │ └── interactionModelTypes.ts
│ │ └── test
│ │ └── listDemo1.spec.ts
├── MultiValueListControl
│ ├── src
│ │ ├── buildInteractionModel.ts
│ │ └── index.ts
│ └── test
│ │ └── multiValueListDemo.spec.ts
├── NumberControl
│ └── NumberDemo
│ │ ├── src
│ │ ├── buildInteractionModel.ts
│ │ └── index.ts
│ │ └── test
│ │ └── numberDemo.spec.ts
├── QuestionnaireControl
│ ├── src
│ │ ├── buildInteractionModel.ts
│ │ └── index.ts
│ └── test
│ │ └── questionnaireDemo.spec.ts
└── TwoLists
│ ├── src
│ ├── buildInteractionModel.ts
│ └── index.ts
│ └── test
│ └── multiListDemo1.spec.ts
├── doc
├── img
│ ├── fruitShopControlTree.png
│ ├── handlingChain.png
│ └── requestHandling.png
└── userGuide.md
├── ide
└── vscode
│ ├── launch.json
│ └── tasks.json
├── package-lock.json
├── package.json
├── src
├── commonControls
│ ├── DateControl.ts
│ ├── LanguageStrings.ts
│ ├── ValueControl.ts
│ ├── dateRangeControl
│ │ ├── DateHelper.ts
│ │ ├── DateRangeControl.ts
│ │ └── DateRangeNLUHelper.ts
│ ├── listControl
│ │ ├── ListControl.ts
│ │ └── ListControlAPL.ts
│ ├── multiValueListControl
│ │ ├── MultiValueListControl.ts
│ │ └── MultiValueListControlAPL.ts
│ ├── numberControl
│ │ ├── NumberControl.ts
│ │ ├── NumberControlAPL.ts
│ │ └── NumberControlBuiltIns.ts
│ └── questionnaireControl
│ │ ├── QuestionnaireControl.ts
│ │ ├── QuestionnaireControlBuiltIns.ts
│ │ ├── QuestionnaireControlStructs.ts
│ │ └── QuestionnaireControlSystemActs.ts
├── constants
│ └── Strings.ts
├── controls
│ ├── ComponentModeControlManager.ts
│ ├── ContainerControl.ts
│ ├── Control.ts
│ ├── ControlInput.ts
│ ├── ControlManager.ts
│ ├── ControlResult.ts
│ ├── ControlServices.ts
│ ├── DynamicContainerControl.ts
│ ├── Validation.ts
│ ├── interfaces
│ │ ├── IContainerControl.ts
│ │ ├── IControl.ts
│ │ ├── IControlInput.ts
│ │ ├── IControlManager.ts
│ │ ├── IControlResult.ts
│ │ ├── IControlResultBuilder.ts
│ │ ├── ILogger.ts
│ │ └── ILoggerFactory.ts
│ └── mixins
│ │ ├── ControlStateDiagramming.ts
│ │ └── InteractionModelContributor.ts
├── index.ts
├── intents
│ ├── AmazonBuiltInIntent.ts
│ ├── AmazonBuiltInSlotType.ts
│ ├── BaseControlIntent.ts
│ ├── ConjunctionControlIntent.ts
│ ├── DateRangeControlIntent.ts
│ ├── GeneralControlIntent.ts
│ ├── OrdinalControlIntent.ts
│ └── ValueControlIntent.ts
├── interactionModelGeneration
│ ├── ControlInteractionModelGenerator.ts
│ ├── InteractionModelGenerator.ts
│ └── ModelTypes.ts
├── intl
│ ├── EnglishGrammar.ts
│ └── ListFormat.ts
├── logging
│ ├── DefaultLogger.ts
│ └── DefaultLoggerFactory.ts
├── modality
│ └── ModalityEvaluation.ts
├── responseGeneration
│ ├── AplMode.ts
│ └── ControlResponseBuilder.ts
├── runtime
│ ├── ControlHandler.ts
│ └── SessionBehavior.ts
├── systemActs
│ ├── ContentActs.ts
│ ├── InitiativeActs.ts
│ ├── PayloadTypes.ts
│ └── SystemAct.ts
└── utils
│ ├── ArrayUtils.ts
│ ├── AssertionUtils.ts
│ ├── BasicTypes.ts
│ ├── ControlTreeVisualization.ts
│ ├── ControlUtils.ts
│ ├── ControlVisitor.ts
│ ├── DeepRequired.ts
│ ├── ErrorUtils.ts
│ ├── InputUtil.ts
│ ├── IntentUtils.ts
│ ├── Predicates.ts
│ ├── RequestUtils.ts
│ ├── ResponseUtils.ts
│ ├── SerializationValidator.ts
│ └── testSupport
│ ├── SkillInvoker.ts
│ ├── SkillWrapper.ts
│ └── TestingUtils.ts
├── test
├── CustomSerdeTests.spec.ts
├── commonControlTests
│ ├── DateControl.spec.ts
│ ├── DateRangeControl.spec.ts
│ ├── DynamicContainerControl.spec.ts
│ ├── ListControl.spec.ts
│ ├── MultiValueListControl.spec.ts
│ ├── NumberControl.spec.ts
│ ├── QuestionnaireControl.spec.ts
│ └── ValueControl.spec.ts
├── custom_policy.spec.ts
├── game_e2e.spec.ts
├── game_strings.ts
├── interactionModelGeneratorTests
│ ├── controlInteractionModelGenerator.spec.ts
│ ├── interactionModelForTest.ts
│ └── interactionModelGenerator.spec.ts
├── mock
│ └── inputInteractionModel.json
├── modality.spec.ts
├── rendering.spec.ts
├── scenarios.spec.ts
├── systemActs
│ └── ContentActs.spec.ts
└── utilsTest
│ ├── IntentUtils.spec.ts
│ ├── ResponseUtils.spec.ts
│ └── arrayUtils.spec.ts
└── tsconfig.json
/.commitlintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "body-leading-blank": [1, "always"],
4 | "footer-leading-blank": [1, "always"],
5 | "subject-empty": [2, "never"],
6 | "subject-full-stop": [2, "never", "."],
7 | "type-case": [2, "always", "lower-case"],
8 | "type-empty": [2, "never"],
9 | "type-enum": [
10 | 2,
11 | "always",
12 | [
13 | "chore",
14 | "chore_",
15 | "docs",
16 | "docs_",
17 | "feat",
18 | "feat_",
19 | "fix",
20 | "fix_",
21 | "perf",
22 | "perf_",
23 | "refactor",
24 | "refactor_",
25 | "revert",
26 | "style",
27 | "style_",
28 | "test",
29 | "test_",
30 | "wip",
31 | "wip_"
32 | ]
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | coverage
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "prettier/@typescript-eslint"
5 | //"plugin:@typescript-eslint/recommended"
6 | //"plugin:@typescript-eslint/recommended-requiring-type-checking" // TODO: fix errors and turn on.
7 | ],
8 | "parser": "@typescript-eslint/parser",
9 | "plugins": ["@typescript-eslint", "@typescript-eslint/eslint-plugin", "eslint-plugin-tsdoc"],
10 | "env": {
11 | "commonjs": true,
12 | "node": true,
13 | "mocha": true
14 | },
15 | "globals": {
16 | "Atomics": "readonly",
17 | "SharedArrayBuffer": "readonly"
18 | },
19 |
20 | "parserOptions": {
21 | "ecmaVersion": 2019,
22 | "sourceType": "module",
23 | "ecmaFeatures": {
24 | "modules": true,
25 | "impliedStrict": true
26 | },
27 |
28 | // NOTE: change this to an absolute path if viewing the code via a skill
29 | // codebase with files in /lambda. This fixes 'Can't open tsconfig.json'
30 | // errors
31 | "project": "tsconfig.json"
32 | },
33 | "rules": {
34 | // ---------------------------------------------------------
35 | // --- Rules from eslint:recommended that we override
36 | // ---------------------------------------------------------
37 | "no-empty": "warn",
38 | "no-unused-vars": "off", // because it flags types that are imported for type declarations (but otherwise unused).
39 | "no-undef": "off", // because if flags lack of import for Set. // TODO: fix the issues and remove this override.
40 |
41 | // ---------------------------------------------------------
42 | // --- Rules that we add and/or customize
43 | // ---------------------------------------------------------
44 |
45 | "eqeqeq": ["error", "smart"],
46 | "guard-for-in": "error",
47 | "id-blacklist": [
48 | "error",
49 | "any",
50 | "Number",
51 | "number",
52 | "String",
53 | "string",
54 | "Boolean",
55 | "boolean",
56 | "Undefined",
57 | "undefined"
58 | ],
59 | "id-match": "error",
60 | "import/no-extraneous-dependencies": ["off", { "devDependencies": false }],
61 | "no-caller": "error",
62 | "no-duplicate-imports": "error",
63 | "no-eval": "error",
64 | "no-extra-bind": "error",
65 | "no-new-func": "error",
66 | "no-new-wrappers": "error",
67 | "no-return-await": "error",
68 | "no-sequences": "error",
69 | "no-shadow": ["off", { "hoist": "all" }],
70 | "no-undef-init": "error",
71 | "no-var": "error",
72 | "object-shorthand": "error",
73 | "one-var": ["error", "never"],
74 | "prefer-const": "error",
75 | "prefer-object-spread": "error",
76 | "radix": "error",
77 |
78 | // -- Rules from @typescript-eslint
79 | "@typescript-eslint/strict-boolean-expressions": ["error"], // avoids frequent sources of errors: 0, "", Promise, null vs undefined
80 | "@typescript-eslint/adjacent-overload-signatures": "error",
81 | "@typescript-eslint/array-type": ["error", { "default": "array-simple" }],
82 | "@typescript-eslint/ban-types": [
83 | "error",
84 | {
85 | "types": {
86 | "Object": { "message": "Avoid using the `Object` type. Did you mean `object`?" },
87 | "Function": {
88 | "message": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`."
89 | },
90 | "Boolean": { "message": "Avoid using the `Boolean` type. Did you mean `boolean`?" },
91 | "Number": { "message": "Avoid using the `Number` type. Did you mean `number`?" },
92 | "String": { "message": "Avoid using the `String` type. Did you mean `string`?" },
93 | "Symbol": { "message": "Avoid using the `Symbol` type. Did you mean `symbol`?" }
94 | }
95 | }
96 | ],
97 | "@typescript-eslint/consistent-type-assertions": "error",
98 | "@typescript-eslint/consistent-type-definitions": "off",
99 | "@typescript-eslint/explicit-member-accessibility": ["off", { "accessibility": "explicit" }],
100 | "@typescript-eslint/interface-name-prefix": "off",
101 | "@typescript-eslint/member-ordering": "off",
102 | "@typescript-eslint/no-empty-function": "off",
103 | "@typescript-eslint/no-empty-interface": "off",
104 | "@typescript-eslint/no-explicit-any": "off",
105 | "@typescript-eslint/no-misused-new": "error",
106 | "@typescript-eslint/no-floating-promises": ["error"], // prevent those nasty forgotten promises !
107 | "@typescript-eslint/no-namespace": "off",
108 | "@typescript-eslint/no-parameter-properties": "off",
109 | "@typescript-eslint/no-this-alias": "error",
110 | "@typescript-eslint/no-unused-expressions": "off",
111 | "@typescript-eslint/no-use-before-define": "off",
112 | "@typescript-eslint/no-var-requires": "error",
113 | "@typescript-eslint/prefer-for-of": "error",
114 | "@typescript-eslint/prefer-function-type": "error",
115 | "@typescript-eslint/prefer-namespace-keyword": "error",
116 | "@typescript-eslint/triple-slash-reference": [
117 | "error",
118 | {
119 | "path": "always",
120 | "types": "prefer-import",
121 | "lib": "always"
122 | }
123 | ],
124 | "@typescript-eslint/type-annotation-spacing": "error",
125 | "@typescript-eslint/unified-signatures": "error",
126 |
127 | // -- Rules form tsdoc
128 | "tsdoc/syntax": "error"
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 |
12 |
13 | ## I'm submitting a...
14 |
15 |
16 | [ ] Regression (a behavior that used to work and stopped working in a new release)
17 | [ ] Bug report
18 | [ ] Performance issue
19 | [ ] Feature request
20 | [ ] Documentation issue or request
21 | [ ] Other... Please describe:
22 |
23 |
24 |
25 |
26 | ## Expected Behavior
27 |
28 |
29 |
30 | ## Current Behavior
31 |
32 |
33 |
34 |
35 |
36 | ## Possible Solution
37 |
38 |
39 |
40 | ## Steps to Reproduce (for bugs)
41 |
42 |
43 |
44 |
45 | ## Context
46 |
47 |
48 |
49 | ## Your Environment
50 |
51 | * ASK SDK Controls used: x.x.x
52 | * Operating System and version:
53 |
54 | ## Node.js and NPM Info
55 | * Node.js version used for development:
56 | * NPM version used for development:
57 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Description
4 |
5 |
6 | ## Motivation and Context
7 |
8 |
9 |
10 | ## Testing
11 |
12 |
13 |
14 |
15 | ## Screenshots (if appropriate)
16 |
17 | ## Types of changes
18 |
19 | - [ ] Bug fix (non-breaking change which fixes an issue)
20 | - [ ] New feature (non-breaking change which adds functionality)
21 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
22 | - [ ] Docs(Add new document content)
23 | - [ ] Translate Docs(Translate document content)
24 |
25 | ## Checklist
26 |
27 |
28 | - [ ] My code follows the code style of this project
29 | - [ ] My change requires a change to the documentation
30 | - [ ] I have updated the documentation accordingly
31 | - [ ] I have read the **README** document
32 | - [ ] I have added tests to cover my changes
33 | - [ ] All new and existing tests passed
34 | - [ ] My commit message follows [Conventional Commit Guideline](https://conventionalcommits.org/)
35 |
36 | ## License
37 | - [ ] By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
38 |
39 | [issues]: https://https://github.com/alexa/ask-sdk-controls/issues
40 | [license]: http://aws.amazon.com/apache2.0/
41 | [cla]: http://en.wikipedia.org/wiki/Contributor_License_Agreement
42 |
--------------------------------------------------------------------------------
/.github/workflows/unit-test.yml:
--------------------------------------------------------------------------------
1 | name: Linter, Formatting checks and unit Tests
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - 'doc/**'
7 |
8 | jobs:
9 | build:
10 | runs-on: ${{ matrix.os }}
11 | strategy:
12 | matrix:
13 | os: [ubuntu-latest, macos-latest, windows-latest]
14 | node: [12, 14, 16]
15 |
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Use Node.js ${{ matrix.node }}
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: ${{ matrix.node }}
22 | - run: npm install
23 | - run: npm run format-check
24 | - run: npm run lint
25 | - run: npm run test
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | .vscode
4 | .DS_Store
5 | .nycrc
6 | .nyc_output
7 | coverage/
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | doc/
3 | src/
4 | node_modules/
5 | test/
6 | package-lock.json
7 | tsconfig.json
8 | ide/
9 | .nyc_output
10 | .eslintrc.json
11 | .github/
12 | .prettierrc
13 | .eslintignore
14 | .commitlintrc.json
15 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 4,
4 | "semi": true,
5 | "printWidth": 110,
6 | "singleQuote": true
7 | }
8 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *develop* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Create a new branch on our repository from *develop*.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your branch using clear commit messages. We use [Conventional Commit Guideline](https://conventionalcommits.org/).
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 |
40 | ## Finding contributions to work on
41 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
42 |
43 |
44 | ## Code of Conduct
45 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
46 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
47 | opensource-codeofconduct@amazon.com with any additional questions or comments.
48 |
49 |
50 | ## Security issue notifications
51 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
52 |
53 |
54 | ## Licensing
55 |
56 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
57 |
58 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.
59 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | ASK SDK Controls Framework
2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
ASK SDK Controls Framework (Beta)
8 |
9 |
10 | The ASK SDK Controls framework builds on the [ASK SDK for Node.js](https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs), offering a scalable solution for creating large, multi-turn skills in code with reusable components called *controls*.
11 |
12 | ## Documentation
13 |
14 | - [About the Controls Framework](https://alexa.design/controls)
15 | - [User Guide](https://github.com/alexa/ask-sdk-controls/blob/develop/doc/userGuide.md)
16 | - [API Reference](http://ask-sdk-controls-typedoc.s3-website-us-east-1.amazonaws.com)
17 |
18 | ## Samples
19 |
20 | ### [Hello World](https://github.com/alexa/skill-sample-controls-hello-world)
21 |
22 | Sample that familiarizes you with the Controls framework, allowing you to hear a response from Alexa when you trigger the skill.
23 |
24 | ### [Fruit Shop](https://github.com/alexa/skill-sample-controls-fruit-shop)
25 |
26 | Build a multi-modal grocery shopping skill using custom and library controls for item lists, shopping cart management, and checkout.
27 |
28 | ## Opening Issues
29 |
30 | For bug reports, feature requests and questions, we would like to hear about it. Search the [existing issues](https://github.com/alexa/ask-sdk-controls/issues) and try to make sure your problem doesn’t already exist before opening a new issue. It’s helpful if you include the version of the SDK, Node.js or browser environment and OS you’re using. Please include a stack trace and reduced repro case when appropriate, too.
31 |
32 | ## License
33 |
34 | This SDK is distributed under the Apache License, Version 2.0, see LICENSE for more information.
35 |
36 | ## Got Feedback?
37 |
38 | * [Connect with us on Slack](https://amazonalexa.slack.com/archives/C018QMP3K33)!
39 | * Request and vote for Alexa features [here](https://alexa.uservoice.com/forums/906892-alexa-skills-developer-voice-and-vote/filters/top?category_id=322783)!
40 |
--------------------------------------------------------------------------------
/demo/Common/index.ts:
--------------------------------------------------------------------------------
1 | export { DemoRootControl as BasicRootControl, WelcomeAct } from './src/DemoRootControl';
2 |
--------------------------------------------------------------------------------
/demo/Common/src/DemoRootControl.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 | import { IntentRequest, interfaces } from 'ask-sdk-model';
14 | import {
15 | AmazonIntent,
16 | ContainerControl,
17 | ContainerControlProps,
18 | ContentAct,
19 | ControlInput,
20 | ControlResponseBuilder,
21 | ControlResultBuilder,
22 | InputUtil,
23 | LiteralContentAct,
24 | } from '../../../src';
25 |
26 | /**
27 | * Communicates a simple "welcome" message.
28 | */
29 | export class WelcomeAct extends ContentAct {
30 | render(input: ControlInput, responseBuilder: ControlResponseBuilder) {
31 | responseBuilder.addPromptFragment('Welcome.');
32 | }
33 | }
34 |
35 | /**
36 | * A container control that handles LaunchRequest and is thus suitable for use
37 | * as a basic root control.
38 | *
39 | * On launch, a welcome message is issued
40 | */
41 | export class DemoRootControl extends ContainerControl {
42 | handleFunc: (input: ControlInput, resultBuilder: ControlResultBuilder) => Promise;
43 | takeInitiativeFunc: (input: ControlInput, resultBuilder: ControlResultBuilder) => Promise;
44 |
45 | constructor(props: ContainerControlProps) {
46 | super({ id: props.id });
47 | }
48 |
49 | async canHandle(input: ControlInput) {
50 | // directly handle launch request
51 | if (InputUtil.isLaunchRequest(input)) {
52 | this.handleFunc = this.handleLaunch;
53 | return true;
54 | }
55 |
56 | if (InputUtil.isSessionEndedRequest(input)) {
57 | this.handleFunc = this.handleSessionEnded;
58 | return true;
59 | }
60 |
61 | // otherwise delegate to children
62 | if (await this.canHandleByChild(input)) {
63 | return true;
64 | }
65 |
66 | this.handleFunc = this.handleFallbackEtc;
67 | return true;
68 | }
69 |
70 | async handle(input: ControlInput, resultBuilder: ControlResultBuilder): Promise {
71 | if (this.handleFunc !== undefined) {
72 | await this.handleFunc(input, resultBuilder);
73 | return;
74 | }
75 | return this.handleByChild(input, resultBuilder);
76 | }
77 |
78 | async handleLaunch(input: ControlInput, resultBuilder: ControlResultBuilder) {
79 | resultBuilder.addAct(new WelcomeAct(this));
80 | }
81 |
82 | async handleSessionEnded(input: ControlInput, resultBuilder: ControlResultBuilder) {
83 | //nothing.
84 | }
85 |
86 | async handleFallbackEtc(input: ControlInput, resultBuilder: ControlResultBuilder) {
87 | let requestDescription;
88 | if (InputUtil.isIntent(input)) {
89 | requestDescription = (input.request as IntentRequest).intent.name;
90 | } else if (input.request.type === 'Alexa.Presentation.APL.UserEvent') {
91 | requestDescription = '';
92 | const event = input.request as interfaces.alexa.presentation.apl.UserEvent;
93 | const args = (event.arguments ?? []).join(', ');
94 | requestDescription = `APL UserEvent with params ${args}`;
95 | } else {
96 | requestDescription = 'Input of unknown type';
97 | }
98 |
99 | resultBuilder.addAct(
100 | new LiteralContentAct(this, {
101 | promptFragment: `${requestDescription} was not handled by any control.`,
102 | }),
103 | );
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/demo/ComponentModeAPL/src/buildInteractionModel.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { ComponentModeDemo } from '.';
15 | import { ControlServices } from '../../../src/controls/ControlServices';
16 | import { ControlInteractionModelGenerator } from '../../../src/interactionModelGeneration/ControlInteractionModelGenerator';
17 |
18 | const log = ControlServices.getLogger('ComponentModeDemo:InteractionModel');
19 |
20 | export namespace ComponentModeDemoIM {
21 | export const imGen = new ControlInteractionModelGenerator()
22 | .withInvocationName('controls demo')
23 | .addIntent({ name: 'AMAZON.StopIntent' })
24 | .addIntent({ name: 'AMAZON.NavigateHomeIntent' })
25 | .addIntent({ name: 'AMAZON.HelpIntent' })
26 | .addIntent({ name: 'AMAZON.CancelIntent' })
27 | .addIntent({ name: 'AMAZON.YesIntent' })
28 | .addIntent({ name: 'AMAZON.NoIntent' })
29 | .addIntent({ name: 'AMAZON.FallbackIntent' })
30 | .setModelConfiguration({ fallbackIntentSensitivity: { level: 'HIGH' } })
31 |
32 | .addOrMergeSlotType({
33 | name: 'target',
34 | values: [
35 | {
36 | id: 'age',
37 | name: {
38 | value: 'age',
39 | synonyms: ['my age'],
40 | },
41 | },
42 | {
43 | id: 'guests',
44 | name: {
45 | value: 'guests',
46 | synonyms: ['number of guests'],
47 | },
48 | },
49 | {
50 | id: 'theme',
51 | name: {
52 | value: 'theme',
53 | synonyms: ['party theme'],
54 | },
55 | },
56 | ],
57 | })
58 |
59 | .addOrMergeSlotType({
60 | name: 'PartyTheme',
61 | values: [
62 | {
63 | id: 'pirate',
64 | name: {
65 | value: 'pirate',
66 | synonyms: ['pirates'],
67 | },
68 | },
69 | {
70 | id: 'cartoon',
71 | name: {
72 | value: 'cartoon',
73 | synonyms: ['cartoons'],
74 | },
75 | },
76 | {
77 | id: 'fairy',
78 | name: {
79 | value: 'fairy',
80 | synonyms: ['fairies'],
81 | },
82 | },
83 | {
84 | id: 'monster',
85 | name: {
86 | value: 'monster',
87 | synonyms: ['monsters'],
88 | },
89 | },
90 | {
91 | id: 'animal',
92 | name: {
93 | value: 'animal',
94 | synonyms: ['animals'],
95 | },
96 | },
97 | ],
98 | })
99 | .buildCoreModelForControls(new ComponentModeDemo.DemoControlManager());
100 | }
101 |
102 | // If launched directly, build and write to a file
103 | if (require.main === module) {
104 | // Build and write
105 | ComponentModeDemoIM.imGen.buildAndWrite('en-US-generated.json');
106 | log.info('Wrote ./en-US-generated.json');
107 | }
108 |
--------------------------------------------------------------------------------
/demo/ListControl/YesNoMaybe/src/buildInteractionModel.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { ListDemo1 } from '.';
15 | import { ControlServices } from '../../../../src/controls/ControlServices';
16 | import { ControlInteractionModelGenerator } from '../../../../src/interactionModelGeneration/ControlInteractionModelGenerator';
17 | import { filteredYesNoMaybeSlotType, yesNoMaybeSlotType } from './interactionModelTypes';
18 |
19 | const log = ControlServices.getLogger('ComponentModeDemo:InteractionModel');
20 |
21 | export namespace ListDemo1IM {
22 | export const imGen = new ControlInteractionModelGenerator()
23 | .withInvocationName('controls demo')
24 | .addIntent({ name: 'AMAZON.StopIntent' })
25 | .addIntent({ name: 'AMAZON.NavigateHomeIntent' })
26 | .addIntent({ name: 'AMAZON.HelpIntent' })
27 | .addIntent({ name: 'AMAZON.CancelIntent' })
28 | .addIntent({ name: 'AMAZON.YesIntent' })
29 | .addIntent({ name: 'AMAZON.NoIntent' })
30 | .addIntent({ name: 'AMAZON.FallbackIntent' })
31 | .setModelConfiguration({ fallbackIntentSensitivity: { level: 'HIGH' } })
32 |
33 | // Add a custom intent
34 | .addIntent({
35 | name: 'HelloIntent',
36 | samples: ['Say hello', 'Say hi'],
37 | })
38 | .addOrMergeSlotType(yesNoMaybeSlotType)
39 | .addOrMergeSlotType(filteredYesNoMaybeSlotType)
40 | .buildCoreModelForControls(new ListDemo1.DemoControlManager());
41 | }
42 |
43 | // If launched directly, build and write to a file
44 | if (require.main === module) {
45 | // Build and write
46 | ListDemo1IM.imGen.buildAndWrite('en-US-generated.json');
47 | log.info('Wrote ./en-US-generated.json');
48 | }
49 |
--------------------------------------------------------------------------------
/demo/ListControl/YesNoMaybe/src/index.ts:
--------------------------------------------------------------------------------
1 | import { SkillBuilders } from 'ask-sdk-core';
2 | import { AmazonBuiltInSlotType, Strings } from '../../../../src';
3 | import { ListControl } from '../../../../src/commonControls/listControl/ListControl';
4 | import { Control } from '../../../../src/controls/Control';
5 | import { ControlManager } from '../../../../src/controls/ControlManager';
6 | import { ControlHandler } from '../../../../src/runtime/ControlHandler';
7 | import { defaultIntentToValueMapper } from '../../../../src/utils/IntentUtils';
8 | import { DemoRootControl } from '../../../Common/src/DemoRootControl';
9 | import { filteredYesNoMaybeSlotType, yesNoMaybeSlotType } from './interactionModelTypes';
10 |
11 | /**
12 | * Simple demonstration of a list control.
13 | *
14 | * root [RootControl]
15 | * - ListControl
16 | */
17 | export namespace ListDemo1 {
18 | export class DemoControlManager extends ControlManager {
19 | createControlTree(): Control {
20 | const rootControl = new DemoRootControl({ id: 'root' });
21 | rootControl.addChild(
22 | new ListControl({
23 | id: 'question',
24 | listItemIDs: ['yes', 'no', 'maybe'],
25 | slotType: yesNoMaybeSlotType.name!,
26 | confirmationRequired: true,
27 | interactionModel: {
28 | targets: [Strings.Target.It, Strings.Target.Choice],
29 | slotValueConflictExtensions: {
30 | filteredSlotType: filteredYesNoMaybeSlotType.name!,
31 | intentToValueMapper: (intent) => defaultIntentToValueMapper(intent),
32 | },
33 | },
34 | prompts: {
35 | valueSet: '', // TODO: if confirmation required, this should probably default to ''. Possible??
36 | },
37 | }),
38 | );
39 |
40 | return rootControl;
41 | }
42 | }
43 | }
44 |
45 | export const handler = SkillBuilders.custom()
46 | .addRequestHandlers(new ControlHandler(new ListDemo1.DemoControlManager()))
47 | .lambda();
48 |
--------------------------------------------------------------------------------
/demo/ListControl/YesNoMaybe/src/interactionModelTypes.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { v1 } from 'ask-smapi-model';
15 | import SlotType = v1.skill.interactionModel.SlotType;
16 |
17 | export const yesNoMaybeSlotType: SlotType = {
18 | name: 'YesNoMaybe',
19 | values: [
20 | {
21 | id: 'yes',
22 | name: {
23 | value: 'yes',
24 | synonyms: ['yep', 'correct', 'affirmative', 'yup', 'yeah'],
25 | },
26 | },
27 | {
28 | id: 'no',
29 | name: {
30 | value: 'no',
31 | synonyms: ['no', 'nope', 'no way'],
32 | },
33 | },
34 | {
35 | id: 'maybe',
36 | name: {
37 | value: 'maybe',
38 | synonyms: ['maybe', 'perhaps', "I haven't decided"],
39 | },
40 | },
41 | ],
42 | };
43 |
44 | export const filteredYesNoMaybeSlotType: SlotType = {
45 | name: 'Maybe',
46 | values: [
47 | {
48 | id: 'maybe',
49 | name: {
50 | value: 'maybe',
51 | synonyms: ['maybe', 'perhaps', "I haven't decided"],
52 | },
53 | },
54 | ],
55 | };
56 |
--------------------------------------------------------------------------------
/demo/ListControl/YesNoMaybe/test/listDemo1.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 | import { expect } from 'chai';
14 | import { suite, test } from 'mocha';
15 | import {
16 | ControlHandler,
17 | IntentBuilder,
18 | ValueControlIntent,
19 | SkillInvoker,
20 | TestInput,
21 | testTurn,
22 | waitForDebugger,
23 | } from '../../../../src';
24 | import { ListDemo1 } from '../src';
25 | import { ListDemo1IM } from '../src/buildInteractionModel';
26 |
27 | waitForDebugger();
28 |
29 | suite('List Demo', () => {
30 | test('YesNoMaybe List Demo - IMGen', async () => {
31 | const imData = ListDemo1IM.imGen.build();
32 | expect(imData.interactionModel?.languageModel?.intents?.find((x) => x.name === 'HelloIntent')).not
33 | .undefined;
34 |
35 | const yesNoMaybeValueControlIntent = imData.interactionModel?.languageModel?.intents?.find(
36 | (x) => x.name === 'YesNoMaybe_ValueControlIntent',
37 | );
38 | expect(yesNoMaybeValueControlIntent).not.undefined;
39 | expect(yesNoMaybeValueControlIntent?.samples?.includes('{YesNoMaybe}')).is.false;
40 | });
41 |
42 | test('YesNoMaybe List Demo - yes as value, then yes as confirmation', async () => {
43 | const requestHandler = new ControlHandler(new ListDemo1.DemoControlManager());
44 | const invoker = new SkillInvoker(requestHandler);
45 | await testTurn(
46 | invoker,
47 | 'U: __',
48 | TestInput.launchRequest(),
49 | 'A: Welcome. What is your selection? Some suggestions are yes, no or maybe.',
50 | );
51 |
52 | await testTurn(
53 | invoker,
54 | 'U: yes',
55 | TestInput.of(IntentBuilder.of('AMAZON.YesIntent')),
56 | 'A: Was that yes?',
57 | );
58 |
59 | await testTurn(invoker, 'U: yes', TestInput.of(IntentBuilder.of('AMAZON.YesIntent')), 'A: Great.');
60 | });
61 |
62 | test('YesNoMaybe List Demo - yes as value, no as disconfirmation, maybe as new value', async () => {
63 | const requestHandler = new ControlHandler(new ListDemo1.DemoControlManager());
64 | const invoker = new SkillInvoker(requestHandler);
65 | await testTurn(
66 | invoker,
67 | 'U: __',
68 | TestInput.launchRequest(),
69 | 'A: Welcome. What is your selection? Some suggestions are yes, no or maybe.',
70 | );
71 |
72 | await testTurn(
73 | invoker,
74 | 'U: yes',
75 | TestInput.of(IntentBuilder.of('AMAZON.YesIntent')),
76 | 'A: Was that yes?',
77 | );
78 |
79 | await testTurn(
80 | invoker,
81 | 'U: no',
82 | TestInput.of(IntentBuilder.of('AMAZON.NoIntent')),
83 | 'A: My mistake. What is your selection? Some suggestions are yes, no or maybe.',
84 | );
85 |
86 | await testTurn(
87 | invoker,
88 | 'U: maybe',
89 | TestInput.of(ValueControlIntent.of('YesNoMaybe', { Maybe: 'maybe' })),
90 | 'A: Was that maybe?',
91 | );
92 |
93 | await testTurn(invoker, 'U: yes', TestInput.of(IntentBuilder.of('AMAZON.YesIntent')), 'A: Great.');
94 | });
95 | });
96 |
--------------------------------------------------------------------------------
/demo/MultiValueListControl/src/buildInteractionModel.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { MultiValueListDemo } from '.';
15 | import { ControlInteractionModelGenerator } from '../../../src';
16 | import { ControlServices } from '../../../src/controls/ControlServices';
17 | import {
18 | filteredYesNoMaybeSlotType,
19 | yesNoMaybeSlotType,
20 | } from '../../ListControl/YesNoMaybe/src/interactionModelTypes';
21 |
22 | const log = ControlServices.getLogger('MultiValueListDemo:InteractionModel');
23 |
24 | export namespace MultiValueListDemoIM {
25 | export const imGen = new ControlInteractionModelGenerator()
26 | .withInvocationName('controls demo')
27 | .addIntent({ name: 'AMAZON.StopIntent' })
28 | .addIntent({ name: 'AMAZON.NavigateHomeIntent' })
29 | .addIntent({ name: 'AMAZON.HelpIntent' })
30 | .addIntent({ name: 'AMAZON.CancelIntent' })
31 | .addIntent({ name: 'AMAZON.YesIntent' })
32 | .addIntent({ name: 'AMAZON.NoIntent' })
33 | .addIntent({ name: 'AMAZON.FallbackIntent' })
34 | .setModelConfiguration({ fallbackIntentSensitivity: { level: 'HIGH' } })
35 | .addOrMergeSlotType(yesNoMaybeSlotType)
36 | .addOrMergeSlotType(filteredYesNoMaybeSlotType)
37 | .addOrMergeSlotType({
38 | name: 'GroceryItem',
39 | values: [
40 | {
41 | id: 'Milk',
42 | name: {
43 | value: 'Milk',
44 | synonyms: ['milk', 'almond milk', 'low fat milk'],
45 | },
46 | },
47 | {
48 | id: 'Eggs',
49 | name: {
50 | value: 'Eggs',
51 | synonyms: ['egg', 'eggs'],
52 | },
53 | },
54 | {
55 | id: 'Cereal',
56 | name: {
57 | value: 'Cereal',
58 | synonyms: ['cereal', 'oats', 'breakfast food'],
59 | },
60 | },
61 | {
62 | id: 'Bread',
63 | name: {
64 | value: 'Bread',
65 | synonyms: ['bread'],
66 | },
67 | },
68 | {
69 | id: 'Honey',
70 | name: {
71 | value: 'Honey',
72 | synonyms: ['honey', 'nectar'],
73 | },
74 | },
75 | ],
76 | })
77 | .buildCoreModelForControls(new MultiValueListDemo.DemoControlManager());
78 | }
79 |
80 | // If launched directly, build and write to a file
81 | if (require.main === module) {
82 | // Build and write
83 | MultiValueListDemoIM.imGen.buildAndWrite('en-US-generated.json');
84 | log.info('Wrote ./en-US-generated.json');
85 | }
86 |
--------------------------------------------------------------------------------
/demo/MultiValueListControl/src/index.ts:
--------------------------------------------------------------------------------
1 | import { SkillBuilders } from 'ask-sdk-core';
2 | import { Control } from '../../..//src/controls/Control';
3 | import {
4 | MultiValueListControl,
5 | MultiValueListStateValue,
6 | MultiValueValidationFailure,
7 | } from '../../../src/commonControls/multiValueListControl/MultiValueListControl';
8 | import { ControlManager } from '../../../src/controls/ControlManager';
9 | import { ControlHandler } from '../../../src/runtime/ControlHandler';
10 | import { DemoRootControl } from '../../Common/src/DemoRootControl';
11 |
12 | export namespace MultiValueListDemo {
13 | export class DemoControlManager extends ControlManager {
14 | createControlTree(): Control {
15 | const rootControl = new DemoRootControl({ id: 'root' });
16 |
17 | rootControl.addChild(
18 | new MultiValueListControl({
19 | id: 'myShoppingList',
20 | validation: validateItems,
21 | listItemIDs: getShoppingList,
22 | slotType: 'GroceryItem',
23 | confirmationRequired: true,
24 | prompts: {
25 | confirmValue: 'Is that all?',
26 | },
27 | }),
28 | );
29 |
30 | function getShoppingList() {
31 | return ['Milk', 'Eggs', 'Cereal', 'Bread'];
32 | }
33 |
34 | function validateItems(values: MultiValueListStateValue[]): true | MultiValueValidationFailure {
35 | const invalidValues = [];
36 | for (const item of values) {
37 | if (getShoppingList().includes(item.id) !== true) {
38 | invalidValues.push(item.id);
39 | }
40 | }
41 | if (invalidValues.length > 0) {
42 | return {
43 | invalidValues,
44 | renderedReason: 'item is not available in the shopping list',
45 | };
46 | }
47 | return true;
48 | }
49 |
50 | return rootControl;
51 | }
52 | }
53 | }
54 |
55 | export const handler = SkillBuilders.custom()
56 | .addRequestHandlers(new ControlHandler(new MultiValueListDemo.DemoControlManager()))
57 | .lambda();
58 |
--------------------------------------------------------------------------------
/demo/MultiValueListControl/test/multiValueListDemo.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 | import { expect } from 'chai';
14 | import { suite, test } from 'mocha';
15 | import {
16 | ControlHandler,
17 | GeneralControlIntent,
18 | IntentBuilder,
19 | SkillInvoker,
20 | TestInput,
21 | testTurn,
22 | waitForDebugger,
23 | } from '../../../src';
24 | import { Strings as $ } from '../../../src/constants/Strings';
25 | import { ValueControlIntent } from '../../../src/intents/ValueControlIntent';
26 | import { MultiValueListDemo } from '../src';
27 | import { MultiValueListDemoIM } from '../src/buildInteractionModel';
28 | waitForDebugger();
29 |
30 | suite('MultiValueList Demo', () => {
31 | test('GroceryItem MultiValueList Demo - IMGen', async () => {
32 | const imData = MultiValueListDemoIM.imGen.build();
33 | const groceryItemValueControlIntent = imData.interactionModel?.languageModel?.intents?.find(
34 | (x) => x.name === 'GroceryItem_ValueControlIntent',
35 | );
36 | expect(groceryItemValueControlIntent).not.undefined;
37 | expect(groceryItemValueControlIntent?.samples?.includes('{GroceryItem}')).is.true;
38 | });
39 |
40 | test('MultiValueList Demo - add multiple values', async () => {
41 | const requestHandler = new ControlHandler(new MultiValueListDemo.DemoControlManager());
42 | const invoker = new SkillInvoker(requestHandler);
43 | await testTurn(
44 | invoker,
45 | 'U: __',
46 | TestInput.launchRequest(),
47 | 'A: Welcome. What is your selection? Some suggestions are Milk, Eggs or Cereal.',
48 | );
49 |
50 | await testTurn(
51 | invoker,
52 | 'U: add Milk and Honey',
53 | TestInput.of(
54 | ValueControlIntent.of('GroceryItem', {
55 | GroceryItem: ['Milk', 'Honey'],
56 | action: $.Action.Add,
57 | }),
58 | ),
59 | "A: OK, added Milk. Sorry, Honey can't be added as item is not available in the shopping list. Is that all?",
60 | );
61 |
62 | await testTurn(invoker, 'U: yes', TestInput.of(IntentBuilder.of('AMAZON.YesIntent')), 'A: Great.');
63 | });
64 |
65 | test('MultiValueList Demo - add multiple values, remove all', async () => {
66 | const requestHandler = new ControlHandler(new MultiValueListDemo.DemoControlManager());
67 | const invoker = new SkillInvoker(requestHandler);
68 | await testTurn(
69 | invoker,
70 | 'U: __',
71 | TestInput.launchRequest(),
72 | 'A: Welcome. What is your selection? Some suggestions are Milk, Eggs or Cereal.',
73 | );
74 |
75 | await testTurn(
76 | invoker,
77 | 'U: add Bread, Eggs and Milk',
78 | TestInput.of(
79 | ValueControlIntent.of('GroceryItem', {
80 | GroceryItem: ['Bread', 'Eggs', 'Milk'],
81 | action: $.Action.Add,
82 | }),
83 | ),
84 | 'A: OK, added Bread, Eggs and Milk. Is that all?',
85 | );
86 |
87 | await testTurn(
88 | invoker,
89 | 'U: Clear all items',
90 | TestInput.of(GeneralControlIntent.of({ action: $.Action.Clear })),
91 | 'A: OK, cleared Bread, Eggs and Milk from the list. What is your selection? Some suggestions are Milk, Eggs or Cereal.',
92 | );
93 | });
94 | });
95 |
--------------------------------------------------------------------------------
/demo/NumberControl/NumberDemo/src/buildInteractionModel.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { BasicNumberDemo, CustomLoggerFactory } from '.';
15 | import { ControlServices } from '../../../../src/controls/ControlServices';
16 | import { ControlInteractionModelGenerator } from '../../../../src/interactionModelGeneration/ControlInteractionModelGenerator';
17 |
18 | const services = { logger: new CustomLoggerFactory() };
19 | ControlServices.setDefaults(services);
20 |
21 | const log = ControlServices.getLogger('NumberControlDemo:InteractionModel');
22 |
23 | export namespace BasicNumberDemoIM {
24 | export const imGen = new ControlInteractionModelGenerator()
25 | .withInvocationName('control demos')
26 | .addIntent({ name: 'AMAZON.StopIntent' })
27 | .addIntent({ name: 'AMAZON.NavigateHomeIntent' })
28 | .addIntent({ name: 'AMAZON.HelpIntent' })
29 | .addIntent({ name: 'AMAZON.CancelIntent' })
30 | .addIntent({ name: 'AMAZON.YesIntent' })
31 | .addIntent({ name: 'AMAZON.NoIntent' })
32 | .addIntent({ name: 'AMAZON.FallbackIntent' })
33 | .setModelConfiguration({ fallbackIntentSensitivity: { level: 'HIGH' } })
34 | .buildCoreModelForControls(new BasicNumberDemo.DemoControlManager());
35 | }
36 |
37 | // If launched directly, build and write to a file
38 | if (require.main === module) {
39 | // Build and write
40 | BasicNumberDemoIM.imGen.buildAndWrite('en-US-generated.json');
41 | log.info('Wrote ./en-US-generated.json');
42 | }
43 |
--------------------------------------------------------------------------------
/demo/NumberControl/NumberDemo/src/index.ts:
--------------------------------------------------------------------------------
1 | import { SkillBuilders } from 'ask-sdk-core';
2 | import Debug from 'debug';
3 | import { DefaultLogger } from '../../../../src';
4 | import { NumberControl } from '../../../../src/commonControls/numberControl/NumberControl';
5 | import { Control } from '../../../../src/controls/Control';
6 | import { ControlManager } from '../../../../src/controls/ControlManager';
7 | import { ControlServices } from '../../../../src/controls/ControlServices';
8 | import { ILogger } from '../../../../src/controls/interfaces/ILogger';
9 | import { ILoggerFactory } from '../../../../src/controls/interfaces/ILoggerFactory';
10 | import { ControlHandler } from '../../../../src/runtime/ControlHandler';
11 | import { DemoRootControl } from '../../../Common/src/DemoRootControl';
12 |
13 | export class CustomLoggerFactory implements ILoggerFactory {
14 | getLogger(namespace: string): ILogger {
15 | return new CustomLogger(namespace);
16 | }
17 | }
18 |
19 | export class CustomLogger extends DefaultLogger {
20 | debug(message: string): void {
21 | Debug(`debug:CustomLogger:${this.moduleName}`)(message);
22 | }
23 |
24 | info(message: string): void {
25 | Debug(`info:CustomLogger:${this.moduleName}`)(message);
26 | }
27 |
28 | sanitize(message: any): string {
29 | if (
30 | process.env.ASK_SDK_RESTRICTIVE_LOGGING !== undefined &&
31 | process.env.ASK_SDK_RESTRICTIVE_LOGGING === 'true'
32 | ) {
33 | return '###################';
34 | } else {
35 | return message;
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * Simple demonstration of a number control.
42 | *
43 | * root [RootControl]
44 | * - NumberControl
45 | */
46 | export namespace BasicNumberDemo {
47 | export class DemoControlManager extends ControlManager {
48 | createControlTree(): Control {
49 | const rootControl = new DemoRootControl({ id: 'root' });
50 | rootControl.addChild(
51 | new NumberControl({
52 | id: 'number',
53 | confirmationRequired: true,
54 | validation: [
55 | (state) => state.value! % 2 === 0 || { renderedReason: 'the value must be even' },
56 | ],
57 | apl: {
58 | validationFailedMessage: (value) =>
59 | `Sorry, ${value} is not valid, value must be even`,
60 | },
61 | }),
62 | );
63 | return rootControl;
64 | }
65 | }
66 | }
67 |
68 | const services = { logger: new CustomLoggerFactory() };
69 | ControlServices.setDefaults(services);
70 |
71 | export const handler = SkillBuilders.custom()
72 | .addRequestHandlers(new ControlHandler(new BasicNumberDemo.DemoControlManager()))
73 | .lambda();
74 |
--------------------------------------------------------------------------------
/demo/NumberControl/NumberDemo/test/numberDemo.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 | import { expect } from 'chai';
14 | import { suite, test } from 'mocha';
15 | import {
16 | AmazonBuiltInSlotType,
17 | AmazonIntent,
18 | ControlHandler,
19 | IntentBuilder,
20 | SkillInvoker,
21 | TestInput,
22 | testTurn,
23 | ValueControlIntent,
24 | waitForDebugger,
25 | } from '../../../../src';
26 | import { BasicNumberDemo } from '../src';
27 | import { BasicNumberDemoIM } from '../src/buildInteractionModel';
28 |
29 | waitForDebugger();
30 |
31 | suite('Number Demo', () => {
32 | test('Number Demo - IMGen', async () => {
33 | const imData = BasicNumberDemoIM.imGen.build();
34 |
35 | const numberValueControlIntent = imData.interactionModel?.languageModel?.intents?.find(
36 | (x) => x.name === 'AMAZON_NUMBER_ValueControlIntent',
37 | );
38 | expect(numberValueControlIntent).not.undefined;
39 | });
40 |
41 | test('Number Demo - give value, then confirm', async () => {
42 | const requestHandler = new ControlHandler(new BasicNumberDemo.DemoControlManager());
43 | const invoker = new SkillInvoker(requestHandler);
44 | await testTurn(invoker, 'U: __', TestInput.launchRequest(), 'A: Welcome. What number?');
45 |
46 | await testTurn(
47 | invoker,
48 | 'U: four',
49 | TestInput.of(ValueControlIntent.of('AMAZON.NUMBER', { 'AMAZON.NUMBER': '4' })),
50 | 'A: Was that 4?',
51 | );
52 |
53 | await testTurn(invoker, 'U: yes', TestInput.of(IntentBuilder.of('AMAZON.YesIntent')), 'A: Great.');
54 | });
55 |
56 | test('Number Demo - 14 as value, no as disconfirmation, resolve ambiguity to 40', async () => {
57 | const requestHandler = new ControlHandler(new BasicNumberDemo.DemoControlManager());
58 | const invoker = new SkillInvoker(requestHandler);
59 | await testTurn(invoker, 'U: __', TestInput.launchRequest(), 'A: Welcome. What number?');
60 |
61 | await testTurn(
62 | invoker,
63 | 'U: fourteen',
64 | TestInput.of(ValueControlIntent.of('AMAZON.NUMBER', { 'AMAZON.NUMBER': '14' })),
65 | 'A: Was that 14?',
66 | );
67 |
68 | await testTurn(
69 | invoker,
70 | 'U: no',
71 | TestInput.of(IntentBuilder.of('AMAZON.NoIntent')),
72 | 'A: My mistake. Did you perhaps mean 40?',
73 | );
74 |
75 | await testTurn(invoker, 'U: yes', TestInput.of(IntentBuilder.of('AMAZON.YesIntent')), 'A: Great.');
76 | });
77 |
78 | test('Number Demo - screen input, no confirmation', async () => {
79 | const requestHandler = new ControlHandler(new BasicNumberDemo.DemoControlManager());
80 | const invoker = new SkillInvoker(requestHandler);
81 | await testTurn(invoker, 'U: __', TestInput.launchRequest(), 'A: Welcome. What number?');
82 |
83 | await testTurn(
84 | invoker,
85 | 'U: __',
86 | TestInput.simpleUserEvent(['number', 40]),
87 | 'A: ', // No voice prompt for touch
88 | );
89 |
90 | expect(requestHandler.getSerializableControlStates().number.value).equals(40);
91 | expect(requestHandler.getSerializableControlStates().number.confirmed).equals(true);
92 | });
93 |
94 | test('Number Demo - screen input, invalid, new value valid', async () => {
95 | const requestHandler = new ControlHandler(new BasicNumberDemo.DemoControlManager());
96 | const invoker = new SkillInvoker(requestHandler);
97 | await testTurn(invoker, 'U: __', TestInput.launchRequest(), 'A: Welcome. What number?');
98 |
99 | await testTurn(
100 | invoker,
101 | 'U: __',
102 | TestInput.simpleUserEvent(['number', 3]),
103 | 'A: ', // No voice prompt for touch
104 | );
105 |
106 | expect(requestHandler.getSerializableControlStates().number.value).equals(3);
107 | expect(requestHandler.getSerializableControlStates().number.confirmed).equals(false);
108 |
109 | await testTurn(
110 | invoker,
111 | 'U: 4',
112 | TestInput.of(ValueControlIntent.of(AmazonBuiltInSlotType.NUMBER, { 'AMAZON.NUMBER': '4' })),
113 | 'A: Was that 4?',
114 | );
115 |
116 | await testTurn(
117 | invoker,
118 | 'U: Yes',
119 | TestInput.of(IntentBuilder.of(AmazonIntent.YesIntent)),
120 | 'A: Great.',
121 | );
122 | });
123 | });
124 |
--------------------------------------------------------------------------------
/demo/TwoLists/src/buildInteractionModel.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { MultipleLists } from '.';
15 | import { ControlServices } from '../../../src/controls/ControlServices';
16 | import { ControlInteractionModelGenerator } from '../../../src/interactionModelGeneration/ControlInteractionModelGenerator';
17 |
18 | const log = ControlServices.getLogger('HelloWorld:InteractionModel');
19 |
20 | export namespace TwoListsIM {
21 | export const imGen = new ControlInteractionModelGenerator()
22 | .withInvocationName('controls demo')
23 | .addIntent({ name: 'AMAZON.StopIntent' })
24 | .addIntent({ name: 'AMAZON.NavigateHomeIntent' })
25 | .addIntent({ name: 'AMAZON.HelpIntent' })
26 | .addIntent({ name: 'AMAZON.CancelIntent' })
27 | .addIntent({ name: 'AMAZON.YesIntent' })
28 | .addIntent({ name: 'AMAZON.NoIntent' })
29 | .addIntent({ name: 'AMAZON.FallbackIntent' })
30 | .setModelConfiguration({ fallbackIntentSensitivity: { level: 'HIGH' } })
31 |
32 | .addOrMergeSlotType({
33 | name: 'PetSpecies',
34 | values: [
35 | {
36 | id: 'cat',
37 | name: {
38 | value: 'cat',
39 | synonyms: ['kitty', 'house cat', 'feline'],
40 | },
41 | },
42 | {
43 | id: 'dog',
44 | name: {
45 | value: 'dog',
46 | synonyms: ['doggie', 'mutt', 'puppy'],
47 | },
48 | },
49 | {
50 | id: 'rabbit',
51 | name: {
52 | value: 'rabbit',
53 | synonyms: ['bunny', 'bunny rabbit'],
54 | },
55 | },
56 | ],
57 | })
58 |
59 | .addOrMergeSlotType({
60 | name: 'PetBreed',
61 | values: [
62 | {
63 | id: 'labrador',
64 | name: {
65 | value: 'labrador',
66 | synonyms: [],
67 | },
68 | },
69 | {
70 | id: 'persian',
71 | name: {
72 | value: 'persian',
73 | synonyms: [],
74 | },
75 | },
76 | ],
77 | })
78 |
79 | .addOrMergeSlotType({
80 | name: 'TransactionType',
81 | values: [
82 | {
83 | id: 'adopt',
84 | name: {
85 | value: 'adopt',
86 | synonyms: [],
87 | },
88 | },
89 | {
90 | id: 'foster',
91 | name: {
92 | value: 'foster',
93 | synonyms: [],
94 | },
95 | },
96 | {
97 | id: 'sponsor',
98 | name: {
99 | value: 'sponsor',
100 | synonyms: [],
101 | },
102 | },
103 | ],
104 | })
105 |
106 | .addOrMergeSlotType({
107 | name: 'target',
108 | values: [
109 | {
110 | id: 'species',
111 | name: { value: 'species', synonyms: ['type of pet'] },
112 | },
113 | {
114 | id: 'petKind',
115 | name: { value: 'petKind', synonyms: ['kind of pet', 'pet kind'] },
116 | },
117 | {
118 | id: 'breed',
119 | name: { value: 'breed', synonyms: ['type', 'variety'] },
120 | },
121 | {
122 | id: 'transaction',
123 | name: { value: 'transaction', synonyms: ['contract', 'agreement'] },
124 | },
125 | ],
126 | })
127 | .buildCoreModelForControls(new MultipleLists.DemoControlManager());
128 | }
129 |
130 | // If launched directly, build and write to a file
131 | if (require.main === module) {
132 | // Build and write
133 | TwoListsIM.imGen.buildAndWrite('en-US-generated.json');
134 | log.info('Wrote ./en-US-generated.json');
135 | }
136 |
--------------------------------------------------------------------------------
/demo/TwoLists/src/index.ts:
--------------------------------------------------------------------------------
1 | import { SkillBuilders } from 'ask-sdk-core';
2 | import { Control } from '../../..//src/controls/Control';
3 | import { ListControl } from '../../../src/commonControls/listControl/ListControl';
4 | import { Strings } from '../../../src/constants/Strings';
5 | import { ControlManager } from '../../../src/controls/ControlManager';
6 | import { ControlHandler } from '../../../src/runtime/ControlHandler';
7 | import { DemoRootControl } from '../../Common/src/DemoRootControl';
8 |
9 | export namespace MultipleLists {
10 | const breedControl = new ListControl({
11 | id: 'breed',
12 | listItemIDs: ['labrador', 'persian'],
13 | slotType: 'PetBreed',
14 | });
15 |
16 | export class DemoControlManager extends ControlManager {
17 | createControlTree(): Control {
18 | const rootControl = new DemoRootControl({ id: 'root' });
19 |
20 | rootControl.addChild(
21 | new ListControl({
22 | id: 'species',
23 | listItemIDs: ['cat', 'dog', 'rabbit'],
24 | slotType: 'PetSpecies',
25 | interactionModel: {
26 | targets: [Strings.Target.It, 'species', 'petKind'],
27 | },
28 | confirmationRequired(this: ListControl) {
29 | return this.state.value === 'rabbit';
30 | },
31 | }),
32 | );
33 |
34 | rootControl.addChild(breedControl);
35 |
36 | rootControl.addChild(
37 | new ListControl({
38 | id: 'transactionType',
39 | listItemIDs: ['adopt', 'foster', 'sponsor'],
40 | slotType: 'TransactionType',
41 | }),
42 | );
43 |
44 | return rootControl;
45 | }
46 | }
47 | }
48 |
49 | export const handler = SkillBuilders.custom()
50 | .addRequestHandlers(new ControlHandler(new MultipleLists.DemoControlManager()))
51 | .lambda();
52 |
--------------------------------------------------------------------------------
/demo/TwoLists/test/multiListDemo1.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 | import { suite, test } from 'mocha';
14 | import {
15 | ControlHandler,
16 | ValueControlIntent,
17 | SkillInvoker,
18 | TestInput,
19 | testTurn,
20 | waitForDebugger,
21 | } from '../../../src';
22 | import { MultipleLists } from '../src';
23 |
24 | waitForDebugger();
25 |
26 | suite('multiList Demo', () => {
27 | test('YesNoMaybe List Demo - yes as value, then yes as confirmation', async () => {
28 | const requestHandler = new ControlHandler(new MultipleLists.DemoControlManager());
29 | const invoker = new SkillInvoker(requestHandler);
30 | await testTurn(
31 | invoker,
32 | 'U: __',
33 | TestInput.launchRequest(),
34 | 'A: Welcome. What is your selection? Some suggestions are cat, dog or rabbit.',
35 | );
36 |
37 | await testTurn(
38 | invoker,
39 | 'U: cat',
40 | TestInput.of(ValueControlIntent.of('PetSpecies', { PetSpecies: 'cat' })),
41 | 'A: OK, cat. What is your selection? Some suggestions are labrador or persian.',
42 | );
43 |
44 | await testTurn(
45 | invoker,
46 | 'U: persian',
47 | TestInput.of(ValueControlIntent.of('PetBreed', { PetBreed: 'persian' })),
48 | 'A: OK, persian. What is your selection? Some suggestions are adopt, foster or sponsor.',
49 | );
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/doc/img/fruitShopControlTree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexa/ask-sdk-controls/b8dde93e0e93049d25c9e388541d8337cb630641/doc/img/fruitShopControlTree.png
--------------------------------------------------------------------------------
/doc/img/handlingChain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexa/ask-sdk-controls/b8dde93e0e93049d25c9e388541d8337cb630641/doc/img/handlingChain.png
--------------------------------------------------------------------------------
/doc/img/requestHandling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexa/ask-sdk-controls/b8dde93e0e93049d25c9e388541d8337cb630641/doc/img/requestHandling.png
--------------------------------------------------------------------------------
/ide/vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Run tests (INFO)",
11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
12 | "args": [
13 | "--timeout",
14 | "999999",
15 | "--colors",
16 |
17 | // TODO.
18 |
19 | // Slow startup
20 | "-r",
21 | "ts-node/register",
22 | "${workspaceFolder}/test/**/*.spec.ts",
23 |
24 | // Demo test cases
25 | "${workspaceFolder}/demo/**/test/**/*.spec.ts"
26 |
27 |
28 | // Fast startup.
29 | // "--require",
30 | // "source-map-support/register",
31 | // "${workspaceFolder}/dist/test/**/*.spec.js"
32 | ],
33 | "env": {
34 | // "DEBUG": "error:*, warn:*, info:*" // uncomment for logs
35 | // "ASK_SDK_CONTROLS_LOG_RESPONSE_FILEPATH": // uncomment to write out response for debugging
36 | //"ASK_SDK_RESTRICTIVE_LOGGING": "true" // uncomment to restrict sensitive information from logging such as slot values
37 | },
38 | // "smartStep": true,
39 | "skipFiles": [
40 | "node_modules/**",
41 | "/**"
42 | ],
43 | "console": "integratedTerminal"
44 |
45 | }
46 | ]
47 | }
--------------------------------------------------------------------------------
/ide/vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | // Activate via "Task: Run build task" (Ctrl-Shift-B)
7 | {
8 | "type": "npm",
9 | "script": "watch",
10 | "problemMatcher": [
11 | "$tsc-watch"
12 | ],
13 | "isBackground": true,
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ask-sdk-controls",
3 | "version": "0.9.2",
4 | "description": "Skill-building framework & library for Alexa SDK",
5 | "main": "dist/src/index.js",
6 | "types": "dist/src/index.d.ts",
7 | "scripts": {
8 | "build": "tsc && npm run copyTestSupportFiles && npm run lint",
9 | "copyTestSupportFiles": "[ -d dist/test/mock ] || mkdir dist/test/mock && cp test/mock/* dist/test/mock/",
10 | "watch": "tsc-watch --onSuccess 'npm run copyTestSupportFiles'",
11 | "lint": "eslint \"src/**/*.{ts,tsx}\" \"test/**/*.{ts,tsx}\" \"demo/**/*.{ts,tsx}\"",
12 | "format-check": "prettier --check \"src/**/*.{ts,tsx}\" \"test/**/*.{ts,tsx}\" \"demo/**/*.{ts,tsx}\"",
13 | "format-overwrite": "prettier -w \"src/**/*.{ts,tsx}\" \"test/**/*.{ts,tsx}\" \"demo/**/*.{ts,tsx}\"",
14 | "test": "cross-env TS_NODE_FILES=true mocha -r ts-node/register \"./test/**/*.spec.ts\" \"./demo/**/test/**/*.spec.ts\"",
15 | "test-all": "cross-env TS_NODE_FILES=true mocha --forbid-only -r ts-node/register \"./test/**/*.spec.ts\" \"./demo/**/test/**/*.spec.ts\"",
16 | "coverage": "nyc --cache npm run test-all && nyc report --reporter=html && echo HTML report generated at coverage/index.html",
17 | "clean": "rm -rf ./dist",
18 | "reinstall": "rm -rf ./node_modules && npm install",
19 | "rebuild": "npm run clean && npm run build",
20 | "docs": "rm -rf ./dist/api_docs && typedoc --out dist/api_docs src",
21 | "pre-release": "standard-version",
22 | "release": "npm run coverage && npm run lint && npm run format-check"
23 | },
24 | "dependencies": {
25 | "ask-sdk-core": "^2.12.1",
26 | "ask-sdk-local-debug": "^1.1.0",
27 | "ask-sdk-model": "^1.35.1",
28 | "ask-smapi-model": "^1.15.3",
29 | "chai": "^4.2.0",
30 | "debug": "^4.1.1",
31 | "i18next": "^19.9.2",
32 | "lodash": "^4.17.15",
33 | "tslib": "^2.0.0"
34 | },
35 | "peerDependencies": {
36 | "ask-sdk-core": "^2.12.1",
37 | "ask-sdk-local-debug": "^1.1.0",
38 | "ask-sdk-model": "^1.35.1",
39 | "ask-smapi-model": "^1.15.3",
40 | "chai": "^4.2.0",
41 | "debug": "^4.1.1",
42 | "i18next": "^19.9.2",
43 | "lodash": "^4.17.15",
44 | "tslib": "^2.0.0"
45 | },
46 | "devDependencies": {
47 | "@commitlint/cli": "^9.1.2",
48 | "@commitlint/config-conventional": "^12.1.4",
49 | "@types/chai": "^4.2.8",
50 | "@types/debug": "^4.1.5",
51 | "@types/lodash": "^4.14.149",
52 | "@types/mocha": "^5.2.7",
53 | "@types/node": "^10.17.14",
54 | "@types/sinon": "^7.5.2",
55 | "@typescript-eslint/eslint-plugin": "^3.10.1",
56 | "@typescript-eslint/parser": "^3.3.0",
57 | "cross-env": "^7.0.2",
58 | "eslint": "^7.2.0",
59 | "eslint-config-prettier": "^6.11.0",
60 | "eslint-plugin-tsdoc": "^0.2.5",
61 | "highlight.js": ">=10.4.1",
62 | "husky": "^4.2.5",
63 | "marked": "^4.0.12",
64 | "mocha": "^7.1.2",
65 | "nyc": "^15.0.1",
66 | "prettier": "^2.1.1",
67 | "sinon": "^9.0.1",
68 | "source-map-support": "^0.5.19",
69 | "standard-version": "^9.0.0",
70 | "ts-node": "^8.10.2",
71 | "tsc-watch": "^4.2.3",
72 | "typedoc": "^0.17.6",
73 | "typedoc-neo-theme": "^1.0.8",
74 | "typescript": "^3.9.10"
75 | },
76 | "husky": {
77 | "hooks": {
78 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS && npm run lint && npm run format-check",
79 | "pre-push": "npm run test-all"
80 | }
81 | },
82 | "standard-version": {
83 | "types": [
84 | {
85 | "type": "feat",
86 | "section": "Features"
87 | },
88 | {
89 | "type": "feat_",
90 | "section": "Feature work - not included in Changelog",
91 | "hidden": true
92 | },
93 | {
94 | "type": "fix",
95 | "section": "Bug Fixes"
96 | },
97 | {
98 | "type": "fix_",
99 | "section": "Bug Fixes - not included in Changelog",
100 | "hidden": true
101 | },
102 | {
103 | "type": "chore",
104 | "hidden": true
105 | },
106 | {
107 | "type": "docs",
108 | "section": "Docs"
109 | },
110 | {
111 | "type": "docs_",
112 | "section": "Doc work - not included in Changelog",
113 | "hidden": true
114 | },
115 | {
116 | "type": "style",
117 | "hidden": true
118 | },
119 | {
120 | "type": "refactor",
121 | "hidden": true
122 | },
123 | {
124 | "type": "perf",
125 | "hidden": true
126 | },
127 | {
128 | "type": "test",
129 | "hidden": true
130 | },
131 | {
132 | "type": "revert",
133 | "hidden": true
134 | }
135 | ]
136 | },
137 | "author": "Amazon Alexa Skills Kit SDK team",
138 | "repository": "github:alexa/ask-sdk-controls",
139 | "license": "Apache-2.0"
140 | }
141 |
--------------------------------------------------------------------------------
/src/commonControls/dateRangeControl/DateHelper.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | // transform date to yyyy-mm-dd dateToAlexaDateFormat
15 | export function dateToAlexaDateFormat(date: Date): string {
16 | const mm = date.getMonth() + 1;
17 | const mmString: string = mm >= 10 ? `${mm}` : `0${mm}`;
18 | const dd = date.getDate();
19 | const ddString: string = dd >= 10 ? `${dd}` : `0${dd}`;
20 | return `${date.getFullYear()}-${mmString}-${ddString}`;
21 | }
22 |
23 | // transform yyyy-mm-dd to Date
24 | // TODO: Handle all ALEXA.DATE format such as '2015-W49-WE'
25 | export function alexaDateFormatToDate(date: string): Date {
26 | return new Date(date);
27 | }
28 |
29 | // Given a specific month and year, find the last date of that month
30 | // E.G. given (2, 2020) returns 28
31 | export function getDaysInMonth(m: number, y: number) {
32 | return new Date(y, m - 1, 0).getDate();
33 | }
34 |
35 | /**
36 | * Find Start / End date of the input date range
37 | * @param date - Alexa date format string input E.G. 2019 / 2019-04 / 2019-04-01
38 | * @param start - Flag to determine if the output should be the start / end of the input date range
39 | */
40 | // TODO: Handle all ALEXA.DATE format such as '2015-W49-WE'
41 | export function findEdgeDateOfDateRange(date: string, start: boolean): string {
42 | const yy = getYear(date);
43 | const mm = date.charAt(4) === '-' ? getMonth(date) : undefined;
44 | const dd = date.charAt(7) === '-' ? getDay(date) : undefined;
45 | if (mm === undefined) {
46 | return start ? `${yy}-01-01` : `${yy}-12-31`;
47 | } else if (dd === undefined) {
48 | const mmString: string = mm < 10 ? `0${mm}` : `${mm}`;
49 | return start ? `${yy}-${mmString}-01` : `${yy}-${mmString}-${getDaysInMonth(mm, yy)}`;
50 | }
51 | return date;
52 | }
53 |
54 | export function getYear(date: string): number {
55 | const yy = parseInt(date.substring(0, 4), 10);
56 | return yy;
57 | }
58 |
59 | export function getMonth(date: string): number {
60 | const mm = parseInt(date.substring(5, 7), 10);
61 | return mm;
62 | }
63 |
64 | export function getDay(date: string): number {
65 | const dd = parseInt(date.substring(8, 10), 10);
66 | return dd;
67 | }
68 |
69 | export function getEndDateOfRange(date: string): Date {
70 | return alexaDateFormatToDate(findEdgeDateOfDateRange(date, false));
71 | }
72 |
73 | export function getStartDateOfRange(date: string): Date {
74 | return alexaDateFormatToDate(findEdgeDateOfDateRange(date, true));
75 | }
76 |
77 | export function getUTCDate(date: Date): Date {
78 | // Get the offset between local time zone and UTC in minutes
79 | const offset: number = date.getTimezoneOffset();
80 | const utcTime = date.getTime() + offset * 60000;
81 |
82 | return new Date(utcTime);
83 | }
84 |
--------------------------------------------------------------------------------
/src/commonControls/numberControl/NumberControlBuiltIns.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import i18next from 'i18next';
15 | import { ControlInput } from '../..';
16 | import { NumberControlState } from './NumberControl';
17 |
18 | export namespace NumberControlBuiltIns {
19 | export function defaultValidationFailureText(): (value?: number) => string {
20 | return (value?: number) => i18next.t('NUMBER_CONTROL_DEFAULT_APL_INVALID_VALUE', { value });
21 | }
22 |
23 | export const confirmMostLikelyMisunderstandingInputs = (state: NumberControlState, input: ControlInput) =>
24 | defaultMostLikelyMisunderstandingFunc(state.value) !== undefined;
25 |
26 | export function defaultMostLikelyMisunderstandingFunc(value: number): number | undefined {
27 | // Static map of common ambiguous pairs among integers misinterpreted by NLU.
28 | const ambiguousPairs: { [key: number]: number } = {
29 | 13: 30,
30 | 14: 40,
31 | 15: 50,
32 | 16: 60,
33 | 17: 70,
34 | 18: 80,
35 | 19: 90,
36 | 30: 13,
37 | 40: 14,
38 | 50: 15,
39 | 60: 16,
40 | 70: 17,
41 | 80: 18,
42 | 90: 19,
43 | 113: 130,
44 | 114: 140,
45 | 115: 150,
46 | 116: 160,
47 | 117: 170,
48 | 118: 180,
49 | 119: 190,
50 | 130: 113,
51 | 140: 114,
52 | 150: 115,
53 | 160: 160,
54 | 170: 170,
55 | 180: 118,
56 | 190: 119,
57 | };
58 | return ambiguousPairs[value];
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/constants/Strings.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * The strings associated with various built-ins
16 | */
17 | export namespace Strings {
18 | /**
19 | * Built-in feedback slot values
20 | */
21 | export enum Feedback {
22 | Affirm = 'builtin_affirm',
23 | Disaffirm = 'builtin_disaffirm',
24 | }
25 |
26 | /**
27 | * Built-in action slot values
28 | */
29 | export enum Action {
30 | Set = 'builtin_set',
31 | Change = 'builtin_change',
32 | Select = 'builtin_select',
33 | Start = 'builtin_start',
34 | Restart = 'builtin_restart',
35 | Resume = 'builtin_resume',
36 | GoBack = 'builtin_goBack',
37 | Complete = 'builtin_complete',
38 | None = 'builtin_none',
39 | Add = 'builtin_add',
40 | Remove = 'builtin_remove',
41 | Delete = 'builtin_delete',
42 | Ignore = 'builtin_ignore',
43 | Replace = 'builtin_replace',
44 | Clear = 'builtin_clear',
45 | }
46 |
47 | /**
48 | * Built-in target slot values
49 | */
50 | export enum Target {
51 | It = 'builtin_it',
52 | Start = 'builtin_start',
53 | End = 'builtin_end',
54 | Choice = 'builtin_choice',
55 | // eslint-disable-next-line id-blacklist
56 | Number = 'builtin_number',
57 | Date = 'builtin_date',
58 | StartDate = 'builtin_start_date',
59 | EndDate = 'builtin_end_date',
60 | DateRange = 'builtin_date_range',
61 | Questionnaire = 'builtin_questionnaire',
62 | }
63 |
64 | export const Conjunction = 'builtin_conjunction';
65 | export const Tail = 'builtin_tail';
66 | export const Head = 'builtin_head';
67 | export const Preposition = 'builtin_preposition';
68 | }
69 |
--------------------------------------------------------------------------------
/src/controls/ComponentModeControlManager.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { IControlManager } from '..';
15 | import { APLMode } from '../responseGeneration/AplMode';
16 | import { ControlResponseBuilder } from '../responseGeneration/ControlResponseBuilder';
17 | import { ControlInput } from './ControlInput';
18 | import { ControlManager, ControlManagerProps, renderActsInSequence } from './ControlManager';
19 | import { ControlResult } from './ControlResult';
20 | import { ControlServices } from './ControlServices';
21 |
22 | const MODULE_NAME = 'AskSdkControls:ComponentModeControlManager';
23 |
24 | /**
25 | * ControlManager used to render APL in Component Mode.
26 | */
27 | export abstract class ComponentModeControlManager extends ControlManager implements IControlManager {
28 | constructor(props?: ControlManagerProps) {
29 | super(props);
30 | this.log = ControlServices.getLogger(MODULE_NAME);
31 | }
32 |
33 | async render(
34 | result: ControlResult,
35 | input: ControlInput,
36 | controlResponseBuilder: ControlResponseBuilder,
37 | ): Promise {
38 | // set the APL mode to Component
39 | // Required to disable addRenderAPLDirective on control.renderActs()
40 | controlResponseBuilder.aplMode = APLMode.COMPONENT;
41 |
42 | await renderActsInSequence(result.acts, input, controlResponseBuilder);
43 | await this.renderAPL(result, input, controlResponseBuilder);
44 | }
45 |
46 | /**
47 | * Render an APL template.
48 | *
49 | * @param input - Input
50 | * @param controlResponseBuilder - Response builder
51 | */
52 | abstract renderAPL(
53 | result: ControlResult,
54 | input: ControlInput,
55 | controlResponseBuilder: ControlResponseBuilder,
56 | ): Promise;
57 | }
58 |
--------------------------------------------------------------------------------
/src/controls/ControlInput.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { HandlerInput } from 'ask-sdk-core';
15 | import { Request } from 'ask-sdk-model';
16 | import _ from 'lodash';
17 | import { IControlInput } from './interfaces/IControlInput';
18 | import { IControl } from './interfaces/IControl';
19 | import { InputModality, ResponseStyle } from '../modality/ModalityEvaluation';
20 |
21 | /**
22 | * Properties for creating a ControlInput instance.
23 | */
24 | export interface ControlInputProps {
25 | /**
26 | * The core input for the request.
27 | */
28 | handlerInput: HandlerInput;
29 |
30 | /**
31 | * The turn number of the current request.
32 | */
33 | turnNumber: number;
34 |
35 | /**
36 | * A map of Control IDs to Controls for the current control tree.
37 | */
38 | controlMap: { [index: string]: IControl };
39 |
40 | /**
41 | * The style in which the skill is recommended to respond to the current request,
42 | * e.g. with a screen or voice interface, based on how the user responded to
43 | * previous requests.
44 | */
45 | suggestedResponseStyle: ResponseStyle;
46 |
47 | /**
48 | * The history of how the user responded to previous requests,
49 | * e.g. with touch or voice.
50 | */
51 | inputModalityHistory: InputModality[];
52 | }
53 |
54 | /**
55 | * Defines an expanded input object passed around during processing by Controls.
56 | *
57 | * Purpose:
58 | * * Provides access to the HandlerInput and also conveniences such as simplified access
59 | * to the Request object, a turn counter and a map of all controls in the control tree.
60 | */
61 | export class ControlInput implements IControlInput {
62 | /**
63 | * The input from {@link CustomSkillRequestHandler}
64 | */
65 | readonly handlerInput: HandlerInput;
66 |
67 | /**
68 | * The request object
69 | *
70 | * This is a convenience for `handlerInput.requestEnvelope.request`.
71 | */
72 | readonly request: Request;
73 |
74 | /**
75 | * The number of incoming requests during the user session.
76 | */
77 | readonly turnNumber: number;
78 |
79 | /**
80 | * The style in which the skill is recommended to respond to the request,
81 | * e.g. with a screen or voice modality.
82 | */
83 | readonly suggestedResponseStyle: ResponseStyle;
84 |
85 | /**
86 | * The history of what modality the user responded with for each turn,
87 | * e.g. voice or touch.
88 | */
89 | readonly inputModalityHistory: InputModality[];
90 |
91 | /**
92 | * All the controls of the control tree, indexed by controlID.
93 | *
94 | * Usage:
95 | * * This provides direct access to all other controls in the control tree which
96 | * can be convenient for occasional use but it increases the coupling of specific controls.
97 | * * When controls are close to one another, it is preferable to have their parents
98 | * coordinate data transfer, e.g. by get() from one and set() on the other.
99 | * * If controls that are not close to one another routinely need to share information
100 | * it would be best to create an external datastore. Consider Redux or similar solutions.
101 | */
102 | readonly controls: { [index: string]: IControl };
103 |
104 | constructor(props: ControlInputProps) {
105 | this.handlerInput = props.handlerInput;
106 | this.request = this.handlerInput.requestEnvelope.request;
107 | this.turnNumber = props.turnNumber;
108 | this.controls = props.controlMap;
109 | this.suggestedResponseStyle = props.suggestedResponseStyle;
110 | this.inputModalityHistory = props.inputModalityHistory;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/controls/ControlResult.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { SessionBehavior } from '../runtime/SessionBehavior';
15 | import { ISystemAct, SystemAct } from '../systemActs/SystemAct';
16 | import { IControlResult } from './interfaces/IControlResult';
17 |
18 | /**
19 | * Represents "what" the system should communicate to the user.
20 | *
21 | * This is the output object from the handle and initiative phases and is the
22 | * intermediate representation passed to the render phase.
23 | *
24 | * If the session is not being closed, the ControlResult must contain exactly
25 | * one `InitiativeAct`.
26 | */
27 | export class ControlResult implements IControlResult {
28 | acts: SystemAct[];
29 | sessionBehavior: SessionBehavior;
30 |
31 | // jsDoc - see class jsDoc.
32 | constructor(acts: SystemAct[], sessionBehavior: SessionBehavior) {
33 | this.acts = acts;
34 | this.sessionBehavior = sessionBehavior;
35 | }
36 |
37 | // jsDoc - see Object.
38 | toString(): string {
39 | return stringifyItems(this.acts);
40 | }
41 |
42 | /**
43 | * Determines if the result has an initiative act.
44 | */
45 | hasInitiativeAct(): boolean {
46 | return controlResultHasInitiativeAct(this);
47 | }
48 | }
49 |
50 | /**
51 | * Builder that creates a ControlResult.
52 | */
53 | export class ControlResultBuilder {
54 | acts: SystemAct[];
55 | sessionBehavior: SessionBehavior;
56 |
57 | constructor(acts?: SystemAct[]) {
58 | this.acts = acts ?? [];
59 | this.sessionBehavior = SessionBehavior.OPEN;
60 | }
61 |
62 | build(): ControlResult {
63 | return new ControlResult(this.acts, this.sessionBehavior);
64 | }
65 |
66 | /**
67 | * Add a system act.
68 | *
69 | * Usage:
70 | * * If the session needs to be ended, also call `resultBuilder.endSession()`.
71 | *
72 | * @param act - System act.
73 | */
74 | addAct(act: SystemAct): this {
75 | if (this.hasInitiativeAct() && act.takesInitiative) {
76 | throw new Error(
77 | `Result already contains an initiative item. (If handle() produced an initiative item and takeInitiative also ran, a common issue is missing 'await').` +
78 | `Trying to add ${act.toString()}. Existing items: ${stringifyItems(this.acts)}`,
79 | );
80 | }
81 | this.acts.push(act);
82 | return this;
83 | }
84 |
85 | /**
86 | * End the user session.
87 | *
88 | * See
89 | * https://developer.amazon.com/en-GB/docs/alexa/custom-skills/manage-skill-session-and-session-attributes.html#session-lifecycle
90 | *
91 | * Framework behavior:
92 | * * this will cause the framework to call
93 | * `ask-core.ResponseBuilder.withShouldEndSession(true)`.
94 | *
95 | * Skill/device behavior:
96 | * * Subsequent utterances will not be routed to the skill.
97 | * * A new launch command will start a fresh session.
98 | *
99 | */
100 | endSession(): void {
101 | this.sessionBehavior = SessionBehavior.END;
102 | }
103 |
104 | /**
105 | * Place the user session in the idle state.
106 | *
107 | * https://developer.amazon.com/en-GB/docs/alexa/custom-skills/manage-skill-session-and-session-attributes.html#session-lifecycle
108 | *
109 | * * Framework behavior:
110 | * * this will cause the framework to call
111 | * `ask-core.ResponseBuilder.withShouldEndSession(undefined)`.
112 | *
113 | * Skill/device behavior:
114 | * * The session remains alive but the microphone is closed.
115 | * * The user can interact with the skill but must use the wake-word.
116 | */
117 | enterIdleState(): void {
118 | this.sessionBehavior = SessionBehavior.IDLE;
119 | }
120 |
121 | // jsDoc - see Object
122 | toString(): string {
123 | return stringifyItems(this.acts);
124 | }
125 |
126 | /**
127 | * Determines if the result has an initiative act.
128 | */
129 | hasInitiativeAct(): boolean {
130 | return controlResultHasInitiativeAct(this);
131 | }
132 | }
133 |
134 | function controlResultHasInitiativeAct(result: IControlResult): boolean {
135 | return result.acts.find((item) => item.takesInitiative) !== undefined;
136 | }
137 |
138 | function stringifyItems(items: ISystemAct[]) {
139 | const itemStrs: string[] = [];
140 | for (const item of items) {
141 | itemStrs.push(item.toString());
142 | }
143 | return `[${itemStrs.join(', ')}]`;
144 | }
145 |
--------------------------------------------------------------------------------
/src/controls/ControlServices.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { DefaultLoggerFactory } from '../logging/DefaultLoggerFactory';
15 | import { ILogger } from './interfaces/ILogger';
16 | import { ILoggerFactory } from './interfaces/ILoggerFactory';
17 |
18 | /**
19 | * Defines the props of Control services.
20 | */
21 | export interface ControlServicesProps {
22 | /**
23 | * Logger instance used across controls.
24 | */
25 | logger: ILoggerFactory;
26 | }
27 |
28 | /**
29 | * Defines the ControlService used to customize logger implementation used
30 | * across the framework.
31 | */
32 | export namespace ControlServices {
33 | /**
34 | * Provides default services
35 | */
36 | let defaults: ControlServicesProps;
37 |
38 | /**
39 | * Function to override the default Control service props.
40 | *
41 | * @param customServices - Input custom controls services
42 | */
43 | export function setDefaults(customServices: ControlServicesProps): void {
44 | defaults = customServices;
45 | }
46 |
47 | /**
48 | * Create a default control service object and set it to defaults
49 | * which includes a default logger factory.
50 | */
51 | class DefaultControlServices {
52 | services: ControlServicesProps;
53 |
54 | constructor() {
55 | this.services = {
56 | logger: new DefaultLoggerFactory(),
57 | };
58 | }
59 | }
60 |
61 | /**
62 | * Function to retrieve Control services.
63 | *
64 | * @returns Control Service
65 | */
66 | export function getDefaults(): ControlServicesProps {
67 | if (defaults !== undefined) {
68 | return defaults;
69 | }
70 | const defaultServices = new DefaultControlServices();
71 | return defaultServices.services;
72 | }
73 |
74 | /**
75 | * Function to retrieve logger implementation with a provided
76 | * namespace.
77 | *
78 | * Usage:
79 | * - ControlServices.getLogger('AskSdkControls:ControlManager');
80 | * returns a logger implementation containing methods to log
81 | * info/debug/error and sanitize restrictive information.
82 | *
83 | *
84 | * @param namespace - Input Module Name
85 | */
86 | export function getLogger(namespace: string): ILogger {
87 | if (namespace !== undefined) {
88 | return ControlServices.getDefaults().logger.getLogger(namespace);
89 | }
90 | throw new Error('Invalid Namespace provided');
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/controls/Validation.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { ControlInput } from './ControlInput';
15 |
16 | /**
17 | * State validation function
18 | *
19 | * Takes a Control state object and returns `true` if the state passes validation. If
20 | * validation fails, a `ValidationResult` object is returned instead.
21 | */
22 | export type StateValidationFunction = (
23 | state: TState,
24 | input: ControlInput,
25 | ) => true | ValidationFailure | Promise;
26 |
27 | /**
28 | * Describes a validation failure.
29 | *
30 | * Usage:
31 | * - A reason code should be provided that uniquely describes the kind of validation
32 | * failure. A reason code is useful for context-specific rendering and other business
33 | * function. It is not mandatory as for some simple cases it may be duplicative to
34 | * provide both a reason code and a single rendering of that reason code.
35 | *
36 | * - A rendered reason for situations where the rendered form is not context-sensitive and
37 | * can be conveniently provided when the ValidationFailure is instantiated.
38 | */
39 | export type ValidationFailure = {
40 | /**
41 | * A code representing what validation failed.
42 | *
43 | * Usage:
44 | * - use reasonCode for business logic and transform to a prompt during the rendering
45 | * phase.
46 | */
47 | reasonCode?: string;
48 |
49 | /**
50 | * A rendered prompt fragment that can be directly included in the `Response`.
51 | *
52 | * Usage:
53 | * - If convenient, generate the prompt fragment at instantiation time.
54 | * - A renderedReason should not be used in logic or further transformed.
55 | */
56 | renderedReason?: string;
57 | };
58 |
59 | /**
60 | * Helper to evaluate a prop that accepts one or more StateValidationFunction functions.
61 | * @param validationProp - either a single StateValidationFunction or an array of
62 | * the same.
63 | * @param state - The control's state object.
64 | * @param input - ControlInput.
65 | */
66 | export async function evaluateValidationProp(
67 | validationProp: StateValidationFunction | Array>,
68 | state: TState,
69 | input: ControlInput,
70 | ): Promise {
71 | const listOfValidationFunc: Array> =
72 | typeof validationProp === 'function' ? [validationProp] : validationProp;
73 | for (const validationFunction of listOfValidationFunc) {
74 | const validationResult: true | ValidationFailure = await validationFunction(state, input);
75 | if (validationResult !== true) {
76 | return validationResult;
77 | }
78 | }
79 | return true;
80 | }
81 |
--------------------------------------------------------------------------------
/src/controls/interfaces/IContainerControl.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { IControl } from './IControl';
15 |
16 | /**
17 | * Defines a Control that has child controls.
18 | *
19 | * This is the minimal definition required by the Runtime (ControlHandler)
20 | * See `ContainerControl` for the actual class used by Control implementations.
21 | */
22 | export interface IContainerControl extends IControl {
23 | children: IControl[];
24 | }
25 |
26 | /**
27 | * Type-guard function for the @see IContainerControl interface.
28 | *
29 | * @param control - Control to test
30 | * @returns true if the argument implements the @see ContainerControl interface.
31 | */
32 | export function isContainerControl(control: IControl): control is IContainerControl {
33 | return (control as any).children instanceof Array;
34 | }
35 |
--------------------------------------------------------------------------------
/src/controls/interfaces/IControl.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { IControlInput } from './IControlInput';
15 | import { IControlResultBuilder } from './IControlResultBuilder';
16 |
17 | /**
18 | * Defines a Control object that manages state and dialog behavior.
19 | *
20 | * This is the minimal definition required by the Runtime (ControlHandler)
21 | * See `Control` for the actual class used by Control implementations.
22 | */
23 | export interface IControl {
24 | id: string;
25 |
26 | /**
27 | * Determines if the Control or one of its children can consume the request.
28 | */
29 | canHandle(input: IControlInput): boolean | Promise;
30 |
31 | /**
32 | * Handles the request.
33 | */
34 | handle(input: IControlInput, resultBuilder: IControlResultBuilder): void | Promise;
35 |
36 | /**
37 | * Determines if the Control can take the initiative.
38 | */
39 | canTakeInitiative(input: IControlInput): boolean | Promise;
40 |
41 | /**
42 | * Takes the initiative by adding an InitiativeAct to the result.
43 | */
44 | takeInitiative(input: IControlInput, resultBuilder: IControlResultBuilder): void | Promise;
45 |
46 | /** TODO */
47 | reestablishState(state: any, controlStateMap: { [index: string]: any }): void;
48 |
49 | /**
50 | * Gets the Control's state as an object that is serializable.
51 | *
52 | * Framework behavior:
53 | * - The object will be serialized via a call to `JSON.stringify(obj)`
54 | */
55 | getSerializableState(): any;
56 |
57 | /**
58 | * Sets the state from a serialized state object
59 | */
60 | setSerializableState(serializedState: any): void;
61 | }
62 |
--------------------------------------------------------------------------------
/src/controls/interfaces/IControlInput.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { HandlerInput } from 'ask-sdk-core';
15 |
16 | /**
17 | * Defines the input object passed around during processing by Controls.
18 | *
19 | * This is the minimal definition required by the Runtime (ControlHandler)
20 | * See `ControlInput` for the actual class used by Control implementations.
21 | */
22 | export interface IControlInput {
23 | readonly handlerInput: HandlerInput;
24 | readonly turnNumber: number;
25 | }
26 |
--------------------------------------------------------------------------------
/src/controls/interfaces/IControlManager.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { HandlerInput } from 'ask-sdk-core';
15 | import { InputModality, ResponseStyle } from '../../modality/ModalityEvaluation';
16 | import { ControlResponseBuilder } from '../../responseGeneration/ControlResponseBuilder';
17 | import { IControl } from './IControl';
18 | import { IControlInput } from './IControlInput';
19 | import { IControlResult } from './IControlResult';
20 |
21 | /**
22 | * Manages a skill built with Controls.
23 | *
24 | * This is the minimal definition required by the Runtime (ControlHandler)
25 | * See `ControlManager` for the actual class used by implementations.
26 | */
27 | export interface IControlManager {
28 | /**
29 | * Creates the tree of controls to handle state management and dialog
30 | * decisions for the skill.
31 | *
32 | * Usage:
33 | * - A single control is legal and will suffice for small skills. For larger
34 | * skills a tree of controls structured using @see ContainerControl will
35 | * help manage skill complexity.
36 | *
37 | * - In advanced scenarios with dynamic control tree shapes, this method
38 | * should produce only the static section of the tree. The dynamic
39 | * portion is
40 | * expected to produce a tree that is identical to the tree at the end of
41 | * the previous turn. The serializable control state can be inspected as
42 | * necessary.
43 | *
44 | * - If state objects are re-established during this method, the subsequent
45 | *
46 | * @param controlStateMap - Map of control state objects keyed by
47 | * `controlId` This is provided for advanced cases
48 | * in which the tree has a dynamic shape based on
49 | * the application state.
50 | * @returns A Control that is either a single @see Control or a @see
51 | * ContainerControl that is the root of a tree.
52 | */
53 | createControlTree(): IControl;
54 |
55 | reestablishControlStates(rootControl: IControl, stateMap: { [key: string]: any }): void;
56 |
57 | /**
58 | * Builds the response.
59 | *
60 | * @param result - The result to be rendered
61 | * @param input - Input
62 | * @param responseBuilder - Response builder
63 | */
64 | render(
65 | result: IControlResult,
66 | input: IControlInput,
67 | responseBuilder: ControlResponseBuilder,
68 | ): void | Promise;
69 |
70 | /**
71 | * Custom handling of a internal error.
72 | *
73 | * @param input - ControlInput object or undefined if an error occurs early in processing
74 | * @param error - Error object
75 | * @param responseBuilder - Response builder
76 | */
77 | handleInternalError?(
78 | input: IControlInput | undefined,
79 | error: any,
80 | responseBuilder: ControlResponseBuilder,
81 | ): void;
82 |
83 | /**
84 | *
85 | */
86 | loadControlStateMap(handlerInput: HandlerInput): Promise<{ [key: string]: any }>;
87 |
88 | /**
89 | *
90 | */
91 | saveControlStateMap(state: any, handlerInput: HandlerInput): Promise;
92 |
93 | /**
94 | * Determines the input modality of the current turn, based on the
95 | * currently-configured evaluator function.
96 | * @param handlerInput - Input for the current turn.
97 | * @returns InputModality - Input modality determined for the current turn.
98 | */
99 | evaluateInputModality(handlerInput: HandlerInput): InputModality;
100 |
101 | /**
102 | * Determines the recommended response style for the current turn, based on the
103 | * current-configured evaluator function.
104 | * @param handlerInput - Input for the current turn.
105 | * @param history - History of input modality of previous turns.
106 | * @returns ResponseStyle - Recommended response style for the current turn.
107 | */
108 | evaluateResponseStyle(input: HandlerInput, history: InputModality[]): ResponseStyle;
109 | }
110 |
--------------------------------------------------------------------------------
/src/controls/interfaces/IControlResult.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { SessionBehavior } from '../../runtime/SessionBehavior';
15 | import { ISystemAct } from '../../systemActs/SystemAct';
16 |
17 | /**
18 | * Defines the result of processing a turn using Controls.
19 | *
20 | * This is the minimal definition required by the Runtime (ControlHandler) See
21 | * `ControlResult` for the actual class used by Control implementations.
22 | */
23 | export interface IControlResult {
24 | acts: ISystemAct[];
25 | sessionBehavior: SessionBehavior;
26 | hasInitiativeAct(): boolean;
27 | }
28 |
--------------------------------------------------------------------------------
/src/controls/interfaces/IControlResultBuilder.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { IControlResult } from './IControlResult';
15 | import { SessionBehavior } from '../../runtime/SessionBehavior';
16 | import { ISystemAct } from '../../systemActs/SystemAct';
17 |
18 | /**
19 | * Defines a builder used to prepare a ControlResult.
20 | *
21 | * This is the minimal definition required by the Runtime (ControlHandler) See
22 | * `ControlResultBuilder` for the actual class used by Control implementations.
23 | */
24 | export interface IControlResultBuilder {
25 | acts: ISystemAct[];
26 | sessionBehavior: SessionBehavior;
27 | hasInitiativeAct(): boolean;
28 | build(): IControlResult;
29 | }
30 |
--------------------------------------------------------------------------------
/src/controls/interfaces/ILogger.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * Defines the Logger interface to define the logging format and
16 | * log level used across the framework.
17 | */
18 | export interface ILogger {
19 | moduleName: string;
20 |
21 | /**
22 | * Log a message as an "error".
23 | * @param message - Message
24 | */
25 | error(message: string): void;
26 |
27 | /**
28 | * Log a message as an "informational" message.
29 | * @param message - Message
30 | */
31 | info(message: string): void;
32 |
33 | /**
34 | * Log a message as an "warning".
35 | * @param message - Message
36 | */
37 | warn(message: string): void;
38 |
39 | /**
40 | * Log a message as an "low-level debug message".
41 | * @param message - Message
42 | */
43 | debug(message: string): void;
44 |
45 | /**
46 | * Log an object with pretty print formatting and also
47 | * masking sensitive information if required
48 | *
49 | * @param id - Unique label.
50 | * @param object - Object to be logged
51 | */
52 | logObject(level: string, id: string, object: any, stringify?: boolean): void;
53 |
54 | /**
55 | * Function that returns string with special characters to
56 | * mask restrictive information.
57 | *
58 | * @param message - Logging message
59 | */
60 | sanitize(message: any): string;
61 | }
62 |
--------------------------------------------------------------------------------
/src/controls/interfaces/ILoggerFactory.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { ILogger } from './ILogger';
15 |
16 | /**
17 | * Define the Logger Factory required on the
18 | * framework to customize the logging.
19 | */
20 | export interface ILoggerFactory {
21 | getLogger(namespace: string): ILogger;
22 | }
23 |
--------------------------------------------------------------------------------
/src/controls/mixins/ControlStateDiagramming.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { IControl } from '../interfaces/IControl';
15 |
16 | /**
17 | * Optional interface for Controls that wish to add detail to diagnostic output.
18 | */
19 | export interface ControlStateDiagramming {
20 | /**
21 | * Generates a human-readable representation of the state.
22 | *
23 | * @param this - Control
24 | * @returns Human-readable state
25 | */
26 | stringifyStateForDiagram(this: IControl): string;
27 | }
28 |
29 | /**
30 | * Type-guard for the ControlStateDiagramming interface.
31 | *
32 | * @param arg - Object to test
33 | * @returns `true` if the argument implements the `ControlStateDiagramming`
34 | * interface.
35 | */
36 | export function implementsControlStateDiagramming(arg: any): arg is ControlStateDiagramming {
37 | return typeof arg.stringifyStateForDiagram === 'function';
38 | }
39 |
--------------------------------------------------------------------------------
/src/controls/mixins/InteractionModelContributor.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { ControlInteractionModelGenerator } from '../../interactionModelGeneration/ControlInteractionModelGenerator';
15 | import { ModelData } from '../../interactionModelGeneration/ModelTypes';
16 |
17 | /**
18 | * Optional interface for Controls that wish to customize Interaction Model.
19 | */
20 | export interface InteractionModelContributor {
21 | /**
22 | * Updates the interaction model.
23 | *
24 | * Usage:
25 | * - Controls should add any content to the interaction model that they rely
26 | * on. In particular, intents, slot types and slot values.
27 | * - The imData parameter contains localized information for all known slot
28 | * types and intents. The `updateInteractionModel()` method should use
29 | * this to obtain the raw data that is added to the generator.
30 | *
31 | * @param generator - Interaction Model generator
32 | * @param imData - Localized data for all known intents and slots.
33 | *
34 | */
35 | updateInteractionModel(generator: ControlInteractionModelGenerator, imData: ModelData): void;
36 | }
37 |
38 | /**
39 | * Type-guard for the InteractionModelContributor interface.
40 | *
41 | * @param arg - Object to test
42 | * @returns `true` if the argument implements the InteractionModelContributor
43 | * interface.
44 | */
45 | export function implementsInteractionModelContributor(arg: any): arg is InteractionModelContributor {
46 | return typeof arg.updateInteractionModel === 'function';
47 | }
48 |
--------------------------------------------------------------------------------
/src/intents/AmazonBuiltInIntent.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * Constants for Alexa's built-in intents.
16 | *
17 | * (This is not a complete list)
18 | */
19 | //TODO: naming should be consistent with AmazonBuiltInSlotType. pick one.
20 | export enum AmazonIntent {
21 | CancelIntent = 'AMAZON.CancelIntent',
22 | FallbackIntent = 'AMAZON.FallbackIntent',
23 | HelpIntent = 'AMAZON.HelpIntent',
24 | MoreIntent = 'AMAZON.MoreIntent',
25 | NavigateHomeIntent = 'AMAZON.NavigateHomeIntent',
26 | NavigateSettingsIntent = 'AMAZON.NavigateSettingsIntent',
27 | NextIntent = 'AMAZON.NextIntent',
28 | NoIntent = 'AMAZON.NoIntent',
29 | PageUpIntent = 'AMAZON.PageUpIntent',
30 | PageDownIntent = 'AMAZON.PageDownIntent',
31 | PreviousIntent = 'AMAZON.PreviousIntent',
32 | PauseIntent = 'AMAZON.PauseIntent',
33 | ResumeIntent = 'AMAZON.ResumeIntent',
34 | ScrollRightIntent = 'AMAZON.ScrollRightIntent',
35 | ScrollDownIntent = 'AMAZON.ScrollDownIntent',
36 | ScrollLeftIntent = 'AMAZON.ScrollLeftIntent',
37 | ScrollUpIntent = 'AMAZON.ScrollUpIntent',
38 | StopIntent = 'AMAZON.StopIntent',
39 | YesIntent = 'AMAZON.YesIntent',
40 | }
41 |
--------------------------------------------------------------------------------
/src/intents/AmazonBuiltInSlotType.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * Constants for Alexa's built-in slot types.
16 | *
17 | * (This is not a complete list.)
18 | */
19 |
20 | //TODO: naming should be consistent with AmazonIntent. pick one.
21 | export enum AmazonBuiltInSlotType {
22 | DATE = 'AMAZON.DATE',
23 | DURATION = 'AMAZON.DURATION',
24 | NUMBER = 'AMAZON.NUMBER',
25 | FOUR_DIGIT_NUMBER = 'AMAZON.FOUR_DIGIT_NUMBER',
26 | ORDINAL = 'AMAZON.Ordinal',
27 | PHONENUMBER = 'AMAZON.PhoneNumber',
28 | TIME = 'AMAZON.TIME',
29 | SEARCH_QUERY = 'AMAZON.SearchQuery',
30 | }
31 |
--------------------------------------------------------------------------------
/src/intents/BaseControlIntent.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 | import { v1 } from 'ask-smapi-model';
14 | import Intent = v1.skill.interactionModel.Intent;
15 |
16 | /**
17 | * Abstract base class for the "Control Intents"
18 | */
19 | export abstract class BaseControlIntent {
20 | /**
21 | * Name of the intent as it will appear in the interaction model.
22 | *
23 | * Default: the class name.
24 | */
25 | name: string = this.constructor.name;
26 |
27 | /**
28 | * Generate a complete Intent object.
29 | */
30 | abstract generateIntent(): Intent;
31 | }
32 |
--------------------------------------------------------------------------------
/src/intents/GeneralControlIntent.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { Intent } from 'ask-sdk-model';
15 | import { v1 } from 'ask-smapi-model';
16 | import { SharedSlotType } from '../interactionModelGeneration/ModelTypes';
17 | import { getSlotResolutions, IntentBuilder } from '../utils/IntentUtils';
18 | import { BaseControlIntent } from './BaseControlIntent';
19 |
20 | /**
21 | * Slot values conveyed by a GeneralControlIntent
22 | */
23 | export interface GeneralControlIntentSlots {
24 | feedback?: string;
25 | action?: string;
26 | target?: string;
27 | }
28 |
29 | /**
30 | * Unpacks the complete intent object into a simpler representation.
31 | *
32 | * Note re "empty slots":
33 | * - Slots in the intent with no value appear in the intent object as "".
34 | * However, these are unpacked as **`undefined`** to be more explicit and ease
35 | * the implementation of predicates.
36 | * @param intent - Intent
37 | */
38 | export function unpackGeneralControlIntent(intent: Intent): GeneralControlIntentSlots {
39 | if (!intent.name.startsWith('GeneralControlIntent')) {
40 | throw new Error(`Not a compatible intent : ${intent.name}`);
41 | }
42 |
43 | let feedback: string | undefined;
44 | let action: string | undefined;
45 | let target: string | undefined;
46 |
47 | for (const [name, slot] of Object.entries(intent.slots!)) {
48 | const slotObject = getSlotResolutions(slot);
49 | const slotValue = slotObject ? slotObject.slotValue : undefined;
50 |
51 | switch (name) {
52 | case 'feedback':
53 | feedback = slotValue;
54 | break;
55 | case 'filteredFeedback':
56 | feedback = slotValue;
57 | break;
58 | case 'action':
59 | action = slotValue;
60 | break;
61 | case 'target':
62 | target = slotValue;
63 | break;
64 | case 'head':
65 | break;
66 | case 'tail':
67 | break;
68 |
69 | default:
70 | throw new Error('not handled');
71 | }
72 | }
73 |
74 | return {
75 | feedback,
76 | action,
77 | target,
78 | };
79 | }
80 |
81 | /**
82 | * Intent that conveys feedback, action, and target but no value.
83 | *
84 | * This general-purpose intent conveys the intent for utterances such as:
85 | * - "The time"
86 | * - "Change the date"
87 | * - "Yes, that one"
88 | * - "No, change the event date"
89 | */
90 | export class GeneralControlIntent extends BaseControlIntent {
91 | /**
92 | * Create Intent from specification of the slots
93 | */
94 | static of(slots: GeneralControlIntentSlots): Intent {
95 | return IntentBuilder.of(this.prototype.constructor.name, slots);
96 | }
97 |
98 | // tsDoc: see BaseControlIntent
99 | generateIntent(): v1.skill.interactionModel.Intent {
100 | return {
101 | name: this.name,
102 | slots: this.generateSlots(),
103 | samples: [],
104 | };
105 | }
106 |
107 | private generateSlots(): v1.skill.interactionModel.SlotDefinition[] {
108 | const slots: v1.skill.interactionModel.SlotDefinition[] = [
109 | {
110 | name: 'feedback',
111 | type: SharedSlotType.FEEDBACK,
112 | },
113 | {
114 | name: 'filteredFeedback',
115 | type: SharedSlotType.FILTERED_FEEDBACK,
116 | },
117 | {
118 | name: 'action',
119 | type: SharedSlotType.ACTION,
120 | },
121 | {
122 | name: 'target',
123 | type: SharedSlotType.TARGET,
124 | },
125 | {
126 | name: 'head',
127 | type: SharedSlotType.HEAD,
128 | },
129 | {
130 | name: 'tail',
131 | type: SharedSlotType.TAIL,
132 | },
133 | ];
134 |
135 | return slots;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/intents/OrdinalControlIntent.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { Intent } from 'ask-sdk-model';
15 | import { v1 } from 'ask-smapi-model';
16 | import { SharedSlotType } from '../interactionModelGeneration/ModelTypes';
17 | import { getSlotResolutions, IntentBuilder } from '../utils/IntentUtils';
18 | import { AmazonBuiltInSlotType } from './AmazonBuiltInSlotType';
19 | import { BaseControlIntent } from './BaseControlIntent';
20 |
21 | /**
22 | * Slot values conveyed by a OrdinalControlIntent
23 | */
24 | export interface OrdinalControlIntentSlots {
25 | feedback?: string;
26 | action?: string;
27 | target?: string;
28 | 'AMAZON.Ordinal'?: string;
29 | }
30 |
31 | /**
32 | * Unpacks the complete intent object into a simpler representation.
33 | *
34 | * Note re "empty slots":
35 | * - Slots in the intent with no value appear in the intent object as "".
36 | * However, these are unpacked as **`undefined`** to be more explicit and ease
37 | * the implementation of predicates.
38 | * @param intent - Intent
39 | */
40 | export function unpackOrdinalControlIntent(intent: Intent): OrdinalControlIntentSlots {
41 | if (!intent.name.startsWith('OrdinalControlIntent')) {
42 | throw new Error(`Not a compatible intent: ${intent.name}`);
43 | }
44 |
45 | let action: string | undefined;
46 | let feedback: string | undefined;
47 | let target: string | undefined;
48 | let valueStr: string | undefined;
49 |
50 | for (const [name, slot] of Object.entries(intent.slots!)) {
51 | const slotObject = getSlotResolutions(slot);
52 | const slotValue = slotObject ? slotObject.slotValue : undefined;
53 |
54 | switch (name) {
55 | case 'feedback':
56 | feedback = slotValue !== undefined ? slotValue : undefined;
57 | break;
58 | case 'action':
59 | action = slotValue !== undefined ? slotValue : undefined;
60 | break;
61 | case 'target':
62 | target = slotValue !== undefined ? slotValue : undefined;
63 | break;
64 | case 'AMAZON.Ordinal':
65 | valueStr = slotValue !== undefined ? slotValue : undefined;
66 | break;
67 | // default: ignore content-free slots
68 | }
69 | }
70 |
71 | return {
72 | feedback,
73 | action,
74 | target,
75 | 'AMAZON.Ordinal': valueStr,
76 | };
77 | }
78 |
79 | /**
80 | * Intent that conveys feedback, action, target and an AMAZON.Ordinal value
81 | *
82 | * This general-purpose intent conveys the intent for utterances such as:
83 | * - "The time"
84 | * - "Change the date"
85 | * - "Yes, that one"
86 | * - "No, change the event date"
87 | */
88 | export class OrdinalControlIntent extends BaseControlIntent {
89 | /**
90 | * Create Intent from specification of the slots
91 | */
92 | static of(slots: OrdinalControlIntentSlots): Intent {
93 | return IntentBuilder.of(this.prototype.constructor.name, slots);
94 | }
95 |
96 | // tsDoc: see BaseControlIntent
97 | generateIntent(): v1.skill.interactionModel.Intent {
98 | return {
99 | name: this.name,
100 | slots: this.generateSlots(),
101 | samples: [],
102 | };
103 | }
104 |
105 | private generateSlots(): v1.skill.interactionModel.SlotDefinition[] {
106 | const slots: v1.skill.interactionModel.SlotDefinition[] = [
107 | {
108 | name: 'feedback',
109 | type: SharedSlotType.FEEDBACK,
110 | },
111 | {
112 | name: 'action',
113 | type: SharedSlotType.ACTION,
114 | },
115 | {
116 | name: 'target',
117 | type: SharedSlotType.TARGET,
118 | },
119 | {
120 | name: 'AMAZON.Ordinal',
121 | type: AmazonBuiltInSlotType.ORDINAL,
122 | },
123 | {
124 | name: 'preposition',
125 | type: SharedSlotType.PREPOSITION,
126 | },
127 | {
128 | name: 'head',
129 | type: SharedSlotType.HEAD,
130 | },
131 | {
132 | name: 'tail',
133 | type: SharedSlotType.TAIL,
134 | },
135 | ];
136 |
137 | return slots;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/interactionModelGeneration/ModelTypes.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 | import { v1 } from 'ask-smapi-model';
14 | import SlotType = v1.skill.interactionModel.SlotType;
15 |
16 | /**
17 | * Names of the built-in slot types used by ControlIntents
18 | */
19 | export enum SharedSlotType {
20 | FEEDBACK = 'feedback',
21 | FILTERED_FEEDBACK = 'filteredFeedback',
22 | HEAD = 'head',
23 | TAIL = 'tail',
24 | ACTION = 'action',
25 | TARGET = 'target',
26 | CONJUNCTION = 'conjunction',
27 | PREPOSITION = 'preposition',
28 | }
29 |
30 | /**
31 | * Localized information Names of the built-in slot types used by ControlIntents
32 | */
33 | export interface ModelData {
34 | slotTypes: SlotType[];
35 | intentValues: IntentUtterances[]; // TODO: naming: review name
36 | }
37 |
38 | export interface SlotValue {
39 | synonyms: string[];
40 | id: string;
41 | }
42 |
43 | export interface IntentUtterances {
44 | name: string;
45 | samples: string[];
46 | }
47 |
48 | export type ModelDataMap = { [locale: string]: ModelData };
49 |
--------------------------------------------------------------------------------
/src/intl/EnglishGrammar.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * Utilities for prompt generation in English
16 | */
17 | export namespace EnglishGrammar {
18 | /**
19 | * Choose the article to use in English noun phrases (a vs an)
20 | *
21 | * This is only a partial implementation.
22 | * See https://www.grammar.com/a-vs-an-when-to-use/
23 | * @param antecedent - Antecedent
24 | */
25 | export function article(antecedent: string) {
26 | const firstLetter = antecedent.trim()[0];
27 | if (['a', 'e', 'i', 'o', 'u'].includes(firstLetter)) {
28 | return 'an';
29 | } else {
30 | return 'a';
31 | }
32 | }
33 |
34 | /**
35 | * Render a noun in singular or plural form
36 | *
37 | * Both the singular and plural form of the noun must be supplied as arguments.
38 | *
39 | * Rule:
40 | * * `1 -> singular`
41 | * * `else -> plural` (including zero)
42 | *
43 | * @param count - Count
44 | * @param singular - Singular form
45 | * @param plural - Plural form
46 | */
47 | export function renderNoun(count: number | string, singular: string, plural: string) {
48 | return count === 1 || count === '1' ? singular : plural;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/intl/ListFormat.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import _ from 'lodash';
15 |
16 | /**
17 | * Utilities for rendering lists to strings/prompts.
18 | */
19 | export namespace ListFormatting {
20 | /**
21 | * Format a list with commas and a joiner word.
22 | *
23 | * Example: `formatList(['a', 'b', 'c'], 'and or') -> 'a, b, and or c'`
24 | *
25 | * This can mostly be replaced with Intl.ListFormat(style:'long', ..) once it is implemented for NodeJS.
26 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat
27 | *
28 | * @param arr - Array
29 | * @param joiner - Joiner
30 | */
31 | export function format(arr: string[], joiner = 'or'): string {
32 | if (arr.length === 0) {
33 | return '(empty)';
34 | }
35 | if (arr.length === 1) {
36 | return arr[0];
37 | }
38 | if (arr.length === 2) {
39 | return `${arr[0]} ${joiner} ${arr[1]}`;
40 | } else {
41 | return `${_.join(_.take(arr, arr.length - 1), ', ')} ${joiner} ${arr[arr.length - 1]}`;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/logging/DefaultLogger.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 | import Debug from 'debug';
14 | import { ILogger } from '../controls/interfaces/ILogger';
15 |
16 | const DEFAULT_LOG_LEVEL = 'error:*, warn:*';
17 |
18 | /**
19 | * Default Logger Implementation.
20 | *
21 | * This wraps the Debug object from npm 'Debug' package to provide "log-levels".
22 | * The log-levels are handled as top-level namespaces.
23 | *
24 | * Examples
25 | * ```
26 | * export DEBUG="error:*" -> Log 'error' messages from every module
27 | * export DEBUG="error:moduleA" -> Log 'error' messages for moduleA only
28 | * export DEBUG="error:*, warn:*, info:*, debug:*" -> Log everything
29 | * ```
30 | *
31 | * See https://www.npmjs.com/package/debug for more information on
32 | * configuration.
33 | *
34 | * When instantiated for a given "moduleName", this object provides an `error()`
35 | * function that logs with amended name "error:moduleName". Likewise for `warn()`,
36 | * `info()`, and `debug()`.
37 | */
38 | export class DefaultLogger implements ILogger {
39 | moduleName: string;
40 |
41 | constructor(moduleName: string) {
42 | this.moduleName = moduleName;
43 |
44 | const namespace = process.env.DEBUG ?? DEFAULT_LOG_LEVEL;
45 | Debug.enable(namespace);
46 | }
47 |
48 | /**
49 | * Log a message as an "error".
50 | * @param message - Message
51 | */
52 | error(message: string): void {
53 | Debug(`error:${this.moduleName}`)(message);
54 | }
55 |
56 | /**
57 | * Log a message as an "warning".
58 | * @param message - Message
59 | */
60 | warn(message: string): void {
61 | Debug(`warn:${this.moduleName}`)(message);
62 | }
63 |
64 | /**
65 | * Log a message as an "informational" message.
66 | * @param message - Message
67 | */
68 | info(message: string): void {
69 | Debug(`info:${this.moduleName}`)(message);
70 | }
71 |
72 | /**
73 | * Log a message as an "low-level debug message".
74 | * @param message - Message
75 | */
76 | debug(message: string): void {
77 | Debug(`debug:${this.moduleName}`)(message);
78 | }
79 |
80 | /**
81 | * Log an object with pretty print formatting and also
82 | * masking sensitive information if required
83 | *
84 | * @param id - Unique label.
85 | * @param object - Object to be logged
86 | */
87 | logObject(logLevel: string, id: string, object: any, stringify?: true): void {
88 | const objectToLogJson: string = stringify !== true ? object : JSON.stringify(object, null, 2);
89 | Debug(`${logLevel}:${this.moduleName} ${id}`)(objectToLogJson);
90 | }
91 |
92 | /**
93 | * Function that returns string with actual message if sensitive logging is
94 | * turned off or returns special character string masking the information.
95 | *
96 | * @param message - Logging message
97 | *
98 | * Eg: When sensitive logging is turned on
99 | * "env": \{
100 | * "ASK_SDK_RESTRICTIVE_LOGGING": "true"
101 | * \}
102 | *
103 | * All messages which are wrapped around `sanitize()` in log.info for example
104 | * are masked using special characters.
105 | */
106 | sanitize(message: any): string {
107 | if (
108 | process.env.ASK_SDK_RESTRICTIVE_LOGGING !== undefined &&
109 | process.env.ASK_SDK_RESTRICTIVE_LOGGING === 'true'
110 | ) {
111 | return '****';
112 | } else {
113 | return message;
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/logging/DefaultLoggerFactory.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { ILogger } from '../controls/interfaces/ILogger';
15 | import { ILoggerFactory } from '../controls/interfaces/ILoggerFactory';
16 | import { DefaultLogger } from './DefaultLogger';
17 |
18 | /**
19 | * Default Logger Factory
20 | */
21 | export class DefaultLoggerFactory implements ILoggerFactory {
22 | /**
23 | * Function which returns an instance of Default Logger
24 | * implementation.
25 | *
26 | * @param namespace - Module Namespace
27 | */
28 | getLogger(namespace: string): ILogger {
29 | return new DefaultLogger(namespace);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/modality/ModalityEvaluation.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { HandlerInput } from 'ask-sdk-core';
15 |
16 | /**
17 | * Possible modalities a user can use to respond to a request.
18 | */
19 | export enum InputModality {
20 | /**
21 | * User touched the screen.
22 | */
23 | TOUCH = 'touch',
24 | /**
25 | * User used their voice.
26 | */
27 | VOICE = 'voice',
28 | }
29 |
30 | /**
31 | * Possible modalities with which the skill may respond to a request.
32 | */
33 | export enum OutputModality {
34 | /**
35 | * Response is only displayed on the screen.
36 | */
37 | SCREEN = 'screen',
38 |
39 | /**
40 | * Response is in the form of a voice prompt,
41 | * and may or may not include content displayed on the screen.
42 | */
43 | VOICE = 'voice',
44 |
45 | /**
46 | * Response type is not yet known.
47 | */
48 | INDETERMINATE = 'indeterminate',
49 | }
50 |
51 | /**
52 | * Describes the style in which a request should be responded to.
53 | * The most basic characteristic is what modality the response should have,
54 | * but this interface may be extended to describe additional characteristics
55 | * as desired (e.g. whispering).
56 | */
57 | export interface ResponseStyle {
58 | /**
59 | * The modality with which a request should be responded to.
60 | */
61 | modality: OutputModality;
62 | }
63 |
64 | /**
65 | * A function that determines the input modality of a request.
66 | */
67 | export type InputModalityEvaluator = (input: HandlerInput) => InputModality;
68 |
69 | /**
70 | * A function that suggests the style in which a request should be responded to,
71 | * based on the content of the request and the history of how the user responded.
72 | */
73 | export type ResponseStyleEvaluator = (input: HandlerInput, history: InputModality[]) => ResponseStyle;
74 |
75 | /**
76 | * Default functions for determining input modality and response styles.
77 | */
78 | export namespace ModalityEvaluationDefaults {
79 | /**
80 | * Default function for determining the input modality of a request.
81 | * This is best-effort and may not work in all cases.
82 | * @param input - Input for the current request
83 | * @returns InputModality - TOUCH if the request came from a TouchWrapper,
84 | * VOICE otherwise.
85 | */
86 | export function defaultInputModalityEvaluator(input: HandlerInput): InputModality {
87 | const request = input.requestEnvelope.request;
88 | if (request.type === 'Alexa.Presentation.APL.UserEvent') {
89 | if (['TouchWrapper', 'EditText', 'VectorGraphic'].includes(request.source?.type)) {
90 | return InputModality.TOUCH;
91 | }
92 | }
93 | return InputModality.VOICE;
94 | }
95 |
96 | /**
97 | * Default function for suggesting the style in which a request should be responded to.
98 | * @param input - Input for the current request
99 | * @param history - History of how the user responded to previous requests.
100 | * The current request is the last entry.
101 | * @returns ResponseStyle - SCREEN modality if the last InputModality was TOUCH, VOICE otherwise.
102 | */
103 | export function defaultResponseStyleEvaluator(
104 | input: HandlerInput,
105 | history: InputModality[],
106 | ): ResponseStyle {
107 | if (history?.length > 0 && history[history.length - 1] === InputModality.TOUCH) {
108 | return { modality: OutputModality.SCREEN };
109 | }
110 |
111 | return { modality: OutputModality.VOICE };
112 | }
113 |
114 | /**
115 | * Function that always returns an INDETERMINATE modality for a suggested ResponseStyle.
116 | * This is primarily used as the default override function for built-in controls, which results
117 | * in the controls deferring the decision to the function at the ControlManager level.
118 | * @returns OutputModality.INDETERMINATE
119 | */
120 | export function indeterminateResponseStyleEvaluator(input: HandlerInput, history: InputModality[]) {
121 | return { modality: OutputModality.INDETERMINATE };
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/responseGeneration/AplMode.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * APL rendering mode
16 | */
17 | export enum APLMode {
18 | /**
19 | * Controls add APL directives directly to the ResponseBuilder as they are rendering
20 | * their prompts.
21 | */
22 | DIRECT = 'Direct',
23 |
24 | /**
25 | * The top-level ControlManager.renderAPL produces the APL for each turn.
26 | * - typically this will be a document that includes various control.renderAPLComponent calls.
27 | *
28 | * Controls do not render APL directives during renderAct.
29 | */
30 | COMPONENT = 'Component',
31 | }
32 |
--------------------------------------------------------------------------------
/src/runtime/SessionBehavior.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * Defines the behavior for the user's session when the Response is delivered.
16 | */
17 | export enum SessionBehavior {
18 | /**
19 | * Keep the session open.
20 | */
21 | OPEN,
22 |
23 | /**
24 | * End the session.
25 | */
26 | END,
27 |
28 | /**
29 | * Keep the session open for approximately 30 seconds but with the
30 | * microphone disabled.
31 | *
32 | * This only applies to devices with a screen. See
33 | * https://developer.amazon.com/en-US/docs/alexa/custom-skills/manage-skill-session-and-session-attributes.html#screen-session
34 | */
35 | IDLE,
36 | }
37 |
--------------------------------------------------------------------------------
/src/systemActs/PayloadTypes.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 | import { canfulfill } from 'ask-sdk-model';
14 |
15 | /**
16 | * Payload for ValueSetAct
17 | */
18 | export interface ValueSetPayload {
19 | /**
20 | * The control value.
21 | */
22 | value: T;
23 |
24 | /**
25 | * The rendered value.
26 | */
27 | renderedValue: string;
28 | }
29 |
30 | /**
31 | * Payload for ValueAddeddAct
32 | */
33 | export interface ValueAddedPayload {
34 | /**
35 | * The control value.
36 | */
37 | value: T;
38 |
39 | /**
40 | * The rendered value.
41 | */
42 | renderedValue: string;
43 | }
44 |
45 | /**
46 | * Payload for ValueRemovedAct
47 | */
48 | export interface ValueRemovedPayload {
49 | /**
50 | * The control value.
51 | */
52 | value: T;
53 |
54 | /**
55 | * The rendered value.
56 | */
57 | renderedValue: string;
58 | }
59 |
60 | /**
61 | * Payload for ValueClearedAct
62 | */
63 | export interface ValueClearedPayload {
64 | /**
65 | * The control value.
66 | */
67 | value: T;
68 |
69 | /**
70 | * The rendered value.
71 | */
72 | renderedValue: string;
73 | }
74 |
75 | /**
76 | * Payload for ValueChangedAct
77 | */
78 | export interface ValueChangedPayload {
79 | /**
80 | * The control value.
81 | */
82 | value: T;
83 |
84 | /**
85 | * The rendered value.
86 | */
87 | renderedValue: string;
88 |
89 | /**
90 | * The previous control value.
91 | */
92 | previousValue?: T;
93 |
94 | /**
95 | * The previous rendered value.
96 | */
97 | renderedPreviousValue?: string;
98 | }
99 |
100 | /**
101 | * Payload for SuggestActionAct
102 | */
103 | export interface SuggestActionPayload {
104 | renderedTarget?: string;
105 | }
106 |
107 | /**
108 | * Payload for InvalidValueAct
109 | */
110 | export interface InvalidValuePayload {
111 | value: T;
112 | renderedValue: string;
113 | reasonCode?: string;
114 | renderedReason?: string;
115 | }
116 |
117 | /**
118 | * Payload for UnusableInputValueAct
119 | */
120 | export interface ProblematicInputValuePayload {
121 | value: T;
122 | renderedValue: string;
123 | reasonCode: string;
124 | renderedReason?: string;
125 | }
126 |
127 | /**
128 | * Payload for RequestValueAct
129 | */
130 | export interface RequestValuePayload {
131 | renderedTarget?: string;
132 | }
133 |
134 | /**
135 | * Payload for RequestChangedValueAct
136 | */
137 | export interface RequestChangedValuePayload {
138 | currentValue: string;
139 | renderedValue: string;
140 | renderedTarget?: string;
141 | }
142 |
143 | /**
144 | * Payload for RequestValueByListAct
145 | */
146 | export interface RequestValueByListPayload {
147 | choicesFromActivePage: string[];
148 | allChoices: string[];
149 | renderedChoicesFromActivePage: string[];
150 | renderedAllChoices: string[];
151 | renderedTarget?: string;
152 | renderedChoices?: string;
153 | }
154 |
155 | /**
156 | * Payload for RequestChangedValueByListAct
157 | */
158 | export interface RequestChangedValueByListPayload {
159 | currentValue: string;
160 | renderedValue: string;
161 | choicesFromActivePage: string[];
162 | allChoices: string[];
163 | renderedChoicesFromActivePage: string[];
164 | renderedAllChoices: string[];
165 | renderedTarget?: string;
166 | renderedChoices?: string;
167 | }
168 |
169 | /**
170 | * Payload for RequestRemovedValueByListAct
171 | */
172 | export interface RequestRemovedValueByListActPayload {
173 | availableChoicesFromActivePage: string[];
174 | availableChoices: string[];
175 | renderedChoicesFromActivePage: string[];
176 | renderedAvailableChoices: string[];
177 | renderedTarget?: string;
178 | renderedChoices?: string;
179 | }
180 |
181 | /**
182 | * Payload for LiteralInitiativeAct
183 | */
184 | export interface LiteralContentPayload {
185 | promptFragment: string;
186 | repromptFragment?: string;
187 | }
188 |
189 | /**
190 | * Payload for CanFulfillIntentAct
191 | */
192 | export interface CanFulfillIntentPayload {
193 | intent: canfulfill.CanFulfillIntent;
194 | }
195 |
--------------------------------------------------------------------------------
/src/systemActs/SystemAct.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import _ from 'lodash';
15 | import { Control } from '../controls/Control';
16 | import { ControlInput } from '../controls/ControlInput';
17 | import { ControlResponseBuilder } from '../responseGeneration/ControlResponseBuilder';
18 |
19 | /**
20 | * Describes a piece of information to be communicated to the user.
21 | *
22 | * This is the minimal definition required by the Runtime (ControlHandler)
23 | * See `SystemAct` for the actual class used by Control implementations.
24 | */
25 | export interface ISystemAct {
26 | takesInitiative: boolean;
27 | }
28 |
29 | /**
30 | * Base type for system dialog acts.
31 | *
32 | * Each `SystemAct` represents a precise 'piece of information' that is to be communicated to the user. Each
33 | * `SystemAct` is to be associated with a Control that can be used to render it.
34 | *
35 | * There are two more specific base classes that should be used as the base for user-defined SystemActs:
36 | * * `ContentAct`: the base class for System Acts that just communicate some information.
37 | * * `InitiativeAct`: the base class for System Acts that ask a question or otherwise encourage the user to continue the conversation.
38 | *
39 | * Usage:
40 | * * Add `SystemActs` to the result in `Control.canHandle` and `Control.takeInitiative` to represent what the system wants to communicate.
41 | * * Convert the `SystemActs` into surface forms (prompts, APL, etc) during the render phase.
42 | * * Always extend `ContentAct` or `InitiativeAct` rather than this base type.
43 | * * Introduce new System Acts whenever the available acts are not suitable or precise enough.
44 | */
45 | export abstract class SystemAct implements ISystemAct {
46 | control: Control;
47 | takesInitiative: boolean;
48 |
49 | /**
50 | * Creates an instance of SystemAct.
51 | *
52 | * Each system act represent a specific 'atom of dialog' the system wishes to communicate to the user.
53 | *
54 | * Usage:
55 | * - New acts should generally extend `InitiativeAct` or `ContentAct` rather than this class, to improve readability.
56 | */
57 | constructor(control: Control, props: { takesInitiative: boolean }) {
58 | this.control = control;
59 | this.takesInitiative = props.takesInitiative;
60 | }
61 |
62 | /**
63 | * Produces a string representation of the SystemAct.
64 | *
65 | * The associated `Control.id` is included but the complete details of the associate `Control` are omitted for brevity.
66 | */
67 | toString() {
68 | return `${this.constructor.name}:${JSON.stringify(this.cloneWithControlIdNotDetails())}`;
69 | }
70 |
71 | /**
72 | * For use in toString.
73 | * Creates a clone that replaces the control object with controlId.
74 | */
75 | private cloneWithControlIdNotDetails(): any {
76 | const cleanAct = _.cloneDeep(this) as any;
77 | cleanAct.controlId = this.control?.id ?? '';
78 | delete cleanAct.control;
79 | return cleanAct;
80 | }
81 |
82 | /**
83 | * Render the dialog act.
84 | *
85 | * This is the one-size-fits-all direct rendering of a dialog act. This is often appropriate
86 | * for custom acts that are not used by a shared control.
87 | *
88 | * Framework behavior:
89 | * * Shared controls cannot rely on a one-size-fits-all rendering and so they provide
90 | * their own appropriate defaults and props that allow the developer to override the defaults.
91 | *
92 | * @param input - Input
93 | * @param responseBuilder - Response builder
94 | */
95 | abstract render(input: ControlInput, responseBuilder: ControlResponseBuilder): void;
96 | }
97 |
--------------------------------------------------------------------------------
/src/utils/ArrayUtils.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * Randomly selects one item from an array with uniform probability.
16 | */
17 | export function randomlyPick(input: T[]): T {
18 | return input[Math.floor(Math.random() * input.length)];
19 | }
20 |
21 | /**
22 | * Moves one item of an array in place and returns the mutated array.
23 | * @param arr - Array
24 | * @param from - Index of item to move
25 | * @param to - Index to move it to
26 | */
27 | export function moveArrayItem(arr: any[], from: number, to: number): any[] {
28 | arr.splice(to, 0, arr.splice(from, 1)[0]);
29 | return arr;
30 | }
31 |
32 | // TODO: API: remove
33 | /**
34 | * Returns `true` if the value is defined and does not appear in the array.
35 | * @param value - Value
36 | * @param array - Array
37 | */
38 | export function mismatch(value: any, array: any[]): boolean {
39 | return value !== undefined && !array.includes(value);
40 | }
41 |
42 | // TODO: API: remove
43 | /**
44 | * Returns `true` if the value is undefined or appears in the array.
45 | * @param value - Value
46 | * @param array - Array
47 | */
48 | export function matchIfDefined(value: any, array: any[]) {
49 | return value === undefined || array.includes(value);
50 | }
51 |
--------------------------------------------------------------------------------
/src/utils/AssertionUtils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Assertion, with Typescript 3.7 asserts to convey the information to compiler.
3 | * @param condition - condition
4 | * @param message - message
5 | */
6 | export function assert(condition: boolean, message?: string): asserts condition {
7 | if (!condition) {
8 | throw new Error(message !== undefined ? `Assertion failed. ${message}` : 'Assertion failed');
9 | }
10 | return;
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/BasicTypes.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * Either a string or an array of strings
16 | */
17 | export type StringOrList = string | string[];
18 |
19 | // TODO: API: remove or consolidate with ValidationResult / validation function types
20 | /**
21 | * Either the boolean literal `true` or a string.
22 | *
23 | * Purpose:
24 | * - For use in validation functions that return true or an error string.
25 | */
26 | export type StringOrTrue = true | string;
27 |
--------------------------------------------------------------------------------
/src/utils/ControlTreeVisualization.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { implementsControlStateDiagramming } from '../controls/mixins/ControlStateDiagramming';
15 | import { IControl } from '../controls/interfaces/IControl';
16 | import { isContainerControl } from '../controls/interfaces/IContainerControl';
17 |
18 | /**
19 | * Creates a text diagram of the Control tree
20 | *
21 | * Notes:
22 | * 1. The controls in the handling chain are highlighted with `= H =`
23 | * 2. The controls in the initiative chain are highlighted with `= I =`
24 | * 3. Controls that are in both chains are highlighted with `= B =`
25 | * @param control - Control
26 | * @param turnNumber - Turn number
27 | * @param indent - Indent
28 | */
29 | export function generateControlTreeTextDiagram(
30 | control: IControl,
31 | turnNumber: number,
32 | indent?: number,
33 | ): string {
34 | indent = indent ?? 0;
35 | let text = control.id;
36 | let stateStr = control.constructor.name;
37 | if (implementsControlStateDiagramming(control)) {
38 | stateStr = control.stringifyStateForDiagram();
39 | }
40 | text += ` (${stateStr})`;
41 |
42 | const coloredText = text;
43 | let str = `${coloredText}\n`;
44 |
45 | if (isContainerControl(control)) {
46 | for (const child of control.children) {
47 | const childHandledThisTurn =
48 | child.id === (control as any).state?.lastHandlingControl?.controlId &&
49 | (control as any).state?.lastHandlingControl?.turnNumber === turnNumber;
50 | const childTookInitiativeThisTurn =
51 | child.id === (control as any).state?.lastInitiativeChild?.controlId &&
52 | (control as any).state?.lastInitiativeChild?.turnNumber === turnNumber;
53 | // const text = `|${(childHandledThisTurn || childTookInitiativeThisTurn) ? "== " : "-- "}`;
54 |
55 | const text = `|${
56 | childHandledThisTurn && childTookInitiativeThisTurn
57 | ? '= B = '
58 | : childHandledThisTurn
59 | ? '= H = '
60 | : childTookInitiativeThisTurn
61 | ? '= I = '
62 | : '----- '
63 | }`;
64 |
65 | str += '| '.repeat(indent) + text + generateControlTreeTextDiagram(child, turnNumber, indent + 1);
66 | }
67 | }
68 |
69 | return str;
70 | }
71 |
--------------------------------------------------------------------------------
/src/utils/ControlUtils.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 | import { Control } from '../controls/Control';
14 | import { ControlInput } from '../controls/ControlInput';
15 | import { ControlServices } from '../controls/ControlServices';
16 |
17 | const MODULE_NAME = 'AskSdkControls:ControlUtils';
18 | /*
19 | * // TODO: tighten up the contract.. what props are supported, precisely.
20 | * probably also good to factor into gatherFuncs() and evaluateFuncs()
21 |
22 | * // TODO: tighten by refactoring NumberControl/DateRangeControl canHandle.
23 | /**
24 | * Evaluate the Input handlers
25 | *
26 | * Handlers can be defined in two places:
27 | * 1. `props.inputHandling.customHandlingFuncs`, and
28 | * 2. `props.apl.customHandlingFuncs`
29 | * @param control - control
30 | * @param input - input
31 | */
32 | export async function evaluateInputHandlers(control: Control, input: ControlInput): Promise {
33 | const log = ControlServices.getLogger(MODULE_NAME);
34 | const stdHandlers = (control as any).standardInputHandlers ?? [];
35 | const customHandlers = (control as any).props?.inputHandling?.customHandlingFuncs ?? [];
36 |
37 | const aplProps = (control as any).props.apl;
38 |
39 | // TODO: deprecate apl customHandlers prop
40 | if (aplProps !== undefined) {
41 | Object.entries(aplProps).forEach(([_key, value]) => {
42 | if (typeof value === 'object' && 'customHandlingFuncs' in value!) {
43 | customHandlers.push(...(value as any).customHandlingFuncs);
44 | }
45 | });
46 | }
47 |
48 | const matches = [];
49 | for (const handler of stdHandlers.concat(customHandlers)) {
50 | if ((await handler.canHandle.call(control as any, input)) === true) {
51 | matches.push(handler);
52 | }
53 | }
54 |
55 | if (matches.length > 1) {
56 | throw Error(
57 | `More than one handler matched. Handlers in a single control should be mutually exclusive.` +
58 | ` handlers: ${JSON.stringify(matches.map((x) => x.name))}`,
59 | );
60 | }
61 |
62 | if (matches.length >= 1) {
63 | (control as any).handleFunc = matches[0].handle.bind(control as any);
64 | return true;
65 | } else {
66 | return false;
67 | }
68 | }
69 |
70 | //Exported for internal use only. Not sufficiently well-defined or valuable for public export.
71 | export function _logIfBothTrue(customCanHandle: boolean, builtInCanHandle: boolean) {
72 | const log = ControlServices.getLogger(MODULE_NAME);
73 | if (customCanHandle === true && builtInCanHandle === true) {
74 | log.warn(
75 | 'Custom canHandle function and built-in canHandle function both returned true. Turn on debug logging for more information',
76 | );
77 | }
78 | }
79 |
80 | /**
81 | * Selects the first control with specific ID from an array.
82 | *
83 | * Behavior:
84 | * - implemented by linear search.
85 | * - if more than one control matches, the first is returned.
86 | * - if parameter `id` is undefined, returns `undefined`.
87 | * - if there is no control with matching id, returns `undefined`.
88 | *
89 | * @param controls - Controls
90 | * @param childId - The id to match
91 | * @returns - The matching childControl, or undefined if not present.
92 | */
93 | export function findControlById(controls: Control[], id: string | undefined): Control | undefined {
94 | if (id === undefined) {
95 | return undefined;
96 | }
97 | return controls.find((c) => c.id === id);
98 | }
99 |
--------------------------------------------------------------------------------
/src/utils/ControlVisitor.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { IControl } from '../controls/interfaces/IControl';
15 | import { isContainerControl } from '../controls/interfaces/IContainerControl';
16 |
17 | /**
18 | * Visits all controls in a control tree and calls `fn(control)` for each.
19 | * @param control - Control
20 | * @param fn - Function to run on each control
21 | */
22 | export function visitControls(control: IControl, fn: (control: IControl) => void) {
23 | fn.call(undefined, control);
24 |
25 | if (isContainerControl(control)) {
26 | for (const child of control.children) {
27 | visitControls(child, fn);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/DeepRequired.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * A type that changes all optional properties to be required, recursively.
16 | *
17 | * Function types are not rewritten as that would change their formal-argument
18 | * types.
19 | */
20 |
21 | export type DeepRequired =
22 | // eslint-disable-next-line @typescript-eslint/ban-types
23 | T extends Function
24 | ? T
25 | : {
26 | [k in keyof T]-?: DeepRequired;
27 | };
28 |
--------------------------------------------------------------------------------
/src/utils/ErrorUtils.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | // TODO: API: review if these are still required.
15 |
16 | /**
17 | * Throws an Error if the guard is true.
18 | */
19 | export function throwIf(guard: boolean, message: string): guard is true {
20 | if (guard) {
21 | throw new Error(message);
22 | }
23 | return true;
24 | }
25 |
26 | /**
27 | * Throws an Error if the object is undefined.
28 | */
29 | export function throwIfUndefined(object: T | undefined, message: string): object is T {
30 | if (object === undefined) {
31 | throw new Error(message);
32 | }
33 | return true;
34 | }
35 |
--------------------------------------------------------------------------------
/src/utils/Predicates.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | /**
15 | * Represents that the state of a control is not consistent and cannot be used.
16 | */
17 | export class StateConsistencyError extends Error {
18 | constructor(message?: string) {
19 | super(message);
20 | // see: typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html
21 | Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
22 | this.name = StateConsistencyError.name; // stack traces display correctly now
23 | }
24 | }
25 |
26 | /**
27 | * Represents that a predicate-guard expression failed.
28 | *
29 | * Purpose:
30 | * * This helps to write predicate functions as a linear chain of individual tests.
31 | */
32 | export class GuardFailed extends Error {
33 | constructor(message?: string) {
34 | super(message);
35 | // see: typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html
36 | Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
37 | this.name = GuardFailed.name; // stack traces display correctly now
38 | }
39 | }
40 |
41 | /**
42 | * Does nothing if the predicate is true. Otherwise throws GuardFailed error.
43 | *
44 | * Purpose:
45 | * * This helps to write predicate functions as a linear chain of individual tests.
46 | */
47 | export function okIf(predicate: boolean): asserts predicate {
48 | if (!predicate) {
49 | throw new GuardFailed('okIf failed. predicate===false.');
50 | }
51 | }
52 |
53 | /**
54 | * Throws a GuardFailed error if predicate is true.
55 | *
56 | * Purpose:
57 | * * This helps to write predicate functions as a linear chain of individual tests.
58 | * @param predicate - Predicate
59 | */
60 | export function failIf(predicate: boolean) {
61 | if (predicate) {
62 | throw new GuardFailed('failIf triggered. predicate===true.');
63 | }
64 | }
65 |
66 | /**
67 | * Consumes an GuardFailed error and returns false, but otherwise rethrows.
68 | *
69 | * Purpose:
70 | * * This helps to write predicate functions as a linear chain of individual tests.
71 | *
72 | * @returns `false` if error is a `GuardFailed` error.
73 | * @throws Rethrows the error if it is not a `GuardFailed` error.
74 | */
75 | export function falseIfGuardFailed(err: Error): false {
76 | if (err instanceof GuardFailed) {
77 | return false;
78 | }
79 | throw err; // otherwise rethrow
80 | }
81 |
82 | // different style than falseIfGuardFailed
83 | // better comprehension, but results in 2-lines of boilerplate rather than one.
84 | export function verifyErrorIsGuardFailure(err: Error): void {
85 | if (!(err instanceof GuardFailed)) {
86 | throw err;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/utils/RequestUtils.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { Request } from 'ask-sdk-model';
15 | import { SimplifiedIntent } from './IntentUtils';
16 |
17 | /**
18 | * Utility to stringify a Request object.
19 | * @param request - Request
20 | */
21 | export function requestToString(request: Request) {
22 | switch (request.type) {
23 | case 'IntentRequest':
24 | return SimplifiedIntent.fromIntent(request.intent).toString();
25 | case 'Alexa.Presentation.APL.UserEvent':
26 | return `APL.UserEvent: ${JSON.stringify(request.arguments)}`;
27 | default:
28 | return request.type;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/ResponseUtils.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { OutputModality, ResponseStyle, ResponseStyleEvaluator } from '../modality/ModalityEvaluation';
15 | import { ControlResponseBuilder } from '../responseGeneration/ControlResponseBuilder';
16 | import { Response } from 'ask-sdk-model';
17 | import { ControlInput } from '../controls/ControlInput';
18 | import { ControlResultBuilder } from '..';
19 |
20 | /**
21 | * Properties for building a response based on ResponseStyle.
22 | */
23 | export interface ResponseStyleBuilderProps {
24 | voicePrompt: string;
25 | voiceReprompt?: string;
26 | responseStyle: ResponseStyle;
27 | builder: ControlResponseBuilder;
28 | }
29 |
30 | /**
31 | * Adds voice prompt and reprompt to a ControlResponseBuilder conditionally based on
32 | * the ResponseStyle contained in the ResponseStyleBuilderProps.
33 | *
34 | * If the modality of the response style is SCREEN, the voice prompt and reprompt
35 | * will not be added. In any other case, they will be added.
36 | *
37 | * @param props - ResponseStyleBuilderProps - properties needed for adding prompts to
38 | * the response.
39 | */
40 | export function addFragmentsForResponseStyle(props: ResponseStyleBuilderProps): void {
41 | const voicePrompt = props.voicePrompt;
42 | const voiceReprompt = props.voiceReprompt;
43 | const responseStyle = props.responseStyle;
44 |
45 | const builder = props.builder;
46 | if (responseStyle.modality === OutputModality.SCREEN) {
47 | return;
48 | }
49 |
50 | builder.addPromptFragment(voicePrompt);
51 |
52 | if (voiceReprompt !== undefined) {
53 | builder.addRepromptFragment(voiceReprompt);
54 | }
55 | }
56 |
57 | /**
58 | * Adds voice prompt and reprompt to a ControlResponseBuilder conditionally based on
59 | * the ResponseStyle contained in the ResponseStyleBuilderProps.
60 | *
61 | * If the modality of the response style is SCREEN, the voice prompt and reprompt
62 | * will not be added. In any other case, they will be added.
63 | *
64 | * After adding the prompts, the response is built.
65 | *
66 | * @param props - ResponseStyleBuilderProps - properties needed for adding prompts to
67 | * the response.
68 | * @returns Response - The response for the current skill turn.
69 | */
70 | export function buildResponseForStyle(props: ResponseStyleBuilderProps): Response {
71 | addFragmentsForResponseStyle(props);
72 |
73 | return props.builder.build();
74 | }
75 |
76 | /**
77 | * Executes a ResponseStyleEvaluator override function and determines whether to use
78 | * the result from it or the base, non-overridden result.
79 | * @param evaluator - ResponseStyleEvaluator - Overridden ResponseStyleEvaluator
80 | * @param input - ControlInput - Input for the current skill turn
81 | * @returns ResponseStyle - Uses the result from the provided evaluator if it was
82 | * determinate, otherwise uses the ResponseStyle present in the provided ControlInput.
83 | */
84 | export function getDeterminateResponseStyle(
85 | evaluator: ResponseStyleEvaluator,
86 | input: ControlInput,
87 | ): ResponseStyle {
88 | const overriddenResponseStyle = evaluator(input.handlerInput, input.inputModalityHistory);
89 |
90 | if (overriddenResponseStyle.modality !== OutputModality.INDETERMINATE) {
91 | return overriddenResponseStyle;
92 | }
93 |
94 | return input.suggestedResponseStyle;
95 | }
96 |
97 | /**
98 | * Overrides the default session behavior depending on the requested response style.
99 | * If the response style includes a screen output modality, the session will enter an idle state.
100 | * In all other cases, the default behavior is preserved.
101 | * @param resultBuilder - ControlResultBuilder - The ControlResultBuilder for the current skill turn
102 | * @param responseStyle - ResponseStyle - The ResponseStyle for the current skill turn
103 | */
104 | export function setSessionBehaviorForStyle(
105 | resultBuilder: ControlResultBuilder,
106 | responseStyle: ResponseStyle,
107 | ): void {
108 | if (responseStyle.modality === OutputModality.SCREEN) {
109 | resultBuilder.enterIdleState();
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/utils/SerializationValidator.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import _ from 'lodash';
15 | import { ControlServices } from '../controls/ControlServices';
16 | import { IControl } from '../controls/interfaces/IControl';
17 | import { IControlInput } from '../controls/interfaces/IControlInput';
18 | import { IControlManager } from '../controls/interfaces/IControlManager';
19 | import { _extractStateFromControlTree } from '../runtime/ControlHandler';
20 |
21 | const MODULE_NAME = 'AskSdkControls:SerializationValidator';
22 |
23 | /**
24 | * Validates that the serialized state will survive the round-trip successfully.
25 | *
26 | * If round-trip fails, diagnostic information is printed to the console.
27 | * @param serializedState - Serialized state (a string in JSON format)
28 | * @param controlManager - Control manager
29 | * @param input - Input
30 | * @throws Error if round-trip fails.
31 | */
32 | export function validateSerializedState(
33 | serializedState: string,
34 | controlManager: IControlManager,
35 | input: IControlInput,
36 | ): void {
37 | const log = ControlServices.getLogger(MODULE_NAME);
38 |
39 | // perform deserialization
40 | const deserializedState = JSON.parse(serializedState);
41 | const rebuiltTopControl: IControl = controlManager.createControlTree();
42 | controlManager.reestablishControlStates(rebuiltTopControl, deserializedState);
43 |
44 | // and then re-serialize to complete the round trip
45 | const roundTrippedState = _extractStateFromControlTree(rebuiltTopControl);
46 | const roundTrippedUISerialized = JSON.stringify(roundTrippedState, null, 2);
47 |
48 | if (!_.isEqual(deserializedState, roundTrippedState)) {
49 | log.info(
50 | 'serializedState did not survive the simulated round trip (deserialization into controlTree and re-serialization).',
51 | );
52 | const lines1 = serializedState.split('\n');
53 | const lines2 = roundTrippedUISerialized.split('\n');
54 | if (lines1.length === lines2.length) {
55 | log.info('=================================================');
56 | for (const [i, line1] of lines1.entries()) {
57 | const line2 = lines2[i];
58 | if (line1 !== line2) {
59 | log.info(`${i}: Expected: ${log.sanitize(line1)}`);
60 | log.info(`${i}: Actual: ${log.sanitize(line2)}`);
61 | }
62 | }
63 | log.info('=================================================');
64 | } else {
65 | log.info('Diff is complicated.. use a text differ on the following');
66 | log.info('=================================================');
67 | log.info(log.sanitize(serializedState));
68 | log.info('=================================================');
69 | log.info('=================================================');
70 | log.info(log.sanitize(roundTrippedUISerialized));
71 | log.info('=================================================');
72 |
73 | log.info('First diff:');
74 | for (const [i, line1] of lines1.entries()) {
75 | const line2 = lines2[i];
76 | if (line1 !== line2) {
77 | log.info(`${i}: Expected: ${log.sanitize(line1)}`);
78 | log.info(`${i}: Actual: ${log.sanitize(line2)}`);
79 | break;
80 | }
81 | }
82 | }
83 |
84 | throw new Error(
85 | 'Problem round-tripping JSON serialization (see logs above to obtain diff). Do your custom controls have fromJson, new up correct type and pass all properties?',
86 | );
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/utils/testSupport/SkillInvoker.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the 'License').
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the 'license' file accompanying this file. This file is distributed
9 | * on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { RequestHandler, Skill } from 'ask-sdk-core';
15 | import { Directive, Response, ResponseEnvelope, ui } from 'ask-sdk-model';
16 | import { IControlInput } from '../../controls/interfaces/IControlInput';
17 | import { wrapRequestHandlerAsSkill } from './SkillWrapper';
18 |
19 | type TSessionAttributes = { [key: string]: any } | undefined;
20 |
21 | /**
22 | * Utility to invoke a Skill or RequestHandler for testing.
23 | *
24 | * This wrapper mimics the lifecycle of an ASK Skill using AWS Lambda
25 | * that is repeatedly invoked as a 'stateless' function which receives actual state
26 | * in the session attributes of the Request object.
27 | *
28 | */
29 | export class SkillInvoker {
30 | private sessionAttributes: TSessionAttributes;
31 | private skill: Skill;
32 |
33 | constructor(skillOrRequestHandler: Skill | RequestHandler) {
34 | this.skill =
35 | (skillOrRequestHandler as any).handle !== undefined
36 | ? wrapRequestHandlerAsSkill(skillOrRequestHandler as RequestHandler)
37 | : (skillOrRequestHandler as Skill);
38 | }
39 |
40 | /**
41 | * Invoke the skill with a control-input object.
42 | * The control input is first converted to a RequestEnvelope(IntentRequest)
43 | *
44 | * @param input - Input
45 | * @returns [prompt ssml inner-text, reprompt ssml inner-text]
46 | */
47 | public async invoke(input: IControlInput): Promise {
48 | const envelope = input.handlerInput.requestEnvelope;
49 | envelope.session!.attributes = this.sessionAttributes; // populate saved state
50 |
51 | // ********** INVOKE SKILL ****************
52 | const responseEnvelope = await this.skill.invoke(envelope, undefined);
53 |
54 | this.sessionAttributes = responseEnvelope.sessionAttributes; // save updated state
55 |
56 | const promptSSML = (responseEnvelope.response?.outputSpeech as ui.SsmlOutputSpeech)?.ssml;
57 | const prompt = promptSSML?.replace('', '').replace('', '');
58 |
59 | const repromptSSML = (
60 | (responseEnvelope.response?.reprompt as ui.Reprompt)?.outputSpeech as ui.SsmlOutputSpeech
61 | )?.ssml;
62 | const reprompt = repromptSSML?.replace('', '').replace('', '');
63 |
64 | const directive: Directive[] | undefined = responseEnvelope.response?.directives;
65 | // const cardContent = (responseEnv.response.card as ui.SimpleCard).content;
66 | return {
67 | responseEnvelope,
68 | response: responseEnvelope.response,
69 | prompt,
70 | reprompt,
71 | directive,
72 | };
73 | }
74 | }
75 |
76 | export interface TestResponseObject {
77 | responseEnvelope: ResponseEnvelope;
78 | response?: Response;
79 | prompt: string;
80 | reprompt?: string;
81 | directive?: Directive[];
82 | }
83 |
--------------------------------------------------------------------------------
/src/utils/testSupport/SkillWrapper.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { ErrorHandler, HandlerInput, RequestHandler, Skill, SkillBuilders } from 'ask-sdk-core';
15 | import { ControlServices } from '../../controls/ControlServices';
16 |
17 | const MODULE_NAME = 'AskSdkControls:SkillWrapper';
18 |
19 | /**
20 | * Wraps a single RequestHandler as a Skill for testing.
21 | *
22 | * The resulting Skill has a single requestHandler (the one provided) and a
23 | * default error handler that logs any internal error that may occur.
24 | *
25 | * @param requestHandler - Request handler
26 | */
27 | export function wrapRequestHandlerAsSkill(requestHandler: RequestHandler): Skill {
28 | const log = ControlServices.getLogger(MODULE_NAME);
29 | const errorHandler: ErrorHandler = {
30 | canHandle() {
31 | return true;
32 | },
33 | handle(handlerInput: HandlerInput, error: Error) {
34 | log.error(`~~~~ Error handled: ${error.stack}`);
35 | const speakOutput = `${error.message}`;
36 |
37 | return handlerInput.responseBuilder.speak(speakOutput).reprompt(speakOutput).getResponse();
38 | },
39 | };
40 |
41 | /*
42 | * The SkillBuilder acts as the entry point for your skill, routing all request and response
43 | * payloads to the handlers above. Make sure any new handlers or interceptors you've
44 | * defined are included below. The order matters - they're processed top to bottom.
45 | */
46 |
47 | const _handler = SkillBuilders.custom().addRequestHandlers(requestHandler).addErrorHandlers(errorHandler);
48 | const skill = _handler.create();
49 | return skill;
50 | }
51 |
--------------------------------------------------------------------------------
/test/CustomSerdeTests.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { expect } from 'chai';
15 | import { suite, test } from 'mocha';
16 | import { Control, SystemAct, TestInput, testTurn, waitForDebugger } from '../src';
17 | import { ControlProps, ControlState } from '../src/controls/Control';
18 | import { ControlInput } from '../src/controls/ControlInput';
19 | import { ControlManager } from '../src/controls/ControlManager';
20 | import { ControlResultBuilder } from '../src/controls/ControlResult';
21 | import { GeneralControlIntent } from '../src/intents/GeneralControlIntent';
22 | import { ControlResponseBuilder } from '../src/responseGeneration/ControlResponseBuilder';
23 | import { ControlHandler } from '../src/runtime/ControlHandler';
24 | import { SkillInvoker } from '../src/utils/testSupport/SkillInvoker';
25 |
26 | waitForDebugger();
27 |
28 | /*
29 | * Tests a control that has state (Set) that doesn't automatically serialize with JSON.stringify.
30 | *
31 | * The test shows the control changing state and being serialized and deserialized.
32 | * To accomplish this, the state object implements toJSON() and assign()
33 | * - the name and signature for toJSON() is defined by JSON.stringify and called automatically by JSON.stringify
34 | * - the name and signature for fromJSON() is defined by the Control framework and used as an alternative to Object.assign() when present.
35 |
36 | */
37 | suite('== Custom Serde ==', () => {
38 | test('explicit confirmation and disaffirm', async () => {
39 | // Note: this test demonstrates using testTurn to run a multi-turn scenario with assertions between turns.
40 | const requestHandler = new ControlHandler(new CustomSerdeControlManager());
41 | const invoker = new SkillInvoker(requestHandler);
42 | await testTurn(invoker, 'U: ', TestInput.of(GeneralControlIntent.of({})), 'A:');
43 |
44 | expect((requestHandler.getSerializableControlStates().customControl as string[])[0]).exist.and.equals(
45 | 'x',
46 | );
47 |
48 | await testTurn(invoker, 'U: ', TestInput.of(GeneralControlIntent.of({})), 'A:');
49 |
50 | expect((requestHandler.getSerializableControlStates().customControl as string[])[0]).exist.and.equals(
51 | 'y',
52 | );
53 | });
54 | });
55 |
56 | class CustomSerdeControlManager extends ControlManager {
57 | createControlTree(): Control {
58 | return new CustomSerdeControl({
59 | id: 'customControl',
60 | });
61 | }
62 | }
63 |
64 | class CustomSerdeControlState implements ControlState {
65 | value: Set;
66 |
67 | lastInitiative: {
68 | actname?: string;
69 | };
70 | constructor(value: Set) {
71 | this.value = value;
72 | this.lastInitiative = {};
73 | }
74 | }
75 |
76 | class CustomSerdeControl extends Control {
77 | state: CustomSerdeControlState;
78 |
79 | constructor(props: ControlProps, state?: CustomSerdeControlState) {
80 | super(props.id);
81 | this.state = state ?? new CustomSerdeControlState(new Set());
82 | }
83 |
84 | canHandle(input: ControlInput): boolean {
85 | return true;
86 | }
87 |
88 | async handle(input: ControlInput, resultBuilder: ControlResultBuilder): Promise {
89 | if (this.state.value.size === 0) {
90 | this.state.value.add('x');
91 | } else if (this.state.value.size === 1 && this.state.value.has('x')) {
92 | this.state.value.delete('x');
93 | this.state.value.add('y');
94 | }
95 |
96 | return;
97 | }
98 |
99 | canTakeInitiative(input: ControlInput): boolean {
100 | return false;
101 | }
102 | async takeInitiative(input: ControlInput, resultBuilder: ControlResultBuilder): Promise {
103 | throw new Error('Method not implemented.');
104 | }
105 |
106 | getSerializableState(): string[] {
107 | // render the set as a simple list.
108 | return [...this.state.value.keys()];
109 | }
110 |
111 | setSerializableState(obj: string[]) {
112 | if (obj !== undefined) {
113 | // refreshes the set from the serialized array
114 | this.state.value = new Set();
115 | for (const x of obj) {
116 | this.state.value.add(x);
117 | }
118 | }
119 | }
120 |
121 | async renderAct(act: SystemAct, input: ControlInput, builder: ControlResponseBuilder): Promise {
122 | throw new Error('Method not implemented.');
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/test/game_strings.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | export namespace GameStrings {
15 | export enum SlotType {
16 | DomainValue = 'DomainValue',
17 | }
18 |
19 | export enum Target {
20 | Name = 'name',
21 | AvatarName = 'avatarName',
22 | PetName = 'petName',
23 | Age = 'age',
24 | CharClass = 'class',
25 | Species = 'species',
26 | }
27 |
28 | export enum ID {
29 | GameContainer = 'gameContainer',
30 |
31 | PlayerContainer = 'playerContainer',
32 | PlayerName = 'playerName',
33 | PlayerAge = 'playerAge',
34 | PlayerClass = 'playerClass',
35 |
36 | PetContainer = 'petContainer',
37 | PetName = 'petName',
38 | PetSpecies = 'petSpecies',
39 | }
40 |
41 | export enum Value {
42 | Elf = 'elf',
43 | Dwarf = 'dwarf',
44 | Human = 'human',
45 | Cat = 'cat',
46 | Dog = 'dog',
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/mock/inputInteractionModel.json:
--------------------------------------------------------------------------------
1 | {
2 | "interactionModel": {
3 | "languageModel": {
4 | "modelConfiguration": {
5 | "fallbackIntentSensitivity": { "level": "HIGH" }
6 | },
7 | "invocationName": "mock skill",
8 | "intents": [
9 | {
10 | "name": "AMAZON.CancelIntent",
11 | "samples": []
12 | },
13 | {
14 | "name": "HelloWorldIntent",
15 | "slots": [],
16 | "samples": ["sample one", "sample two"]
17 | }
18 | ],
19 | "types": []
20 | },
21 | "prompts": []
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/modality.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { suite, test } from 'mocha';
15 | import { expect } from 'chai';
16 | import {
17 | InputModality,
18 | ModalityEvaluationDefaults,
19 | OutputModality,
20 | ResponseStyle,
21 | } from '../src/modality/ModalityEvaluation';
22 | import { HandlerInput } from 'ask-sdk-core';
23 | import { TestInput } from '../src/utils/testSupport/TestingUtils';
24 | import { ControlHandler } from '../src/runtime/ControlHandler';
25 | import { SkillInvoker } from '../src/utils/testSupport/SkillInvoker';
26 | import { ControlManager, ControlManagerProps } from '../src/controls/ControlManager';
27 | import { ControlInput } from '../src/controls/ControlInput';
28 | import { ContainerControl } from '../src/controls/ContainerControl';
29 | import { ControlResultBuilder } from '../src/controls/ControlResult';
30 | import UserEvent = interfaces.alexa.presentation.apl.UserEvent;
31 | import { interfaces } from 'ask-sdk-model';
32 |
33 | suite('== Modality evaluation and tracking scenarios ==', () => {
34 | const touchInput = TestInput.simpleUserEvent([]);
35 | const voiceInput = TestInput.of('VoiceIntent');
36 | const editTextInput = TestInput.userEvent(userEventWithSource('EditText'));
37 | const vectorGraphicInput = TestInput.userEvent(userEventWithSource('EditText'));
38 | let manager: TestControlManager;
39 | let handler: ControlHandler;
40 | let rootControl: TestControl;
41 | let lastHistory: InputModality[] | undefined;
42 | let invoker: SkillInvoker;
43 |
44 | beforeEach(() => {
45 | manager = new TestControlManager({
46 | responseStyleEvaluator,
47 | });
48 | handler = new ControlHandler(manager);
49 | rootControl = manager.createControlTree();
50 | rootControl.lastKnownModality = undefined;
51 | lastHistory = undefined;
52 | invoker = new SkillInvoker(handler);
53 | });
54 |
55 | test('Input and output modality are properly evaluated', async () => {
56 | await invoker.invoke(touchInput);
57 | expect(await rootControl.lastKnownModality).to.equal(OutputModality.SCREEN);
58 |
59 | await invoker.invoke(editTextInput);
60 | expect(await rootControl.lastKnownModality).to.equal(OutputModality.SCREEN);
61 |
62 | await invoker.invoke(vectorGraphicInput);
63 | expect(await rootControl.lastKnownModality).to.equal(OutputModality.SCREEN);
64 |
65 | await invoker.invoke(voiceInput);
66 | expect(await rootControl.lastKnownModality).to.equal(OutputModality.VOICE);
67 | });
68 |
69 | test('Input modality history is properly tracked', async () => {
70 | await invoker.invoke(touchInput);
71 | await invoker.invoke(voiceInput);
72 | expect(lastHistory).to.deep.equal(['touch', 'voice']);
73 | });
74 |
75 | function responseStyleEvaluator(input: HandlerInput, history: InputModality[]): ResponseStyle {
76 | lastHistory = JSON.parse(JSON.stringify(history));
77 | return ModalityEvaluationDefaults.defaultResponseStyleEvaluator(input, history);
78 | }
79 |
80 | function userEventWithSource(source: string): UserEvent {
81 | return {
82 | type: 'Alexa.Presentation.APL.UserEvent',
83 | requestId: 'amzn1.echo-api.request.1234567890',
84 | timestamp: '2019-10-04T18:48:22Z',
85 | locale: 'en-US',
86 | components: {},
87 | source: {
88 | type: source,
89 | handler: 'Press',
90 | id: 'TestComponent',
91 | },
92 | token: 'testToken',
93 | };
94 | }
95 | });
96 |
97 | class TestControlManager extends ControlManager {
98 | constructor(props?: ControlManagerProps) {
99 | super(props);
100 | }
101 |
102 | createControlTree(state?: any, input?: ControlInput): TestControl {
103 | return TestControl.getInstance();
104 | }
105 | }
106 |
107 | class TestControl extends ContainerControl {
108 | private static instance = new TestControl();
109 |
110 | lastKnownModality: OutputModality | undefined = undefined;
111 |
112 | private constructor() {
113 | super({ id: 'TestControl' });
114 | }
115 |
116 | async canHandle(input: ControlInput): Promise {
117 | this.lastKnownModality = input.suggestedResponseStyle.modality;
118 | return true;
119 | }
120 |
121 | async handle(input: ControlInput, resultBuilder: ControlResultBuilder): Promise {
122 | // Do nothing
123 | }
124 |
125 | static getInstance() {
126 | return this.instance;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/test/systemActs/ContentActs.spec.ts:
--------------------------------------------------------------------------------
1 | import { suite, test } from 'mocha';
2 | import { ContainerControl } from '../../src/controls/ContainerControl';
3 | import { Control } from '../../src/controls/Control';
4 | import { ControlManager } from '../../src/controls/ControlManager';
5 | import { ControlHandler } from '../../src/runtime/ControlHandler';
6 | import { SkillInvoker, TestResponseObject } from '../../src/utils/testSupport/SkillInvoker';
7 | import { TestInput, testTurn } from '../../src/utils/testSupport/TestingUtils';
8 | import { ControlInput } from '../../src/controls/ControlInput';
9 | import { ControlResultBuilder } from '../../src/controls/ControlResult';
10 | import { CanFulfillIntentAct } from '../../src/systemActs/ContentActs';
11 | import { canfulfill } from 'ask-sdk-model';
12 |
13 | suite('Test CanFulfillIntentRequest', () => {
14 | const canFulfillIntent: canfulfill.CanFulfillIntent = {
15 | canFulfill: 'YES',
16 | slots: {
17 | Artist: {
18 | canUnderstand: 'YES',
19 | canFulfill: 'YES',
20 | },
21 | Song: {
22 | canUnderstand: 'YES',
23 | canFulfill: 'YES',
24 | },
25 | DedicatedPerson: {
26 | canUnderstand: 'YES',
27 | canFulfill: 'YES',
28 | },
29 | },
30 | };
31 | class TestControlManager extends ControlManager {
32 | createControlTree(): Control {
33 | return new CustomRootControl({ id: 'root' });
34 | }
35 | }
36 |
37 | class CustomRootControl extends ContainerControl {
38 | async canHandle(input: ControlInput): Promise {
39 | return input.request.type === 'CanFulfillIntentRequest';
40 | }
41 |
42 | async handle(input: ControlInput, resultBuilder: ControlResultBuilder): Promise {
43 | if (input.request.type === 'CanFulfillIntentRequest') {
44 | resultBuilder.addAct(
45 | new CanFulfillIntentAct(this, {
46 | intent: canFulfillIntent,
47 | }),
48 | );
49 | }
50 | return;
51 | }
52 | }
53 |
54 | test('CanFulfillIntentRequest', async () => {
55 | const requestHandler = new ControlHandler(new TestControlManager());
56 | const invoker = new SkillInvoker(requestHandler);
57 | const intentName = 'SendSongRequest';
58 | const expectedResponse: TestResponseObject = {
59 | responseEnvelope: {
60 | version: '',
61 | response: {
62 | canFulfillIntent,
63 | },
64 | },
65 | prompt: '',
66 | };
67 | const slots = {
68 | Artist: {
69 | name: 'Artist',
70 | confirmationStatus: 'NONE',
71 | },
72 | Song: {
73 | name: 'Song',
74 | confirmationStatus: 'NONE',
75 | },
76 | DedicatedPerson: {
77 | name: 'DedicatedPerson',
78 | confirmationStatus: 'NONE',
79 | },
80 | };
81 | await testTurn(
82 | invoker,
83 | 'U: __',
84 | TestInput.canFulfillIntentRequest(intentName, slots),
85 | expectedResponse,
86 | );
87 | });
88 | });
89 |
--------------------------------------------------------------------------------
/test/utilsTest/IntentUtils.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { expect } from 'chai';
15 | import { suite, test } from 'mocha';
16 | import { defaultIntentToValueMapper, IntentBuilder } from '../../src/utils/IntentUtils';
17 |
18 | suite('== IntentUtils.IntentNameToValueMapper ==', () => {
19 | test("WeatherIntent -> 'weather'", async () => {
20 | expect('weather').equal(defaultIntentToValueMapper(IntentBuilder.of('weatherIntent')));
21 | });
22 |
23 | test("GetWeatherIntent -> 'getWeather'", async () => {
24 | expect('getWeather').equal(defaultIntentToValueMapper(IntentBuilder.of('GetWeatherIntent')));
25 | });
26 |
27 | test("AMAZON.YesIntent -> 'yes'", async () => {
28 | expect('yes').equal(defaultIntentToValueMapper(IntentBuilder.of('AMAZON.YesIntent')));
29 | });
30 |
31 | test("AMAZON.ShuffleOffIntent -> 'shuffleOff'", async () => {
32 | expect('shuffleOff').equal(defaultIntentToValueMapper(IntentBuilder.of('AMAZON.ShuffleOffIntent')));
33 | });
34 |
35 | test("Namespace1.namespace2.ThingIntent -> 'thing'", async () => {
36 | expect('thing').equal(
37 | defaultIntentToValueMapper(IntentBuilder.of('Namespace1.namespace2.ThingIntent')),
38 | );
39 | });
40 |
41 | test("SomethingRandom -> 'somethingRandom'", async () => {
42 | expect('somethingRandom').equal(defaultIntentToValueMapper(IntentBuilder.of('SomethingRandom')));
43 | });
44 |
45 | test('weatherIntent -> weather', async () => {
46 | expect('weather').equal(defaultIntentToValueMapper(IntentBuilder.of('weatherIntent')));
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/test/utilsTest/ResponseUtils.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { ResponseFactory } from 'ask-sdk-core';
15 | import { Response, ui } from 'ask-sdk-model';
16 | import { expect } from 'chai';
17 | import { suite, test } from 'mocha';
18 | import { ControlResultBuilder } from '../../src';
19 | import { OutputModality } from '../../src/modality/ModalityEvaluation';
20 | import { ControlResponseBuilder } from '../../src/responseGeneration/ControlResponseBuilder';
21 | import { SessionBehavior } from '../../src/runtime/SessionBehavior';
22 | import { buildResponseForStyle, setSessionBehaviorForStyle } from '../../src/utils/ResponseUtils';
23 |
24 | suite('== ResponseUtils ==', () => {
25 | let builder: ControlResponseBuilder;
26 |
27 | beforeEach(() => {
28 | builder = new ControlResponseBuilder(ResponseFactory.init());
29 | });
30 |
31 | suite('== ResponseUtils.buildResponseForStyle ==', () => {
32 | const voicePrompt = 'voicePrompt';
33 | const voiceReprompt = 'voiceReprompt';
34 |
35 | test('Voice prompts and reprompts are ignored for screen modality', async () => {
36 | const { voicePromptResponse, voiceRepromptResponse } = getSsmlFromResponse(
37 | buildResponseForStyle({
38 | voicePrompt,
39 | voiceReprompt,
40 | responseStyle: { modality: OutputModality.SCREEN },
41 | builder,
42 | }),
43 | );
44 |
45 | expect(voicePromptResponse).equal(getSsmlString());
46 | expect(voiceRepromptResponse).equal(getSsmlString());
47 | });
48 |
49 | test('Voice prompts are included for voice modality', async () => {
50 | const { voicePromptResponse, voiceRepromptResponse } = getSsmlFromResponse(
51 | buildResponseForStyle({
52 | voicePrompt,
53 | responseStyle: { modality: OutputModality.VOICE },
54 | builder,
55 | }),
56 | );
57 |
58 | expect(voicePromptResponse).equal(getSsmlString(voicePrompt));
59 | expect(voiceRepromptResponse).equal(getSsmlString());
60 | });
61 |
62 | test('Voice reprompts are included for voice modality', async () => {
63 | const { voicePromptResponse, voiceRepromptResponse } = getSsmlFromResponse(
64 | buildResponseForStyle({
65 | voicePrompt,
66 | voiceReprompt,
67 | responseStyle: { modality: OutputModality.VOICE },
68 | builder,
69 | }),
70 | );
71 |
72 | expect(voicePromptResponse).equal(getSsmlString(voicePrompt));
73 | expect(voiceRepromptResponse).equal(getSsmlString(voiceReprompt));
74 | });
75 |
76 | function getSsmlString(prompt?: string) {
77 | return `${prompt ?? ''}`;
78 | }
79 |
80 | function getSsmlFromResponse(response: Response) {
81 | return {
82 | voicePromptResponse: (response.outputSpeech as ui.SsmlOutputSpeech).ssml,
83 | voiceRepromptResponse: (response.reprompt?.outputSpeech as ui.SsmlOutputSpeech)?.ssml,
84 | };
85 | }
86 | });
87 |
88 | suite('== ResponseUtils.setSessionBehaviorForStyle ==', () => {
89 | test('Session state is OPEN when output modality is voice', async () => {
90 | const builder = new ControlResultBuilder();
91 | setSessionBehaviorForStyle(builder, { modality: OutputModality.VOICE });
92 | expect(builder.build().sessionBehavior).to.equal(SessionBehavior.OPEN);
93 | });
94 |
95 | test('Session state is IDLE when output modality is screen', async () => {
96 | const builder = new ControlResultBuilder();
97 | setSessionBehaviorForStyle(builder, { modality: OutputModality.SCREEN });
98 | expect(builder.build().sessionBehavior).to.equal(SessionBehavior.IDLE);
99 | });
100 | });
101 | });
102 |
--------------------------------------------------------------------------------
/test/utilsTest/arrayUtils.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License").
4 | * You may not use this file except in compliance with the License.
5 | * A copy of the License is located at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * or in the "license" file accompanying this file. This file is distributed
9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10 | * express or implied. See the License for the specific language governing
11 | * permissions and limitations under the License.
12 | */
13 |
14 | import { expect } from 'chai';
15 | import { suite, test } from 'mocha';
16 | import { moveArrayItem, randomlyPick } from '../../src/utils/ArrayUtils';
17 |
18 | suite('== ArrayUtils.randomlyPick() ==', () => {
19 | test('ensure distribution is reasonable', async () => {
20 | const array = [0, 1, 2];
21 | const histogram: number[] = [0, 0, 0, 0, 0, 0, 0];
22 |
23 | for (let i = 0; i < 30000; i++) {
24 | histogram[randomlyPick(array)]++;
25 | }
26 |
27 | for (let i = 0; i < array.length; i++) {
28 | const relativeDiff = Math.abs(histogram[0] / histogram[i] - 1.0);
29 | expect(relativeDiff).lessThan(0.1); // conservative comparison to reduce false negatives
30 | }
31 | });
32 | });
33 |
34 | suite('== ArrayUtils.reorder() ==', () => {
35 | test('basics', async () => {
36 | const array = [0, 1, 2];
37 | expect(moveArrayItem([0, 1, 2, 3, 4], 1, 0)).deep.equal([1, 0, 2, 3, 4]);
38 | expect(moveArrayItem([0, 1, 2, 3, 4], 0, 2)).deep.equal([1, 2, 0, 3, 4]);
39 | expect(moveArrayItem([0, 1, 2, 3, 4], 0, 5)).deep.equal([1, 2, 3, 4, 0]);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017", // es2017 is lowest with JS await keyword which makes async debugging much nicer.
4 | "outDir": "dist",
5 | "rootDir": "./",
6 | "lib": [], // do not add any libs here. See https://github.com/Microsoft/TypeScript/issues/27416
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "strict": true,
10 | "alwaysStrict": true,
11 | "strictPropertyInitialization": false,
12 | "noImplicitReturns": true,
13 | "noFallthroughCasesInSwitch": true,
14 | "downlevelIteration": true,
15 | "inlineSourceMap": true,
16 | "inlineSources": true,
17 | "declaration": true,
18 | "declarationMap": true,
19 | "composite": true,
20 | "esModuleInterop": true,
21 | "forceConsistentCasingInFileNames": true,
22 | "importHelpers": true
23 | },
24 | "include": [
25 | "src",
26 | "test",
27 | "demo"
28 | ],
29 | "exclude": [
30 | "doc",
31 | "node_modules"
32 | ],
33 | "typedocOptions": {
34 | "name": "ASK SDK Controls Framework",
35 | "readme": "./README.md",
36 | "target": "ES2018",
37 | "module": "commonjs",
38 | "out": "typedoc",
39 | "theme": "node_modules/typedoc-neo-theme/bin/default",
40 | "excludeExternals": true,
41 | "hideGenerator": true,
42 | "excludePrivate": true,
43 | "excludeNotExported": true,
44 | "links": [
45 |
46 | ],
47 | "outline": [{
48 | "Controls Framework (Major types)": {
49 | "Control": "classes/_controls_control_.control",
50 | "ContainerControl": "modules/_controls_containercontrol_",
51 | "DynamicContainerControl": "modules/_controls_dynamiccontainercontrol_",
52 | "ControlManager": "modules/_controls_controlmanager_",
53 | "ComponentModeControlManager": "modules/_controls_componentmodecontrolmanager_",
54 | "ControlInput": "classes/_controls_controlinput_.controlinput",
55 | "ControlResultBuilder": "classes/_controls_controlresult_.controlresultbuilder",
56 | "ControlResponseBuilder": "classes/_responsegeneration_controlresponsebuilder_.controlresponsebuilder"
57 | },
58 | "System acts": {
59 | "SystemAct": "classes/_systemacts_systemact_.systemact",
60 | "InitiativeAct": "modules/_systemacts_initiativeacts_",
61 | "ContentAct": "modules/_systemacts_contentacts_"
62 | },
63 | "Controls runtime": {
64 | "ControlHandler": "classes/_runtime_controlhandler_.controlhandler"
65 | },
66 | "Controls Library": {
67 | "ValueControl": "modules/_commoncontrols_valuecontrol_",
68 | "ListControl": "modules/_commoncontrols_listcontrol_listcontrol_",
69 | "DateControl": "modules/_commoncontrols_datecontrol_",
70 | "DateRangeControl": "modules/_commoncontrols_daterangecontrol_daterangecontrol_",
71 | "NumberControl": "modules/_commoncontrols_numbercontrol_numbercontrol_",
72 | "QuestionnaireControl": "modules/_commoncontrols_questionnairecontrol_questionnairecontrol_",
73 | "MultiValueListControl": "modules/_commoncontrols_multivaluelistcontrol_multivaluelistcontrol_"
74 | },
75 | "Interaction Model Generation": {
76 | "ControlInteractionModelGenerator": "classes/_interactionmodelgeneration_controlinteractionmodelgenerator_.controlinteractionmodelgenerator",
77 | "GeneralControlIntent": "classes/_intents_generalcontrolintent_.generalcontrolintent",
78 | "ValueControlIntent": "classes/_intents_valuecontrolintent_.valuecontrolintent",
79 | "OrdinalControlIntent": "classes/_intents_ordinalcontrolintent_.ordinalcontrolintent",
80 | "ConjunctionControlIntent": "classes/_intents_conjunctioncontrolintent_.conjunctioncontrolintent",
81 | "DateRangeControlIntent": "classes/_intents_daterangecontrolintent_.daterangecontrolintent"
82 |
83 | },
84 | "Test support": {
85 | "SkillInvoker": "classes/_utils_testsupport_skillinvoker_.skillinvoker",
86 | "SkillWrapper": "modules/_utils_testsupport_skillwrapper_",
87 | "TestingUtils": "modules/_utils_testsupport_testingutils_"
88 | },
89 | "Utilities (selected)": {
90 | "InputUtils": "modules/_utils_inpututil_.inpututil",
91 | "IntentUtils": "modules/_utils_intentutils_",
92 | "Predicates": "modules/_utils_predicates_"
93 | }
94 | }]
95 | }
96 | }
--------------------------------------------------------------------------------