npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cognigy/extension-tools

v0.16.1

Published

Tools for building Cognigy.AI extensions for Cognigy.AI 4

Downloads

1,151

Readme

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
	]
});

Usage

Field Sections

Instead of showing all configured Fields among themselves, they can be organized into sections using the sections and form properties, improving the Node Editor's user experience.

In this example, the Node's form will be structured like this:

  • Field 1
  • Section 1
    • Field 2
    • Field 3
  • Section 2
    • Field 4
    • Field 5
createNodeDescriptor({
    // ...
    fields: [
        // ... (field1, field2, field3, field4, field5)
    ],
    sections: [
        {
            key: "section1",
            label: "Section 1",
            fields: ["field1", "field2"]
        },
        {
            key: "section2",
            label: "Section 2",
            fields: ["field3", "field4"]
        }
    ],
    form: [
		{
			key: "field1",
			type: "field"
		},
        {
            key: "section1",
            type: "section"
        },
        {
            key: "section2",
            type: "section"
        }
    ]
});

Appearance Customization

It's possible to customize the appearance of a Node in order to improve recognizability of the Node's type and content configuration in the Flow Editor.

In this example, the Flow Node will have a light pink color with dark text on it, and the second line of the Flow will preview the value configured in the field1 field.

createNodeDescriptor({
    // ... (field1)
    appearance: {
        color: "#FFAABB",
        textColor: "#222222"
    },
    preview: {
        key: "field1",
        type: "text"
    }
});

Tokens

A Node can introduce new "Cognigy Tokens" to the Flow Editor when the Extension is installed by defining the tokens section. This can be useful when the Node writes data e.g. into the conversation context that can then easilly accessed through a graphical token in the CognigyScript editors.

The following Node grants easy access to its result value by a Cognigy Token called Some Node Result that can be used e.g. in Say Nodes.

createNodeDescriptor({
    // ...
    function: async ({ cognigy }) => {
        // ...
        cognigy.context.someNodeResult = "example result";
    },
    tokens: [
        {
            type: "context",
            label: "Some Node Result",
            script: "context.someNodeResult"
        }
    ]
});

Conditional Fields and Sections

It is possible to conditionally show Fields and Sections based on other parts of the Node's configuration in order to improve the user experience for Flow builders. Fields and Sections with their condition property configured will only be visible in the Node Editor UI if their configured condition is met.

Conditional Field

In this example, the field field2 is only shown if the value of field1 is true.

createNodeDescriptor({
	// ...
	fields: [
		{
			key: "field1",
			type: "toggle",
			// ...
			defaultValue: false,
		},
		{
			key: "field2",
			// ...
			condition: {
				key: "field1",
				value: true
			}
		}
	]
});

In this example, field2 will only be shown if the value of field1 is "example".

createNodeDescriptor({
	// ...
	fields: [
		{
			key: "field1",
			type: "text",
			// ...
		},
		{
			key: "field2",
			// ...
			condition: {
				key: "field1",
				value: "example"
			}
		}
	]
});

In this example, field2 will only be shown if the value of field1 is either "example1" or "example2".

createNodeDescriptor({
	// ...
	fields: [
		{
			key: "field1",
			type: "text",
			// ...
		},
		{
			key: "field2",
			// ...
			condition: {
				key: "field1",
				value: ["example1", "example2"]
			}
		}
	]
});

Conditional Section

In this example, the section1 section and all its nested fields are only shown if the value of field1 is true.

createNodeDescriptor({
	// ...
    fields: [
        // ... (field1)
    ],  
    sections: [
        {
            key: "section1",
            // ...
            condition: {
                key: "field1",
                value: true
            }
        }
    ]
});

Negation

In this example, field2 will only be shown if the value of field1 is not true.

createNodeDescriptor({
    // ...
    fields: [
        // ... (field1)
        {
            key: "field2",
            // ...
            condition: {
                key: "field1",
                value: true,
                negate: true
            }
        }
    ]
})

AND Operator

In this example, field3 is only shown if the values of both field1 and field2 are set to true.

createNodeDescriptor({
    // ...
    fields: [
        // ... (field1 and field2)
        {
            key: "field3",
            // ...
            condition: {
                and: [
                    {
                        key: "field1",
                        value: true
                    },
                    {
                        key: "field2",
                        value: true
                    }
                ]
            }
        }
    ]
});

OR Operator

In this example, field3 is only shown if at least one of the values of field1 or field2 is set to true.

createNodeDescriptor({
    // ...
    fields: [
        // ... (field1 and field2)
        {
            key: "field3",
            // ...
            condition: {
                or: [
                    {
                        key: "field1",
                        value: true
                    },
                    {
                        key: "field2",
                        value: true
                    }
                ]
            }
        }
    ]
});

Nested Operators

In this example, field4 is only shown either if field1 and field2 are both set to true or if field3 is set to true.

createNodeDescriptor({
    // ...
    fields: [
        // ... (field1, field2 and field3)
        {
            key: "field4",
            // ...
            condition: {
                or: [
                    {
                        and: [
                            {
                                key: "field1",
                                value: true
                            },
                            {
                                key: "field2",
                                value: true
                            }
                        ]
                    },
                    {
                        key: "field3",
                        value: true
                    }
                ]
            }
        }
    ]
});

Child Nodes

You can build decision-making and branching techniques using Child Nodes and the setNextNode API. This is e.g. used in the Lookup and If Nodes.

The example below shows how to build a random Node and its exclusive Child Node type, randomChild. When executed, it picks one of its children at random and continues Flow execution at that Child Node.

createNodeDescriptor({
    // ...
    type: "random",

    // pick a random configuration from one of its child Nodes
    // and continue Flow execution there
    function: async ({ childConfigs, cognigy }) => {
        if (childConfigs.length > 0) {
            const nextNode = childConfigs[Math.floor(Math.random() * childConfigs.length)]
            cognigy.api.setNextNode(nextNode.id);
        }
    },

    // only "randomChild" Nodes are allowed as children for this Node!
    constraints: {
        placement: {
            children: {
                whitelist: ["randomChild"]
            }
        }
    },

    // when this Node is created, two "randomChild" child Nodes
    // will be created automatically
    dependencies: {
        children: ["randomChild", "randomChild"]
    }
});

createNodeDescriptor({
    // ...
    type: "randomChild",
    parentType: "random",
    appearance: {
        variant: "mini"
    }
});

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
        }
    })

Option 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).

Every time 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.