Amazon Alexa

The Amazon Alexa platform integration allows you to build custom Alexa Skills using Jovo.

Introduction

Apps for Alexa are called Alexa Skills. The official suite of services, frameworks, and APIs to build Alexa Skills provided by Amazon is called Alexa Skills Kit (ASK). You can find a general introduction into building Alexa Skills in the official Alexa documentation.

In the installation section, we're going to set up a Jovo project that works with Amazon Alexa.

An Alexa Skill usually consists of two parts:

  • The Alexa Skill project in the Alexa Developer Console
  • The code that handles the logic of your Skill

In the Alexa Developer Console, the Alexa Skill project is configured, including an Alexa Interaction Model that trains Alexa's language understanding service. Learn more about how to use the Jovo CLI to create and deploy Alexa Skill projects in the Alexa Developer Console project section.

If a user converses with your Skill, Alexa sends API requests to your Skill's code endpoint. The code is then responsible for returning an appropriate response. Learn more about how you can build this with the Jovo Framework in the Alexa Skill code section.

Jovo is a framework that allows you to build apps that work across devices and platforms. However, this does not mean that you can't build highly complex Alexa Skills with Jovo. Any custom Alexa Skill that can be built with the official ASK SDK can also be built with the Jovo Framework. In the platform-specific features section, we're going to take a look at building

Installation

To create a new Alexa project with Jovo, we recommend installing the Jovo CLI, creating a new Jovo project, and selecting Alexa as platform using the CLI wizard. Learn more in our getting started guide.

# Install Jovo CLI globally
$ npm install -g @jovotech/cli

# Start new project wizard
# In the platform step, use the space key to select Alexa
$ jovo new <directory>

If you want to add Alexa to an existing Jovo project, you can install the plugin like this:

$ npm install @jovotech/platform-alexa

Add it as plugin to your app configuration, e.g. app.ts:

import { App } from '@jovotech/framework';
import { AlexaPlatform } from '@jovotech/platform-alexa';
// ...

const app = new App({
  plugins: [
    new AlexaPlatform({
      intentMap: {
        'AMAZON.StopIntent': 'END',
        'AMAZON.CancelIntent': 'END',
      },
      // ...
    }),
    // ...
  ],
});

The Alexa platform comes with its own intentMap that gets merged into the global intentMap. This maps incoming Alexa intents to Jovo intents or handlers.

You can also add the CLI plugin to your project configuration in jovo.project.js. Learn more about the Alexa-specific project configuration here.

const { ProjectConfig } = require('@jovotech/cli-core');
const { AlexaCli } = require('@jovotech/platform-alexa');
// ...

const project = new ProjectConfig({
  // ...
  plugins: [
    new AlexaCli(),
    // ...
  ],
});

The Alexa CLI plugin uses the official ASK (Alexa Skills Kit) CLI provided by Amazon for deployment. For the deployment to work, you need to at least set up a default ASK profile using the ASK CLI. Follow the official Alexa docs to install and configure ASK CLI.

# Install ASK CLI globally
$ npm install -g ask-cli

# Configure ASK profile
$ ask configure

After the successful installation, you can do the following:

Alexa Developer Console Project

Jovo helps you manage your Alexa Skill project in the Alexa Developer Console using the Jovo CLI.

You can use the build command to turn the Alexa project configuration into Alexa specific files.

$ jovo build:platform alexa

These files can be found in a folder called platform.alexa in the build directory of your project. They include the Alexa Interaction Model that is responsible for training Alexa's natural language understanding service.

Since Alexa requires certain built-in intents, make sure that the files in your models folder contain the following elements before running build. They are added by default if you select Alexa as platform in the new CLI wizard.

{
  "alexa": {
    "interactionModel": {
      "languageModel": {
        "intents": [
          {
            "name": "AMAZON.CancelIntent",
            "samples": []
          },
          {
            "name": "AMAZON.HelpIntent",
            "samples": []
          },
          {
            "name": "AMAZON.StopIntent",
            "samples": []
          }
        ]
      }
    }
  }
}

The resulting files can then be deployed to the Alexa Developer Console using the deploy:platform command.

$ jovo deploy:platform alexa

Learn more on the following pages:

Alexa Skill Code

The Jovo Alexa platform package is a platform integration that understands the types of requests Alexa sends and knows how to translate output into an Alexa response. To learn more about the Jovo request lifecycle, take a look at the RIDR documentation.

