Scripting

Voxta supports scripting in scenarios, scenarios, events and actions. Scripts use JavaScript modules, and supports most modern features of JavaScript. Scripts can be used to generate replies, modify state, and interact with the app running the chat.

Because scripts are modules, you can import { chat} from "@voxta"; to access Voxta’s chat state, and you can use import with scenario scripts (import { myCode } from "./my_file";). Scripts live for the duration of the chat instance, so if you exit the chat, variables will be reset; use chat.setFlags() and chat.variables to persist data across chat sessions.

Triggers

Scripts must export a function called trigger, that will be called when the event or action happens.

import { chat } from "@voxta";

export function trigger(e) {
    // Your code here
}

The chat object

You can import the chat object from the @voxta package to act on the current chat.

Sending a message

You can use script to generate replies. Here is an example of all possible reply types you can see. See Messages for more information.

export function trigger(e) {
    // Message types:
    chat.instructions('Instructions');
    chat.note('Note');
    chat.secret('Secret');
    chat.event('Event');
    chat.story('Story');
    // Reply as one of the participants:
    chat.userMessage('User');
    chat.characterMessage('Character');
    chat.roleMessage('main', 'Your message here');
    // Customize the reply, e.g. generate a secret using story writer:
    chat.customMessage({
        role: 'Secret',
        text: 'Generated Secret',
        useStoryWriter: true,
        maxNewTokens: 10,
        maxSentences: 5,
    });
}

Examples:

  1. Trigger a reply as the user (you):
import { chat } from "@voxta";

export function trigger(e) {
    chat.userMessage('Hey, How are you?');
}

user reply

  1. Trigger a reply as a character:
import { chat } from "@voxta";

export function trigger(e) {
    chat.characterMessage('Hey, I am Voxta, nice to meet you!');
}

char reply

3.Trigger a reply as a character with an assigned scenario role.

import { chat } from "@voxta";

export function trigger(e) {
    chat.roleMessage('role2', 'How are you doing today?');
}

role2 char

Character

You can get the main character by doing e.character

import { chat } from "@voxta";

export function trigger(e) {
    const name = e.character.name;
}

You can also access other characters by their role, for example chat.roles.role1.

import { chat } from "@voxta";

export function trigger(e) {
    const name = chat.roles.role1.name;
}

User

Get the user’s name using e.user.name.

import { chat } from "@voxta";

export function trigger(e) {
    const name = e.user.name;
}

Arguments

Get the action invocation arguments using e.arguments["some_name"] or e.arguments.some_name.

Note: Arguments can only be defined by providers at the moment.

import { chat } from "@voxta";

export function trigger(e) {
    const speed = e.arguments.speed;
}

Stateful variables and flags

You can use variables and flags to store data across chat sessions. variables is a map (dictionary) in which you can store values of integers, strings, booleans or floats. Flags are a set of values that can be present or missing, and can then be used to filter contexts and actions (see Flags).

export function trigger(e) {
    // Set a variable
    chat.variables.myVariable = 123;
    // Or
    chat.set("myVariable", 123);
    // Get a variable, remember that it can be undefined!
    const myVariable1 = chat.variables.myVariable;
    // Or, using a default value
    const myVariable2 = chat.get("myVariable", 0);
    // Set a single flag
    chat.setFlag('my_flag');
    // Set multiple flag (set flag, set enum flag, unset flag)
    chat.setFlags('my_flag', 'my_enum_flag.enum_value', '!another_flag');
    // Check flags
    if (chat.hasFlag('my_flag')) {
        // Do something
    }
};

It is often a good idea to initialize values in your scenario initialization script, for example:

import { chat } from "@voxta";

chat.addEventListener('start', () => {
    chat.variables.myVariable = 0;
});

Because those are normal variables, you can also do things like chat.variables.counter++ or chat.variables.score += 5 when they are correctly initialized.

Expiring flags

You can set flags that expire after a certain amount of time. This is useful for temporary flags that should only be active for a short period of time, such as cooldowns.

export function trigger(e) {
    // Set a flag that expires in 10 conversation messages (excluding events)
    chat.setFlag('cooldown_flag', { messages: 10 });

    // Set a flag that expires in 2 minutes
    chat.setFlag('cooldown_flag', { seconds: 2 * 60 });
};

Enable or disable roles

Use this to toggle whether a character assigned to a role is included in the chat or not.

    export function trigger(e) {
        chat.setRoleEnabled("role2", true);
    };

App Triggers

This is a very powerful way to interact with the app running your chat. See your application’s documentation for more details on the triggers it provides.

export function trigger(e) {
    // Trigger an app event, provide arguments as strings, booleans or floats
    chat.appTrigger('my_event', "arg1", "arg2");
};

You can also run app triggers in the messages queue, for example if you want a sound to play after some messages.

