@cognigy/extension-tools
TypeScript icon, indicating that this package has built-in type declarations

0.15.1 • Public • Published

Typescript / Javascript library to extend the Cognigy.AI 4 platform with your own code.

Table of Contents

Installing

Using npm:

npm i @cognigy/extension-tools

Using yarn:

yarn add @cognigy/extension-tools

Folder Structure

  • extension-name
    • README.md
    • src/
      • nodes/
        • myFlowNode.ts
      • connections/
        • myConnection.ts
      • module.ts
      • package.json
      • package-lock.json
      • tslint.json
      • tsconfig.json
      • icon.png

This structure includes all the required resources to build and upload the Extension to Cognigy.AI. The README.md is used to show the code's content. Thus, other developers or interested users can get familiar with the functionality. The entire source code of the exposed Flow Nodes is located in the src/nodes/ folder, in which the next four files (package.json, package-lock.json, tslint.json, tsconfig.json) are used to create the Javascript module and check it for mistakes. The icon is required to show the module's logo in Cognigy.AI. In most cases, the icon shows the logo of the third-party software product which we are integrating with.

Notes:

  • The icon.png needs to have the following dimensions: 64x64 Pixels

Example

Import extension-tools

In order to use the provided interfaces and functions, the NPM package needs to be imported to every file:

module.ts

import { createExtension } from "@cognigy/extension-tools";

connection.ts

import { IConnectionSchema } from "@cognigy/extension-tools";

flowNode.ts

import { createNodeDescriptor, INodeFunctionBaseParams } from "@cognigy/extension-tools";

Connection

A Connection can be created by using the following format, while the list of fields define the list of required credential values the user needs to provide inside of Cognigy.AI:

import { IConnectionSchema } from "@cognigy/extension-tools";

export const myConnection: IConnectionSchema = {
	type: "myconnection",
	label: "Basic Auth",
	fields: [
		{ fieldName: "username" },
        { fieldName: "password" }
	]
};

If the Extension should provide multiple Connections, e.g. Basic Auth, OAuth2 and API Key, each Connection is defined in its own file:

  • connections/
    • basicAuthConnection.ts
    • oauthConnection.ts
    • apiKeyConnection.ts

Flow Node

An Extension Flow Node is created by describing fields, sections and function, for example, while this package provides a method called createNodeDescriptor() in order to follow the necessary interfaces:

import { createNodeDescriptor, INodeFunctionBaseParams } from "@cognigy/extension-tools";

export interface IMyParams extends INodeFunctionBaseParams {
	config: {};
}

export const myFlowNode = createNodeDescriptor({
	type: "myFlowNode",
	defaultLabel: "My Flow Node",
	fields: [],
	function: async ({ cognigy, config }: IMyParams) => {}
});

This minimal setup now can be filled with information:

import { createNodeDescriptor, INodeFunctionBaseParams } from "@cognigy/extension-tools";

export interface IMyParams extends INodeFunctionBaseParams {
	config: {
        text: string;
    };
}

export const myFlowNode = createNodeDescriptor({
	type: "myFlowNode",
	defaultLabel: "My Flow Node",
	fields: [
		{
			key: "text",
			label: "Text",
			type: "cognigyText",
			defaultValue: "{{input.text}}",
			params: {
				required: true
			}
		},
    ],
	function: async ({ cognigy, config }: IMyParams) => {
        const { api } = cognigy;
        const { text } = config;

        api.say(`You said ${text}`);
    }
});

Next to this pseudo-code example, one can find more examples in the GitHub Extensions Repository.

If the Extension should provide multiple Flow Nodes, each Node is defined in its own file:

  • nodes/
    • createContact.ts
    • getContact.ts
    • search.ts

Module / Extension

Finally, the Extension can be created with the described Flow Nodes and Connections. For this purpose, this package provides a function called createExtension():

import { createExtension } from "@cognigy/extension-tools";

import { myConnection } from "./connections/myConnection";
import { myFlowNode } from "./nodes/myFlowNode";

export default createExtension({
	nodes: [
		myFlowNode
	],
	connections: [
		myConnection
	]
});

If no Connection is required in this Extension, the connections list can be removed:

export default createExtension({
	nodes: [
		myFlowNode
	]
});

Interface Descriptions

Next to the minimum examples presented above, this package provides various settings, such as sections, form, appearance or dependencies, for example, that could be used for improving the Extension's usage experience and performance.

createExtension()

interface ICreateExtensionParams {
	nodes: INodeDescriptor[];
	connections?: IConnectionSchema[];
	options?: IExtensionOptions;
}

connections

export interface IConnectionSchema {
	type: string;
	label: string;
	fields: IConnectionSchemaField[];
}