When a user interacts with your Skill through Alexa, the voice assistant turns user input (usually speech) into structured meaning (usually intents and slots). It then sends a request with this data to you Jovo app. Learn more about the request structure in the official Alexa docs.

The Jovo app then uses this request information to return an appropriate response that tells Alexa what to say (or display) to the user. For example, the code snippet below asks the user if they like pizza:

LAUNCH() {
  return this.$send(YesNoOutput, { message: 'Do you like pizza?' });
}

If you want to learn more about how to return the right response, take a look at these concepts:

The output is then translated into a response that is returned to Alexa. Learn more about the response structure in the official Alexa docs.

Platform-Specific Features

The Alexa platform integration for Jovo supports a lot of platform-specific features. You can access the Alexa object like this:

this.$alexa;

You can also use this object to see if the request is coming from Alexa (or a different platform):

if (this.$alexa) {
  // ...
}

The following Alexa properties offer additional features:

Request

You can access the request using the following methods:

// Generic request type
this.$request;

// Alexa request type
this.$alexa.$request;

There are also some helper methods to help you retrieve information from Alexa requests.

For example, you can retrieve the unit property from the Alexa System object like this:

this.$alexa.$request.getUnit();

/* Result:
  {
    unitId: string;
    persistentUnitId: string;
  }
*/

User

There are various Alexa specific features added to the user class that can be accessed like this:

this.$alexa.$user;

The following features are offered by the Alexa user property:

User Profile

You can call the Alexa Customer Profile API by using the following methods:

await this.$alexa.$user.getEmail();
// Result: string

await this.$alexa.$user.getName();
// Result: string

await this.$alexa.$user.getGivenName();
// Result: string

await this.$alexa.$user.getMobileNumber();
// Result: { countryCode: string; mobileNumber: string; }

Below is an example getEmail handler:

import { AskForPermissionConsentCardOutput } from '@jovotech/platform-alexa';
// ...

async getEmail() {
  try {
    const email = await this.$alexa.$user.getEmail();
    return this.$send({ message: `Your email address is ${email}` });
  } catch(error) {
    if (error.code === 'NO_USER_PERMISSION') {
      return this.$send(AskForPermissionConsentCardOutput, {
        message: 'Please grant access to your email address.',
        permissions: 'alexa::profile:email:read',
        listen: false,
      });
    } else {
      // ...
    }
  }
},

If the getEmail call returns an error with the code NO_USER_PERMISSION, an AskForPermissionsConsent card (learn more in the official Alexa docs) returned to ask the user for permission. For this, the AskForPermissionConsentCardOutput convenience output class is used.

Under the hood, it looks like this:

{
  listen: this.options.listen,
  message: this.options.message,
  platforms: {
    alexa: {
      nativeResponse: {
        response: {
          card: {
            type: 'AskForPermissionsConsent',
            permissions: this.options.permissions,
          },
        },
      },
    },
  },
}

For a Skill to be able to request information from the Customer Profile API, the permissions need to be added to the Skill manifest, either in the Alexa Developer Console or the skill.json file. The latter can be done by using the files property of the Alexa project config:

new AlexaCli({
  files: {
    'skill-package/skill.json': {
      manifest: {
        permissions: [
          'alexa::profile:email:read',
          // ...
        ],
      },
    },
  },
  // ...
});

Person Profile

You can call the Alexa Person Profile API by using the following methods:

await this.$alexa.$user.getSpeakerName();
// Result: string

await this.$alexa.$user.getSpeakerGivenName();
// Result: string

await this.$alexa.$user.getSpeakerMobileNumber();
// Result: { countryCode: string; mobileNumber: string; }

Below is an example getName handler:

import { AskForPermissionConsentCardOutput } from '@jovotech/platform-alexa';
// ...

async getName() {
  try {
    const name = await this.$alexa.$user.getSpeakerName();
    return this.$send({ message: `Your name is ${name}` });
  } catch(error) {
    if (error.code === 'NO_USER_PERMISSION') {
      return this.$send(AskForPermissionConsentCardOutput, {
        message: 'Please grant access to your name.',
        permissions: 'alexa::profile:name:read',
        listen: false,
      });
    } else {
      // ...
    }
  }
},

For a Skill to be able to request information from the Person Profile API, the permissions need to be added to the Skill manifest, either in the Alexa Developer Console or the skill.json file. The latter can be done by using the files property of the Alexa project config:

new AlexaCli({
  files: {
    'skill-package/skill.json': {
      manifest: {
        permissions: [
          'alexa::person_id:read',
          'alexa::profile:name:read',
          // ...
        ],
      },
    },
  },
  // ...
});

Account Linking

Account linking enables you to connect your Alexa Skill users to other systems.

Learn more in the Account Linking documentation for Alexa.

Output

There are various Alexa specific elements that can be added to the output.

Learn more in the Jovo Output documentation for Alexa.

Device

You can check if the device supports APL by using the following method that checks for platform-specific device capabilities:

import { AlexaCapability } from '@jovotech/platform-alexa';
// ...

if (this.$device.supports(AlexaCapability.Apl)) {
  /* ... */
}
// or
if (this.$device.supports('ALEXA:APL')) {
  /* ... */
}

The following Alexa specific capabilities are available:

  • AlexaCapability.Apl or 'ALEXA:APL' for the Alexa.Presentation.APL interface
  • AlexaCapability.Html or 'ALEXA:HTML' for the Alexa.Presentation.HTML interface

There are also various Alexa specific features added to the device class that can be accessed like this:

this.$alexa.$device;

You can access the following properties and methods of the Alexa device class:

Device Location and Address

It is possible to retrieve your Alexa Skill user's address information, if they grant the permission for this. Learn more in the official Alexa docs.

You need to first get the permission, which you can do by sending a card to the user's Alexa app. You can use the AskForPermissionOutput for this:

import { AskForPermissionOutput } from '@jovotech/platform-alexa';
// ...

someHandler() {
  // ...

  try {
    const location = await this.$alexa.$device.getLocation();
    // ...

  } catch(error) {
    if (error.code === 'NO_USER_PERMISSION') {
      return this.$send(AskForPermissionOutput, {
        message: 'Please grant the permission to access your device address.',
        permissionScope: 'read::alexa:device:all:address',
      });
    } else {
      // ...
    }
  }
}

Under the hood, the AskForPermissionOutput looks like this:

{
  message: this.options.message,
  platforms: {
    alexa: {
      nativeResponse: {
        response: {
          shouldEndSession: true,
          directives: [
            {
              type: 'Connections.SendRequest',
              name: 'AskFor',
              payload: {
                '@type': 'AskForPermissionsConsentRequest',
                '@version': '1',
                'permissionScope': this.options.permissionScope,
              },
              token: this.options.token || '',
            },
          ],
        },
      },
    },
  },
}

You can use the getLocation() method to retrieve the device location:

import { DeviceLocation } from '@jovotech/platform-alexa';
// ...

async someHandler() {
  const location: DeviceLocation = await this.$alexa.$device.getLocation();

  /* Result:
   {
      city: string;
      countryCode: string;
      postalCode: string;
    }
  */
}

The getAddress() method can be used to retrieve the address associated with the device:

import { DeviceAddressLocation } from '@jovotech/platform-alexa';
// ...

async someHandler() {
  const address: DeviceAddressLocation = await this.$alexa.$device.getAddress();

  /* Result:
   {
      addressLine1: string;
      addressLine2: string;
      addressLine3: string;
      districtOrCounty: string;
      stateOrRegion: string;
      city: string;
    }
  */
}

System Settings

It is possible to retrieve some of your Alexa Skill user's settings without them granting you any special permissions. Learn more in the official Alexa docs.

You can use the getTimeZone() method to retrieve the timezone setting:

async someHandler() {
  const timezone: string = await this.$alexa.$device.getTimeZone();

  /* Result:
    "Africa/Abidjan"
  */
}

Entities (Slots)

Alexa slots are called entities in Jovo. You can learn more in the Jovo Model and the $entities documentation.

You can access the Alexa-specific $entities property like this, which allows you to get typed access to the native API result for each slot:

this.$alexa.$entities;

// Example: Get native API result object for slot "name"
this.$alexa.$entities.name.native;

Learn more about the structure of the API result in the official Alexa documentation on entity resolution.

ISP

Jovo offers an integration with in-skill purchasing (ISP) which allows you to make money by selling digital goods and services through your Alexa Skill.

Learn more in the Jovo ISP documentation for Alexa.

Alexa Conversations

You can build Alexa Skills with Jovo that make use of the Alexa Conversations dialogue management engine.

Learn more in the Jovo Alexa Conversations documentation.

Skill Connections

You can use Jovo with Alexa Skill Connections by sending a Connections.StartConnection directive as shown in the official Alexa docs.