export function trigger(e) {
    chat.queueAppTrigger('my_event', "arg1", "arg2");
};

Chat Time

You can get the time in seconds since the chat started using chat.time.

The e object

The e object is passed to the trigger function and contains information about the action and message.

Message

You can read the role and text of the incoming message that triggered the script using e.message.

export function trigger(e) {
    // NOTE: Role can be User, Assistant, Story, etc.
    const role = e.message.role;
    const text = e.message.text;
};

When the script is triggered by a character message, you can read the character’s name and scenario role using e.character.

export function trigger(e) {
    // NOTE: This is the scenario role, e.g. role1
    const scenarioRole = e.character.scenarioRole;
    const name = e.character.name;
};

You can also access id, senderId (the character or user ID that sent the message), index (the index of the message), conversationIndex (only counting user and character messages), and role (which can be Assistant, User, Event etc.).

Chat Flow

You can force the next reply to be a specific character or user, overriding the normal flow of the chat.

import { chat } from "@voxta";

export function trigger(e) {
    // Force the next reply to be from a specific character
    e.chatFlow(chat.roles.main);
    // Force the next reply to be from the user
    e.chatFlow(chat.user);
};

Evaluate Next Event

By default, when an event is evaluated, no other events are evaluated. You can force the next event to be evaluated by calling e.evaluateNextEvent(). This can be useful when you want an event to evaluate before others or if you need multiple events to work in tandem.

export function trigger(e) {
    // Instead of stopping, the next valid event will be executed
    e.evaluateNextEvent();
};

The character object

The character object has these properties:

  • id: The character’s unique identifier.
  • name: The character’s name.
  • scenarioRole: The character’s role in the scenario.
  • assets: A list of asset paths for that character.

Import and Export

You can create reusable functions and values in scenario scripts. For example, in your scenario script you could define a function like this in a file named lib:

import { chat } from "@voxta";

export function winRound(e, points) {
    const newScore = chat.set("score", chat.get("score", 0) + points);
    chat.note(`${e.character.name} won this round! The score is now: ${score} points`);
};

In your trigger, you can now use it like this:

import { winRound } from './lib';

export function trigger(e) {
    winRound(e, 10);
};

You can also export variables, however remember than if you expert a scalar (e.g. export let score = 5;) you cannot update it, due to the way modules work. You should do instead export let score = { value: 5 }; and then update it as score.value += 10;. Do prefer chat variables for anything that should be persisted.

Event listeners

You can listen to events in the chat using chat.addEventListener(event, callback). The callback will be called when the event happens. You can use this to trigger actions or modify the chat state.

List: start, userMessageReceived generating speechStart speechComplete

Init event (init)

The init event is triggered when starting a session, this is typically used to initialize local variables.

import { chat } from "@voxta";

chat.addEventListener('init', () => {
    console.log("Initializing...");
});

Start event (start)

The start event is triggered when the chat begins, before any messages are sent.

import { chat } from "@voxta";

chat.addEventListener('start', () => {
    console.log("Chat started!");
});

Transcription started event (transcriptionStarted)

The transcriptionStarted event is triggered when the user starts talking (at least two words).

import { chat } from "@voxta";

chat.addEventListener('transcriptionStarted', (e) => {
    console.log(`User started talking...`);
});

Transcription finished event (transcriptionFinished)

The transcriptionFinished event is triggered when the user starts talking (at least two words).

import { chat } from "@voxta";

chat.addEventListener('transcriptionFinished', (e) => {
    if (e.text) {
        console.log(`User said: ${e.text}`);
    } else {
        console.log(`User stopped talking...`);
    }
});

User message received event (userMessageReceived)

The userMessageReceived event is triggered when the user sends a message.

import { chat } from "@voxta";

chat.addEventListener('userMessageReceived', (e) => {
    console.log(`User message received: ${e.message.text}`;
});

You can re-write the user’s message when receiving userMessageReceived:

chat.addEventListener("userMessageReceived", (e) => {
    e.rewriteUserMessage(e.message.text.replace(/John/, 'Jane'));
});

Generating speech event (generating)

The generating event is triggered when the chat is generating a reply.

import { chat } from "@voxta";

chat.addEventListener('generating', (e) => {
    console.log(`Generating reply from ${e.character.name}...`);
});

Speech start event (speechStart)

The speechStart event is triggered when the chat starts speaking. Not triggered if voice is disabled.

import { chat } from "@voxta";

chat.addEventListener('speechStart', (e) => {
    console.log(`Speech chunk from ${e.character.name}: ${e.text}`);
});

Speech complete event (speechComplete)

The speechComplete event is triggered when the chat finishes speaking. Triggered even when the voice is disabled.

import { chat } from "@voxta";

chat.addEventListener('speechComplete', (e) => {
    console.log(`Speech completed for ${e.character.name}.`);
});