export interface IConnectionSchemaField {
	/** The field name, e.g. 'client_id' */
	fieldName: string;
}

options

export interface IExtensionOptions {
	label?: string;
}

nodes

export interface INodeDescriptor {
	/* The type of this descriptor, e.g. 'say' */
	type: string;

	/* The type of the node parent (if there is one) */
	parentType?: string;

	/* The label that should be used when a new Node of this type is created */
	defaultLabel: string;

	/* A short line of text that describes what this Node is used for */
	summary?: string;

	/* Appearance information for the node */
	appearance: INodeAppearance;

	/* Additionall behavioural information */
	behavior?: INodeBehavior;

	/* Constraints for how this node can be created, placed */
	constraints?: INodeConstraints;

	/* Dependencies which we want to create when this node is created  */
	dependencies?: INodeDependencies;

	/* The individual fields (arguments) of this node */
	fields?: INodeField[];

	/* The code of this node */
	function?: TNodeFunction;

	/** Defines how the preview should be generated for nodes using this descriptor */
	preview?: INodePreview;

	/** 
	 * Tags which allow searching individual Nodes 
	 * 
	 * The following tags will put the Node
	 * into the distinct "function tabs":
	 *
	 * - basic
	 * - logic
	 * - message
	 * - profile
	 * - service
	 * - nlu
	 * - data
	 * */
	tags?: TNodeTagType[];

	/** Definition of tokens this node might fill */
	tokens?: ISnippet[];

	/** Sections which allow to group multiple fields */
	sections?: INodeSection[];

	/** The form defines how fields and sections should be render in order */
	form?: INodeFieldAndSectionFormElement[];
}

appearance

export interface INodeAppearance {
	color?: string;
	textColor?: string;
	showIcon?: boolean;
	variant?: "regular" | "mini" | "hexagon";
}

behavior

export interface INodeBehavior {
	stopping?: boolean;
	entrypoint?: boolean;
}

constraints

export interface INodeConstraints {
	/* Should this node be editable? */
	editable?: boolean;

	/* Should this node be deletable? */
	deletable?: boolean;

	/* Should this node be manually creatable */
	creatable?: boolean;

	/* Should this node be callapsable */
	collapsable?: boolean;

	/* Should this node be movable */
	movable?: boolean;

	/* Additional placement information */
	placement: {
		predecessor?: INodeConstraint;
		successor?: INodeConstraint;
		children?: INodeConstraint;
	};
}

dependencies

export interface INodeDependencies {
	/** A list of Node types */
	children: string[];
}

fields

An Extension Flow Node field is of type INodeFieldType:

export interface INodeField {
	type: TNodeFieldType;
	key: string;
	label: string;
	condition?: TNodeFieldCondition;
	defaultValue?: any;
	description?: string;
	params?: object;
	optionsResolver: TOptionsResolver
}

while the following list of types can be used:

[
	"text",
	"rule",
	"json",
	"checkbox",
	"time",
	"date",
	"datetime",
	"select",
	"typescript",
	"xml",
	"textArray",
	"chipInput",
	"cognigyText",
	"toggle",
	"slider",
	"number",
	"daterange",
	"connection",
	"say",
	"condition",
	"adaptivecard"
]
condition

There are three different types of conditions that can be used inside of a field description:

Single Condition:

export interface INodeFieldSingleCondition {
	/** The key of the Field whose value should be matched */
	key: string;
	/** The expected value(s) that make this condition match */
	value: TComparableValue | TComparableValue[];
	/** If this is true, the condition result will be inverted */
	negate?: boolean;
	or?: never;
	and?: never;
}

Example:

{
    fields: [
        {
            [...field],
            condition: {
                key: "displayField",
                value: true
            }
        }
    ]
}

Only if the value of the other field with the key "displayField" is true, then this specific field should be displayed in the UI/Edit Node menu of the Flow Node.

AND Condition:

export interface INodeFieldANDCondition {
	key?: never;
	value?: never;
	negate?: never;
	or?: never;
	/** Nested conditions are supported in version 4.1.4 and up of the Cognigy AI */
	and: TNodeFieldCondition[]
}

Example:

{
    fields: [
        {
            [...field],
            condition: {
                and: [
                    {                
                        key: "displayField",
                        value: true
                    },
                    {                
                        key: "customerIsKnown",
                        value: true
                    },
                ]
            }
        }
    ]
}

For a more detailed condition, the AND operator can be used. In this case, both conditions need to be true in order to display the field.

OR Condition:

export interface INodeFieldORCondition {
	key?: never;
	value?: never;
	negate?: never;
	/** Nested conditions are supported in version 4.1.4 and up of the Cognigy AI */
	or: TNodeFieldCondition[]
	and?: never;
}