For example, if you want to connect to a custom task in a specific skill, you can use the StartConnectionOutput class provided by the Jovo Alexa integration.

import { StartConnectionOutput, OnCompletion } from '@jovotech/platform-alexa';
// ...

someHandler() {
  // ...

  return this.$send(StartConnectionOutput, {
    taskName: {
      amazonPredefinedTask: false,
      name: 'CountDown',
    },
    // Input for the task
    input: {
      upperLimit: 10,
      lowerLimit: 1,
    },
    // Decides whether session is picked up after task
    onCompletion: OnCompletion.SendErrorsOnly,
    token: '<your-token>',
    // defaults to 1
    taskVersion: 1,
    // This is mandatory in case taskName.amazonPredefinedTask is false
    providerSkillId: '<skill-id>',
  });
}

The Output Options can be changed to create a managed skill connection, by setting taskName.amazonPredefinedTask to true and omitting providerSkillId.

In case the session is supposed to be resumed after the task is handled, onCompletion can be changed to OnCompletion.ResumeSession.

For some specific connections, the Jovo Alexa integration offers convenience output classes. Below is an overview of all classes:

You can find the output options in each class implementation. For example, you use the ConnectionAskForPermissionConsentOutput like this:

import { ConnectionAskForPermissionConsentOutput } from '@jovotech/platform-alexa';
// ...

someHandler() {
  // ...

  return this.$send(ConnectionAskForPermissionConsentOutput, {
    // Options
    message: 'Please grant access to your Alexa profile name',
    shouldEndSession: true,
    token: '<your-token>',
    permissionScopes: ['alexa::profile:given_name:read']
  })
}

This would result in the following output template:

{
  message: 'Please grant access to your Alexa profile name',
  platforms: {
    alexa: {
      nativeResponse: {
        response: {
          shouldEndSession: true,
          directives: [
            {
              type: 'Connections.StartConnection',
              uri: 'connection://AMAZON.AskForPermissionsConsent/2',
              input: {
                '@type': 'AskForPermissionsConsentRequest',
                '@version': '2',
                'permissionScopes': ['alexa::profile:given_name:read'],
              },
              token: '<your-token>',
              onCompletion: 'RESUME_SESSION', // default
            },
          ],
        },
      },
    },
  },
}

Custom Tasks

You can handle custom tasks with the following features:

  • this.$alexa.task property that offers helper methods and properties to access task related values
  • AlexaHandles.onTask() for the @Handle decorator to accept task requests

Here is an example:

import { AlexaHandles } from '@jovotech/platform-alexa';
// ...

@Handle(AlexaHandles.onTask('Reminder', 1))
reminderTask(): Promise<void> {
  const task = this.$alexa?.task.getTask();
  const taskName = this.$alexa?.task.name;
  const taskVersion = this.$alexa?.task.version;
  const input = this.$alexa?.task.input;
  console.log(task, taskName, taskVersion, input);

  // ...
}

To learn more about all features of the task property, take a look at the AlexaTask.ts file.

Under the hood, onTask() as part of AlexaHandles looks like this with the parameters taskName: string and optional taskVersion?: number | string:

{
  types: [InputType.Launch],
  global: true,
  if: (jovo: Jovo) => {
    const task = jovo.$alexa?.task.getTask();
    if (!task) return false;
    if (!jovo.$alexa?.task.hasTaskName(taskName)) return false;
    if (taskVersion && !jovo.$alexa.task.isVersion(taskVersion)) return false;
    return true;
  },
}

Name-free Interaction

You can handle name-free interactions by responding to CanFulfillIntentRequest requests. To do this, you can use the following two helpers:

import { Handle } from '@jovotech/framework';
import { CanFulfillIntentOutput, AlexaHandles } from '@jovotech/platform-alexa';
// ...

@Handle(AlexaHandles.onCanFulfillIntentRequest())
someHandler() {
  // ...
  return this.$send(CanFulfillIntentOutput, {
    canFulfill: 'YES',
  });
}

Under the hood, onCanFulfillIntentRequest() as part of AlexaHandles looks like this:

{
  global: true,
  types: ['CanFulfillIntentRequest'],
  platforms: ['alexa'],
}

CanFulfillIntentOutput looks like this:

{
  listen: false,
  platforms: {
    alexa: {
      nativeResponse: {
        response: {
          canFulfillIntent: {
            canFulfill: this.options.canFulfill,
            slots: this.options.slots ?? {},
          },
        },
      },
    },
  },
}