/hermod-javascript-toolkit

Everything you need to write Hermod actions in JavaScript or TypeScript

Primary LanguageTypeScriptMIT LicenseMIT

hermod-javascript-toolkit

npm

Everything you need to write Hermod actions in JavaScript or TypeScript.

Setup

# Run this in your favourite terminal
npm i hermod-toolkit

Then use it in your package.json file scripts.

"scripts": {
    "build": "hermod-toolkit build",
    "dev": "hermod-toolkit dev",
    "test": "hermod-toolkit test",
    "launch": "hermod-toolkit run"
}

Assumes that your action is structured in the following way by default:

.
├── src               # sources folder
|   └── index.[jt]s   # the action code entry point
|
├── dist              # the action build output folder
|
├── tests             # tests folder (can't be changed)
|   └── *.spec.[tj]s  # any number of test files
|
├── package.json      # with an extra "sandbox" key, highlighted lower in the documentation
|
├── config.ini        # action configuration file
|
└── assets
    └── i18n          # internationalization folder
        └── <locale>.json # translation file for a given language

Command line

hermod-toolkit --help

Displays a help message and exits.

hermod-toolkit build

Builds your action to ./dist/index.js using webpack.

This command will warn if your action code uses node.js native modules, and they will be automatically added to your package.json file under the sandbox key.

// Example:
"sandbox": [
  "fs",
  "http",
  "https",
  "os",
  "path",
  "stream",
  "tty",
  "url",
  "util",
  "zlib"
]

hermod-toolkit dev

Automatically rebuilds and run the Hermod action on file change.

You can debug the action by connecting a debugger to the 9229 port. Check the node.js website for more details.

Use the -c/--config-path if you need to use custom hermes options. Use the -ns/--no-sandbox to disable the sandbox.

hermod-toolkit test

Runs your test suite with jest.

Use the -s/--sandbox flag to run the tests in a sandboxed environment.

hermod-toolkit run

Runs your Hermod action.

Use the -c/--config-path if you need to use custom hermes options. Use the -ns/--no-sandbox to disable the sandbox.

Utils

import { config } from 'hermod-toolkit'

Initialize once by calling .init, then use .get to retrieve the configuration.

// Reads the configuration file located at `./config.ini`.
config.init()
// Get the configuration.
const configuration = config.get()

Depends on node.js modules fs, path.

import { i18n } from 'hermod-toolkit'

Initialize once by calling .init.

Uses the i18next library under the hood.

// Reads the `./assets/i18n/en.json` translation file and initializes an i18next instance.
await i18n.init('en')
// Get a translation. (see https://www.i18next.com/overview/api#t)
const translation = i18n.translate(keys, options)
// Get the translation for an error message.
// Assumes that the translation file contains the key 'error.<error message>'.
const errorTranslation = await i18n.errorMessage(error)
// Get a random translation for a given key.
// Assumes that the translation file maps an array of possible translations to the key.
const translation = i18n.randomTranslation(keys, options)

Depends on node.js modules fs, path.

import { http } from 'hermod-toolkit'

An http client using wretch.

const pokeapi = http('https://pokeapi.co/api/v2')

const bulbasaur = await pokeapi
    .url('/pokemon/1')
    .get()
    .json()

Depends on node.js modules http, https, stream, zlib, url.

import { logger } from 'hermod-toolkit'

A logger using debug.

// Enables error printing. You can use * as a wildcard.
logger.enable('error')
// Logs to <actionName>:error
logger.debug('error')
// Logs to <actionName>:info
logger.info('info')
// Logs to <actionName>:debug
logger.error('debug')

Depends on node.js modules tty, util, os.

import { handler } from 'hermod-toolkit'

Utilities for handling hermes callbacks.

// Wrap a dialogue handler to gracefully capture and log errors.
handler.wrap((msg, flow) => {
    // ... //
})

Depends on internal modules i18n, logger.

import { message } from 'hermod-toolkit'

Utilities for parsing hermes messages.

// Get slots matching the slot name.
const mySlot = message.getSlotsByName(
    // The message instance
    msg,
    // The slot name
    'mySlotName',
    {
        // If true, returns only the slot with the highest confidence.
        onlyMostConfident: true,
        // If specified, returns only slots having a confidence higher than this threshold.
        threshold: 0.5
    }
)
// Calculates the ASR confidence.
const confidence = message.getAsrConfidence(msg)

import { camelize } from 'hermod-toolkit'

Camelcase utilities using the camelcase package.

// Camelize a string
const camelizedKey = camelize(key)
// Returns a cloned object having camelized keys.
const camelizedObject = camelizeKeys(object)

Hermes configuration

In order to pass custom hermes options, you can use the -c flag to specify the path to a configuration file.

For instance, if you are using an mqtt broker running on a different machine, you could add options in a file named hermes_config.json.

{
    "address": "ip:port"
}

And add the -c flag in the package.json file.

"scripts": {
    "dev": "hermod-toolkit dev -c ./hermes_config.json",
    "launch": "hermod-toolkit run -c ./hermes_config.json"
}

Unit tests

During unit tests, your action code is run is parallel with the tests and i18n/http utils are mocked.

Mocks

http

You can use the provided HermodToolkit.mock.http global function to override http calls automatically during tests.

// Place this code in the root of the test file to mock.
HermodToolkit.mock.http(fetchMock => {
    // Define as many mocks as you need.
    // See http://www.wheresrhys.co.uk/fetch-mock for API details
    fetchMock.mock('https://my.super.api/route', {
        hello: "world"
    })
})

i18n

The i18n.translate output is not the translation but a stringified JSON reprensentation of the key/options.

This call:

// This call:
i18n.translate('pokemon.info', {
    name: 'Pikachu',
    weight: 10,
    height: 20
})

Returns when mocked: (in stringified form)

{
  "key": "pokemon.info",
  "options": {
    "name": "Pikachu",
    "weight": 10,
    "height": 20
  }
}

globals

You can use the provided HermodToolkit.mock.globals global function to override or define global variables.

HermodToolkit.mock.globals(globals => {
    // Mocks the Date object in a crude way.
    const BackupedDate = global.Date
    const freezedTime = 1550835788763
    const mockedDate = function Date(arg: string | number | Date) {
        return new BackupedDate(arg || freezedTime)
    }
    mockedDate.now = () => freezedTime
    mockedDate.parse = BackupedDate.parse
    mockedDate.UTC = BackupedDate.UTC

    // Assign the mocked Date to the globals object
    globals.Date = mockedDate
})

Session

To simulate dialogue session rounds, you can use the Session helper that will pass hermes messages between your test and action code.

import { Test } from 'hermod-toolkit'

/* ... */

// Initialize a session instance
const session = new Test.Session()
// Publish an intent message that is expected by the action code to start a session
await session.start({
    intentName: 'pokemon_details',
    input: 'Give me the details for Pokemon 1',
    slots: [
        // Function that creates a custom slot, omitted for the sake of brevity.
        createPokemonIdSlot('1')
    ]
})
// Expect the action code to publish a continue session message
// and reply with the following intent message (here we simulate a confirmation message)
const continuation = await session.continue({
    intentName: 'pokemon_confirm',
    input: 'Yes please'
})
// Expect the action to end the session and retrieve the end session message value
const endSessionMessage = await session.end()
// Extract the key/options of the TTS spoken by the end session message
const { key, options } = JSON.parse(endSessionMessage.text || '')
// Assert that the values are correct
expect(key).toBe('pokemon.info')
expect(options.name).toBe('bulbasaur')
expect(options.weight).toBe(69)
expect(options.height).toBe(7)

Credits

Original work by @elbywan from Snips.