# What About Other Alexa Features?
Litexa supports Alexa features through a mixture of code shipped with the Litexa core package and code added by Litexa extensions, extra packages that you install alongside Litexa that can add support for things like events, directives, new Litexa statements and runtime code.
There are plenty of wonderful Alexa features that Litexa does not yet directly support, but you are not prevented from using them with a Litexa project! Here are the extension points where you can quickly convince Litexa you know better, and have it step out of your way.
# Modifying the Alexa Response Object
The final Litexa product is a response, as specified in the ASK documentation. (opens new window)
There are a few reasons you may want to manipulate that response object directly,
rather than rely on the usual Litexa commands. For example, you may want to log
how you're responding to an analytics service you run, inject some data into sessionAttributes
,
or automatically parse your final accumulated outputSpeech
to generate other responses
like cards via a custom function.
Modifying the skill response is currently possible in one of three ways:
- directly, by installing a
responsePostProcessor
handler. - directly, by writing a Litexa extension with a
similar
beforeFinalResponse
handler which would receive the full response object prior to it being sent. - indirectly, by modifying response properties in the Litexa
context
object. See details below.
The responsePostProcessor
is the simplest way to do a one-off custom processor for a single
skill. It's a single callback you add to the global litexa
object anywhere in your inline
code.
litexa.responsePostProcessor = function( body, context ) {
// note: the context argument is the full Litexa context object
// the body object is the full response as documented at:
// https://developer.amazon.com/en-US/docs/alexa/custom-skills/request-and-response-json-reference.html
// any modification you apply to response will be returned to Alexa
let response = body.response;
if ( response.outputSpeech && response.outputSpeech.indexOf('rose') >= 0 ) {
response.card = {
"type": "Standard",
"title": "A Rose For You",
"text": "",
"image": {
"smallImageUrl": "https://there.com/rose-small.png",
"largeImageUrl": "https://there.com/rose-large.png"
}
}
}
}
# Modifying the Language Model
Not Yet Available
There may be times when manual intervention in the model building process is necessary, e.g. you may want to inject some other procedural mechanism for creating values that doesn't fit Litexa's slotBuilder scheme. If you have a case like this, please reach out to us with a proposal for where you feel it would be natural to inject it!
# Accepting Novel Events
To accept named events that Litexa does not yet define in its core, you can write
an extension module, where you indicate the new events in the
compiler.valideEventNames
list. Litexa extension
# Sending Novel Directives
Most of the commands you send as part of a skill's response will be in the form of directives, which are included as JSON objects in a skill response. See the ASK documentation on skill responses (opens new window) for more information about directives in general.
Several Litexa statements, like screen
or card
,
will generate directives as part of their functionality. These
will also insert or assert any other requirements associated
with using the command, such as injecting an API interface
declaration into your skill.json
manifest, or giving you
a compile error should the command require an intent you haven't
defined in your skill's language model.
You can also directly create any directive yourself, by using the
directive
statement. You may want to do this if it's more
convenient to generate many directives, or template their
contents in your code somehow. The statement takes as an argument
an expression, usually a function call that resolves to either a
single JSON directive object, or an array of JSON directives.
For instance, the following skill returns a manually constructed
AudioPlayer.Play
directive:
launch
say "Get ready to groove in 3. 2. 1."
directive playJazzyMusic()
function playJazzyMusic(){
return {
type: "AudioPlayer.Play",
playBehavior: "valid playBehavior value such as REPLACE_ALL",
audioItem: {
stream: {
url: "https://cdn.example.com/url-of-the-stream-to-play",
token: "aStreamToken",
offsetInMilliseconds: 0
},
metadata: {
title: "Downright Jazzy",
subtitle: "Were you ready for that jazz?",
art: {
sources: [
{
url: "https://cdn.example.com/url-of-the-album-art-image.png"
}
]
},
backgroundImage: {
sources: [
{
url: "https://cdn.example.com/url-of-the-background-image.png"
}
]
}
}
}
}
}
By default, Litexa will throw an error during testing if your
skill generates a directive it does not recognize. Additional
directive types may be supported by Litexa extensions, for
example the @litexa/apl
extension, in
which case installing the extension will remove the error.
In the case that no extension exists to support the directive yet, you can inform Litexa that you've vouched for the type and that it should assume it is correct:
You will need to add the desired directive's type to the
validDirectivesList
field of your Litexa project'slitexa.config.*
as seen below:{ "name": "myFirstProject", "deployments": { ... }, "validDirectivesList": [ "AudioPlayer.Play" ] }
If the directive in question requires an interface declaration, then you should add such to your
skill.*
file manually. See the ASK documentation on interfaces (opens new window) for more information on the various available interface declarations.{ "manifest": { ... other contents "apis": { "custom": { "interfaces": [ { "type": "AUDIO_PLAYER" } ... other interfaces ] } } } }
For directives supported by Litexa extensions, any generated directives should be validated for correct content, raising an error during compilation so that you can catch it as early as possible. Directives that you vouch for manually in the validDirectivesList though, offer no such protection, so an invalid directive won't be caught until runtime. In those cases, refer to your Litexa logs to discover what the problem is.
See the chapter on testing for more information on tools you have available to help trap and correct any errors.
# Reading and Modifying Litexa's context
Litexa's runtime primarily operates by creating and manipulating a context
object in response
to each request that comes in. The context
is accessible within the scope of any Litexa code,
and can be passed down to inline code as a function parameter:
# main.litexa
launch
printObject(context)
// main.js
printObject = function(obj) { console.log(JSON.stringify(obj)); }
The Litexa context
is recreated for every new skill request, and contains the following data:
- incoming request data (e.g. the Alexa skill session attributes)
- accumulated response data (e.g. an array of all the
say
statements hit so far) - DB data (e.g. persistent and transient variables)
- utility data (e.g. skill launch time stamp)
Let's take a closer look at each of these in turn, along with some common use cases.
# 1) Incoming Request Data
While Litexa makes most request data available/actionable without requiring
the context
object, there are several outlier reasons that
require reading the context
object directly, such as:
- visibility and manipulation of
context.event.session.attributes
- visibility of supported interfaces in
context.event.context.System
- visibility of device screen specs in
context.event.context.Viewport
- etc.
The full incoming request, along with session and system information, is available in
context.event
:
{
"version": "1.0",
"session": { // SESSION DATA
"sessionId": "amzn1.echo-api.session.someId",
"application": {
"applicationId": "amzn1.ask.skill.someId"
},
"attributes": {},
"user": {
"userId": "amzn1.ask.account.someId"
}
},
"context": { // SYSTEM DATA
"System": {
"device": {
"deviceId": "amzn1.ask.device.someId",
"supportedInterfaces": {
"Alexa.Presentation.APL": {},
"Display": {}
},
"apiEndpoint": "https://api.amazonalexa.com",
"apiAccessToken": "someAccessToken"
}
},
"Viewport": {
"experiences": [
{
"arcMinuteWidth": 246,
"arcMinuteHeight": 144,
"canRotate": false,
"canResize": false
}
],
"shape": "RECTANGLE",
"pixelWidth": 1024,
"pixelHeight": 600,
"dpi": 160,
"currentPixelWidth": 1024,
"currentPixelHeight": 600,
"touch": [
"SINGLE"
],
"video": {
"codecs": [
"H_264_42",
"H_264_41"
]
}
}
},
"request": { // REQUEST DATA
"type": "IntentRequest",
"requestId": "amzn1.echo-api.request.2f164393-f7c7-410d-8819-eccd49a7f26f",
"timestamp": "2017-10-01T22:02:10.000Z",
"locale": "en-US",
"intent": {
"name": "MY_NAME_IS_NAME",
"slots": {
"name": {
"name": "name",
"value": "John"
}
}
}
}
}
Request Data Shortcuts
There are some useful request data shortcuts available in Litexa:
context.event.request
is available in Litexa code as the request variable $request.context.requestId
is a shortcut tocontext.event.request.requestId
context.slots
contains the Litexa request variables. This includes the request slot variables, so while handling the following intent, you'd expect to seecontext.slots.name
defined.when "my name is $name" with $name = AMAZON.US_FIRST_NAME say "hello $name"
# 2) Pending Response Data
The Litexa context
tracks the following data to be included in any pending response:
{
"say": [], // pending outputSpeech (as specified by 'say' statements)
"reprompt": [], // pending reprompt speech (as specified by 'reprompt' statements)
"directives": [], // pending directives (as specified by the 'directive', 'card',
// monetization, and extension statements)
"shouldEndSession": false // value explicitly set by 'END' statement,
// or implicitly set by other statements
}
The response data in context
can be used to read, modify, or validate the pending response.
It also allows for changing the response outside of Litexa code, for instance by handing off
the context to an inline function:
# main.litexa
launch
addCustomSSML(context)
// main.js
addCustomSSML = function(context) {
context.say.push("Hello, I'm <amazon:effect name="whispered">custom.</amazon:effect>");
}
Modifying response data can become especially useful when authoring a custom
Litexa extension. For instance, the @litexa/apl
extension keeps track of all
APL-related data in a newly added context.apl
object.
# 3) DB Data
context.db
allows for directly accessing the DB instance used by Litexa. As an alternative to
using @ variables
to read and write DB data, the same can be achieved by
directly invoking context.db.write(String key, data)
and context.db.read(String key)
.
Among other skill information, the context.db
object tracks local variables
that have database storage, and @ variables:
{
"db": {
"variables": {
"myKey": {
"myData" // as saved by: @myKey = "myData"
}
}
},
"cache": {
"__stateLocals": {
"myVar": "myValue" // as saved by: local myVar = "myValue"
}
}
}
Litexa-internal DB data
DB variables stored with the private '__'
prefix should generally be left untouched: Modifying
them could negatively impact skill functionality, as they are usually relied on by Litexa's core
or extension logic.
# 4) Utility Data
Finally, the context
object provides some useful utility information for the currently active
session:
{
"now": 1506895265000, // time (in milliseconds) when the current request begun processing
"language": "default" // current skill request's Litexa language
"shouldEndSession": false, // modified by the 'END' statement
"settings": { // modified by the 'set [setting] [boolean]' statement
"resetOnLaunch": true
},
"traceHistory": [ // all states that have been visited in processing the current request
"launch"
],
"monetization": [ // overview of all linked in-skill products
"inSkillProducts": [
// [reference name]: 'ENTITLED'|'NOT_ENTITLED'
{ 'myProduct': 'ENTITLED' }
]
]
}
This utility data can serve various purposes:
context.language
can be used to handle the appropriate language in a Litexa extension. Note, that this is the language defined by Litexa:- any locale that has no specific
languages
directory, as detailed in the documentation on Localization Project Structure, is mapped todefault
. - all other locales are mapped to the same name used for their
languages
directory. This would be either a specific locale (es-es
) or a language code spanning multiple locales (e.g.es
would override the localeses-ES
andes-MX
).
- any locale that has no specific
context.traceHistory
can be used for troubleshooting or analyticscontext.now
can be used to enable time-based skill variations, such as:
launch
if minutesBetween(context.now, @lastLaunchTime) < 15
say "We spoke less than 15 minutes ago, did you miss me?"
@lastLaunchTime = context.now
In summary, the Litexa context
object is a good starting point for extending Litexa's
capabilities, be it through external Litexa extensions, or through
skill-specific in-code context
handlers.