Example:

{
    fields: [
        {
            [...field],
            condition: {
                or: [
                    {                
                        key: "displayField",
                        value: true
                    },
                    {                
                        key: "customerIsKnown",
                        value: true
                    },
                ]
            }
        }
    ]
}

Finally, the OR operator tells Cognigy.AI to display the field if one of both keys is true -- in this example.

function

export type TNodeFunction = (params: INodeFunctionBaseParams) => Promise<void>;

export interface INodeFunctionBaseParams {
	cognigy: INodeExecutionCognigyObject;
	childConfigs: TNodeChildConfigs[];
	config: object;
	nodeId: string;
}
childConfigs
export type TNodeChildConfigs = {
	id: string;
	type: string;
	config: {
		[key: string]: unknown
	};
};

preview

export interface INodePreview {
	type: "text" | "image" | "custom";
	key: string;
}

tags

type TCognigyNodeTagType = "basic" | "logic" | "message" | "profile" | "service" | "nlu" | "data";

export type TNodeTagType = TCognigyNodeTagType | string;

tokens

export interface ISnippet {
	/**
	 * The label (name) of the snippet.
	 */
	label: string;

	/**
	 * The type of the snippet
	 * based on what the snippet accesses
	 * (profile, input or context)
	 */
	type: TSnippetType;

	/**
	 * The script the snippet executes
	 */
	script: string;
}

sections

export interface INodeSection {
	/** Unique identifier for this section within all sections of a descriptor, e.g. 'authentication' */
	key: string;

	/** Human readable lable of the seciton, e.g. 'Authentication' */
	label: string;

	/** Condition whether this section should be rendered */
	condition?: TNodeFieldCondition;

	/** Whether the sections should be collapsed by default (default: false) */
	defaultCollapsed: boolean;

	/** The fields that should be grouped in this section, points to 'key' of node-fields */
	fields: string[];
}

form

export interface INodeFieldAndSectionFormElement {
	/** A key either pointing to a 'field -> key' or 'section -> key' */
	key: string;

	/** The type of the pointer, either 'field' or 'section' */
	type: "field" | "section";
}

Options Resolvers

Options Resolvers are a way to enhance the seamless integration of a Cognigy Extension with third-party systems by providing dynamically resolved select options for Node Fields.

Options Resolver Availability

Options Resolvers were introduced with Cognigy AI version 4.9.0, therefore an upgrade to this or a later version is required to use this feature.

Use Cases

The benchmark case this feature was built around was having the Flow-editing users pick an actual entity on a third-party system (e.g. a file on their Dropbox) It could also be used without third-party systems e.g. to filter a set of available options based on other field selections (e.g. a list of cities that can be shrunk down to only show cities from a certain country in case another "country" field was set).

Usage Example

The following code snippet describes a "field configuration" featuring an Options Resolver that causes the select field to display a list of files as options which were fetched from a third-party API.

It assumes that there is an HTTP API at https://example.service/files that returns a list of "files" as JSON and requires an authentication header to be set.

The example also assumes that there is another field called credentials in this Node. It would typically be a "secret".

The "Resolver Function" fetches a list of files from the API using the value of the credentials field and returns it as an array of options.

const node = createNodeDescriptor({
  // ...
  fields: [
    // ...
    {
      type: "select",
      key: "file",
      label: "File on Example Service",
      optionsResolver: {
        dependencies: ["credentials"],
        resolverFunction: async ({ api, config }) => {
          // fetch list of files using http request
          const response = await api.httpRequest({
            method: "GET",
            url: `https://example.service/files`,
            headers: {
              xApiKey: config.credentials.apiKey,
            },
          });

          // map file list to "options array"
          return response.map((file) => {
            return {
              label: file.basename,
              value: file.path,
            };
          });
        },
      },
    },
  ],
});

API Reference

Options Resolver

The optionsResolver configuration can only be set on Node Fields with type select so far.

Options Resolver Dependencies

The dependencies array contains a list of all Node field values that are necessary for the resolverFunction. Typically, this would include a key to a "Secret Field" where authentication credentials for the API are available. All registered dependency Field values will be available to the resolverFunction as an object mapping ({ [fieldKey]: fieldValue }).

Resolver Function

The resolverFunction will be called in order to resolve new options for the select field. It will be triggered from the Node Editor, but executed in the backend.

Dependencies & Triggers

The resolverFunction has access to a config object containing a key-value-mapping of all registered dependency Fields (see section above).

Everytime a change to a registered dependency Field is made in the Node Editor, the resolverFunction will be triggered with the new values (including intermediate, non-saved changes!).

Resolved Options & Validation

The resolverFunction has to return an array of objects with the exact shape of:

interface IResolvedOption {
  label: string;
  value: string;
}

The returned value of the resolverFunction will be validated using a schema. If it does not match the "Option Schema" described above, it will not return any options.

API & HTTP Requests

The resolverFunction will get an api object as a parameter. You will be able to perform an HTTP request using api.httpRequest. The httpRequest function will respect the proxy configuration that is configured for the Cognigy.AI installation you are running on.

Extension Localization

Creating Extensions is an important feature required to further extend Cognigy AI with additional functionality. As the UI supports several languages in the menus we also added a functionality to add translations to Extensions.

Localization is a completely optional feature that can be used whenever required. You don't need to enforce it consistently throughout an Extension, you can use it as well to translate just a single string.

Localization Availability

Localization was introduced with Cognigy AI version 4.12.0, therefore an upgrade to this or a later version is required to use this feature.

The Localization Object

Localization supports the replacement of simple strings with a JSON object like this:

{
	"default": "Default fallback string",
	"enUS": "String with English Localization",
	"deDE": "String with German Localization",
	"esES": "String with Spanish Localization",
	"koKR": "String with Korean Localization",
	"jaJP": "String with Japanese Localization"
}

The default property is mandatory and the other ones are optional, so you can e.g. choose to have an English and a German Localization only.

Localization Visibility

Localization will be visible for every user of the Cognigy.AI User Interface (UI), therefore a Node with a German Localization will be displayed in German for every user that has their UI set to German.

End users won't see a difference when communicating with the localized Node.

Localization Targets

Localization doesn't work for all properties. Right now these properties are supported in a Descriptor:

  1. defaultLabel
  2. summary

These properties are supported in a Node Section:

  1. label

These properties are supported in a Node Field:

  1. label
  2. description

If a Node Field is of type "select" then it's also supported in the label property of the options in the params

Localization Example

Let's take a sample Descriptor built without Localization:

    export const mySampleNode = createNodeDescriptor({
        type: "myExampleNode",
        defaultLabel: "My example Node",
        summary: "Just a simple example Node",
        fields: [
            {
                key: "textInput",
                label: "My Example Text Input"
                type: "cognigyText",
            },
            {
                key: "selectIcecream",
                label: "Do you like Icecream?"
                params: {
                    options: [
                        {
                            label: "Yes, I love Icecream!",
                            value: true
                        },
                        {
                            label: "No, I don't",
                            value: false
                        }
                    ]
                }
            }
        ],
        sections: [
            {
                key: "importantQuestions",
                label: "Important Questions",
                defaultCollapsed: true,
                fields: [
                    "selectIcecream"
                ]
            }
        ],
        form: [
            { type: "field", key: "textInput" },
            { type: "section", key: "importantQuestions" }
        ],
        function: async ({ cognigy, config }: IMySampleNode) => {
            // my Node logic
        }
    })

adding a German translation might look like this:

    export const mySampleNode = createNodeDescriptor({
        type: "myExampleNode",
        defaultLabel: {
            default: "My example Node",
            deDE: "Mein Beispiel Node"
        },
        summary: {
            default: "Just a simple example Node",
            deDE: "Nur ein einfaches Beispielnode"
        },
        fields: [
            {
                key: "textInput",
                label: {
                    default: "My Example Text Input",
                    deDE: "Mein Beispiel Text Eingabefeld"
                },
                type: "cognigyText",
            },
            {
                key: "selectIcecream",
                label: {
                    default: "Do you like Icecream?",
                    deDE: "Magst du Eiscreme?"
                },
                params: {
                    options: [
                        {
                            label: {
                                default: "Yes, I love Icecream!",
                                deDE: "Ja, ich liebe Eiscreme!"
                            },
                            value: true
                        },
                        {
                            label: {
                                default: "No, I don't",
                                deDE: "Nein, ich mag es nicht"
                            },
                            value: false
                        }
                    ]
                }
            }
        ],
        sections: [
            {
                key: "importantQuestions",
                label: {
                    default: "Important Questions",
                    deDE: "Wichtige Fragen"
                },
                defaultCollapsed: true,
                fields: [
                    "selectIcecream"
                ]
            }
        ],
        form: [
            { type: "field", key: "textInput" },
            { type: "section", key: "importantQuestions" }
        ],
        function: async ({ cognigy, config }: IMySampleNode) => {
            // my Node logic
        }
    })

Versions

Current Tags

Version History

Package Sidebar

Install

npm i @cognigy/extension-tools

Weekly Downloads

228

Version

0.15.1

License

SEE LICENSE IN LICENSE

Unpacked Size

53.8 kB

Total Files

18

Last publish

Collaborators

  • kwinto
  • x.jorda
  • lkolapp
  • dshire
  • mayrbenjamin92
  • pedily