🧩 JavaScript API
Since v0.75.0
📖 Overview
The Tavo JavaScript API is a set of JavaScript interfaces for players and creators, enabling users to access powerful functionalities and high playability when JavaScript support is enabled.
✨ Vibe Coding Friendly
For non-professional programming players, we strongly recommend copying this document to an AI and letting the AI generate highly playable code that integrates closely with Tavo!
⚙️ Variables
Variables are used to store data. Native JavaScript variables only survive within the page and are lost after refresh, so we provide a set of variable APIs to help users store data long-term.
Get a Variable
tavo.get(<name>[, <scope>])
For example:
let age = tavo.get('age') // Get "age" from chat variables
let bestScore = tavo.get('bestScore', 'global') // Get global best score
let lover = tavo.get('lover', 'character') // Get the current character's lover
Scope
Scope defines where variables are available. We currently support:
- chat: Chat scope, the default scope. Variables are only accessible in the current chat. You should always prioritize this scope (exportable with the chat).
- global: Global scope. Variables can be accessed and saved across conversations (so naming collisions require extra care).
Set a Variable
tavo.set(<name>, <value>[, <scope>])
For example:
tavo.set('age', 16) // Set chat variable age = 16
tavo.set('Lily_lover', 'Colin', 'global') // Set a global variable: Lily's lover is Colin
tavo.set('status', { hp: 100, mp: 32, location: 'Cave' }) // Set current chat status: HP 100, MP 32, location Cave
Delete a Variable
tavo.unset(<name>[, <scope>])
For example:
tavo.set('age', 16) // age = 16
tavo.unset('age') // age = null
Variable Paths
When operating on variables, path-style access is supported, for example:
tavo.set('status', { hp: 100, mp: 50 })
tavo.get('status.hp') // 100
tavo.unset('status.hp') // status = { mp: 50 }
Use Variables in Prompts
You can send variables to the model through prompts by using macros:
{{getvar::<name>}} gets a variable (scope: chat, current chat)
{{getglobalvar::<name>}} gets a variable (scope: global)
For example:
{{char}} has a new name: {{getvar::name}}
{{user}} current HP: {{getvar::status.hp}}
Global all-time best score: {{getglobalvar::highestScore}}
For more variable macros, see
Example
Copy the following into any bubble to see it in action.
<h3>Variable API Demo</h3>
<pre id="log" style="background: #0006; font-size: 12px; padding: 1em 1.5em; min-height: 80px; max-height: 300px; overflow-y: auto;"></pre>
<p style="margin: 4px 0; font-size: 12px; opacity: .6;">chat scope (saved with current chat)</p>
<div style="display: grid; grid-template-columns: auto auto auto;">
<button onclick="varGet()">tavo.get (chat)</button>
<button onclick="varSet()">tavo.set (chat)</button>
<button onclick="varUnset()">tavo.unset (chat)</button>
</div>
<p style="margin: 8px 0 4px; font-size: 12px; opacity: .6;">global scope (saved across chats and still exists after switching conversations)</p>
<div style="display: grid; grid-template-columns: auto auto auto;">
<button onclick="varGetGlobal()">tavo.get (global)</button>
<button onclick="varSetGlobal()">tavo.set (global)</button>
<button onclick="varUnsetGlobal()">tavo.unset (global)</button>
</div>
<script>
const log = (...args) => {
const text = args.map(v => typeof v === 'string' ? v : JSON.stringify(v, null, 2)).join(' ');
document.getElementById('log').textContent = text + '\n\n';
};
async function varGet() {
const val = await tavo.get('status');
log('chat / status =', val ? val : '(not set yet)');
}
async function varSet() {
await tavo.set('status', { hp: 100, mp: 32, location: 'Cave' });
tavo.utils.toast('Set chat / status');
}
async function varUnset() {
await tavo.unset('status');
tavo.utils.toast('Deleted chat / status');
}
async function varGetGlobal() {
const val = await tavo.get('globalScore', 'global');
log('global / globalScore =', val ? val : '(not set yet)');
}
async function varSetGlobal() {
const s = await tavo.get('globalScore', 'global');
const score = (s ? s : 0) + 1;
await tavo.set('globalScore', score, 'global');
tavo.utils.toast(`globalScore +1, current: ${score}`);
}
async function varUnsetGlobal() {
await tavo.unset('globalScore', 'global');
tavo.utils.toast('Deleted global / globalScore');
}
</script>
💬 Messages
Since v0.78.0
You can use this interface to read or modify messages. All message interfaces are tavo.message.<method>(...).
Find Messages
await tavo.message.find(<indexRange>[, <filter>])
Finds messages by floor range indexRange and filter filter, and returns an array:
indexRange type number | array: floor range
- When
number:- Gets the message at the specified floor
- Floors start from 0: first message is 0, second is 1, and so on
- Negative numbers count from the end:
-1is the last message,-2is the second last, and so on
- When
array:[start, end], for example[2, 4], gets messages 2, 3, and 4 (inclusive)[start]means fromstartto the end[0, end]means from floor 0 toend[] | null | undefinedmeans all floors
- In all cases, this interface always returns an array. If a specified floor does not exist, it returns
[].
filter type object: filter conditions
roletypestring: filter by role, optional values (default: all):'system'system messages'assistant'character messages'user'user messages
hiddentypeboolean: filter by hidden flag, optional values (default: all):trueonly hidden messagesfalseonly non-hidden messages
characterstypearray: character ID array, only keeps messages sent by these characters
Message object format:
{
id: 2338, // Message ID
characterId: 34, // Character ID (assistant messages only)
content: 'Hello!', // Message content
hidden: false, // Whether this is a hidden message
role: 'assistant' // Message role
}
For example:
await tavo.message.find(2) // Get the 3rd message
await tavo.message.find([3, 100]) // Get messages 3-100; if only 50 exist, returns 3-50
await tavo.message.find(-1, { role: 'user' }) // Last user message
await tavo.message.find([10], { hidden: false }) // Non-hidden messages with floor >= 10
Get a Single Message
await tavo.message.get(<messageId>)
Gets one message by message ID. Returns null if the ID is invalid or the message does not exist.
let msg = await tavo.message.get(2338) // Get message with ID 2338
Get Current Message
await tavo.message.current()
Gets the message object for the message where this code is executed. Fields are the same as "Message object format" above and tavo.message.get.
Typical use cases: read role info on this message (tavo.character.get(currentMessage.characterId)), or modify this message (tavo.message.update).
const self = await tavo.message.current()
console.log(self)
Get Message Count
await tavo.message.count()
Gets the total number of messages in the current chat (including hidden messages). Commonly used to locate the last floor: first floor is 0, last floor is messageCount - 1.
let lastIndex = await tavo.message.count() - 1
console.log(lastIndex)
Append a Message
await tavo.message.append(<message>)
Appends a message to the end of the current chat. Returns the new message ID on success, or null on failure.
message type object, common fields:
contenttypestring: message content (required)roletypestring:'assistant' | 'user'(defaults to'assistant')characterIdtypenumber: whenrole = 'assistant', you can specify the speaker character ID (optional in 1-on-1 chat, required in group chat)hiddentypeboolean: whether hidden (defaultfalse)
Note:
- If
role = 'assistant'andcharacterIdis omitted, the character is auto-inferred from current session context.- If no valid character can be inferred, or the character does not belong to current chat, creation fails and returns
null.
For example:
let newId = await tavo.message.append({
role: 'assistant',
characterId: 34,
content: 'This is an appended message',
hidden: false,
})
In 1-on-1 chats, creating a visible message can be simplified:
let newId = await tavo.message.append({
content: 'This is an appended message. role defaults to assistant; in 1-on-1 chats, character is auto-inferred; hidden defaults to false',
})
Update a Message
await tavo.message.update(<message>)
Updates an existing message by message ID. Returns the message ID on success, or null on failure.
message type object, common fields:
idtypenumber: message ID to update (required)contenttypestring: updated message content (required)reasoningtypestring: reasoning content (optional, empty string clears it)hiddentypeboolean: whether hidden (optional, defaults tofalse)
const lastMessage = (await tavo.message.find(-1))[0] // Get the last message
lastMessage.content = 'Updated content'
lastMessage.reasoning = 'Optional reasoning content'
lastMessage.hidden = true // Mark as hidden
await tavo.message.update(lastMessage) // Update the last message
Delete a Message
await tavo.message.delete(<messageId>)
Deletes a message by message ID. Returns the deleted message ID on success, or null on failure.
const count = await tavo.message.count(); // Get total message count
const midIndex = Math.floor(count / 2);
const midMessage = (await tavo.message.find(midIndex))[0] // Get a middle message
await tavo.message.delete(midMessage.id) // Delete that middle message
🗨️ Chat
You can use this interface to get information about the current chat. All chat interfaces are tavo.chat.<method>(...).
Get Current Chat
await tavo.chat.current()
Gets the currently active chat information. Returns null if there is no active chat.
For example:
let chat = await tavo.chat.current()
console.log(chat.name) // Print current chat name
console.log(chat.characters[0]?.name) // Print first character name
console.log(chat.persona?.name) // Print current user persona name (if any)
Async await/async
In Tavo JS API, almost all interfaces except variable operations require asynchronous calls.
For async calls, prepend await, for example: let chat = await tavo.chat.current(). If you forget and write let chat = tavo.chat.current(), it will fail (check logs in the JavaScript console in the sidebar).
Also, await can only be used in an async function (or module top level), for example:
async function demo() {
let chat = await tavo.chat.current();
}
Likewise, if you forget to add async before function but still use await inside, it will throw an error.
In short: except for variable operations, all Tavo JS API calls must use await, and the calling function must be declared with async.
Update Current Chat
await tavo.chat.update(<chat>)
Updates the current chat.
Updatable fields:
name: chat titlecharacters: character ID array (directly replaces current character list)persona: user persona ID
await tavo.chat.update({
name: 'New Chat Title',
characters: [12, 34],
persona: 5,
})
Note: This interface only updates the current chat, and does not support updating other sessions by chat ID.
Chat Object Fields
The chat object returned by current commonly includes:
{
id: 1, // Chat ID
name: 'Conversation with Alice', // Chat name
characters: [ // Character summary list in this chat
{
id: 12,
name: 'Alice',
avatar: 'alice.png'
},
{
id: 7,
name: 'Lee',
avatar: 'lee.png'
},
],
persona: { // Current user persona summary (can be null)
id: 5,
name: 'Default User Persona',
},
preset: { // Current preset summary
id: 9,
name: 'Default Preset',
},
lorebooks: [{
id: 17,
name: 'Sleepless City',
}],
regexes: [{ // Enabled regex summary list
id: 3,
name: 'Remove Stage Directions',
}],
}
Example
Copy the following into any bubble to see it in action.
<h3>Chat API Demo</h3>
<pre id="log" style="background: #0006; font-size: 12px; padding: 1em 1.5em; min-height: 80px; max-height: 300px; overflow-y: auto;"></pre>
<div style="display: grid; grid-template-columns: auto auto;">
<button onclick="chatCurrent()">tavo.chat.current</button>
<button onclick="chatRename()">Rename current chat</button>
</div>
<script>
const log = (...args) => {
const text = args.map(v => typeof v === 'string' ? v : JSON.stringify(v, null, 2)).join(' ');
document.getElementById('log').textContent = text + '\n\n';
};
async function chatCurrent() {
const chat = await tavo.chat.current();
log(chat);
}
async function chatRename() {
const chat = await tavo.chat.current();
if (!chat) return tavo.utils.toast('No active chat right now');
const newName = prompt('Enter the new chat name', chat.name);
if (!newName || newName === chat.name) return;
chat.name = newName;
await tavo.chat.update(chat);
tavo.utils.toast(`Renamed to: ${newName}`);
}
</script>
🧙 Characters
You can use this interface to manage characters. All character interfaces are tavo.character.<method>(...).
Get All Character Summaries
await tavo.character.all()
Returns an array of character summary objects (each item only contains summary fields such as id, name, and avatar):
let chars = await tavo.character.all()
console.log(chars[0].id) // e.g. 12
console.log(chars[0].avatar) // e.g. "chara/alice.png"
console.log(chars[0].name) // e.g. "Alice"
Get a Single Character
await tavo.character.get(<characterId>)
Gets a character object by character ID. Returns null if not found.
let char = await tavo.character.get(12)
if (char) {
console.log(char.name)
}
Find Characters by Name
await tavo.character.find(<name>[, <options>])
Finds characters by name and returns an array of character objects. options.match supports: 'exact' | 'prefix' | 'suffix' | 'contains' (default: 'exact').
let chars = await tavo.character.find('Alice')
let chars2 = await tavo.character.find('Ali', { match: 'prefix' })
console.log(chars.length)
Create a Character
await tavo.character.create(<character>)
Creates a character and returns the new character ID. character.name and character.first_mes are required.
let id = await tavo.character.create({
name: 'Alice',
first_mes: 'Hi, I am Alice.',
description: 'A gentle guide',
})
Update a Character
await tavo.character.update(<character>)
Updates a character and returns the character ID. character.id, character.name, and character.first_mes are required.
await tavo.character.update({
id: 12,
name: 'Alice',
first_mes: 'Hi, I am Alice.',
personality: 'Patient, detail-oriented',
})
Delete a Character
await tavo.character.delete(<characterId>)
Deletes a character by character ID:
await tavo.character.delete(12)
await tavo.character.delete(char) // char must be a character object that includes id
Character Object Fields
Character objects (returned by get / find) commonly include:
{
id: 12, // Unique character ID
avatar: 'xxx.png', // Character avatar image URL or path
name: 'Alice', // Character name (required)
description: '...', // Character intro/description
first_mes: '...', // Character greeting (required)
personality: '...', // Character personality description
scenario: '...', // Applicable scenario/use-case description
mes_example: '...', // Message examples, separated by <START>
creator_notes: '...', // Creator notes or additional remarks
system_prompt: '...', // System prompt
post_history_instructions: '...', // Extra guidance after message history
alternate_greetings: ['...'], // Alternate greetings for this character
tags: ['guide'], // Character tags for classification/search
creator: 'Colin', // Creator username or nickname
character_version: '1.0', // Character version
nickname: 'Ali', // Character nickname/alias; if set, it replaces name in {{char}} output
group_only_greetings: ['...'], // Greetings only used in group chat
creation_date: new Date('2026-03-05T10:20:30.000Z'), // Creation time (Date object)
modification_date: new Date('2026-03-05T11:30:00.000Z'), // Last modified time (Date object)
}
Note: Creating, updating, and deleting characters will show a confirmation dialog. If the user cancels, the operation will not take effect.
Example
Copy the following into any bubble to see it in action.
<h3>Character API Demo</h3>
<pre id="log" style="background: #0006; font-size: 12px; padding: 1em 1.5em; min-height: 100px; max-height: 400px; overflow-y: auto;"></pre>
<div style="display: grid; grid-template-columns: auto auto auto auto auto auto;">
<button onclick="charAll()">tavo.character.all</button>
<button onclick="charGet()">tavo.character.get</button>
<button onclick="charFind()">tavo.character.find</button>
<button onclick="charCreate()">tavo.character.create</button>
<button onclick="charUpdate()">tavo.character.update</button>
<button onclick="charDelete()">tavo.character.delete</button>
</div>
<script>
const log = (...args) => {
const text = args.map(v => typeof v === 'string' ? v : JSON.stringify(v, null, 2)).join(' ');
document.getElementById('log').textContent = text + '\n\n';
};
async function charAll() {
const chars = await tavo.character.all();
log(chars);
}
async function charGet() {
const id = prompt('Enter a character ID (you can get it from tavo.character.all())');
if (!id) return;
const char = await tavo.character.get(Number(id));
log(char);
}
async function charFind() {
const name = prompt('Enter the character name to search');
if (!name) return;
const chars = await tavo.character.find(name);
log(chars);
}
async function charCreate() {
const id = await tavo.character.create({
name: 'Demo Character',
first_mes: 'Hi, I am a demo character!',
description: 'This is a test character created via tavo.character.create.',
});
if (!id) return;
const char = await tavo.character.get(id);
log(char);
}
async function charUpdate() {
const chars = await tavo.character.find('Demo Character');
if (!chars.length) return tavo.utils.toast('Click tavo.character.create first to create a character');
const char = chars[0];
const newDesc = prompt('Edit character description:', char.description);
if (!newDesc) return;
char.description = newDesc;
await tavo.character.update(char);
log(await tavo.character.get(char.id));
}
async function charDelete() {
const chars = await tavo.character.find('Demo Character');
if (!chars.length) return tavo.utils.toast('Click tavo.character.create first to create a character');
const id = await tavo.character.delete(chars[0].id);
if (!id) return;
tavo.utils.toast('Deleted Demo Character');
log('Deleted');
}
</script>
🎭 Personas
You can use this interface to manage user personas. All persona interfaces are tavo.persona.<method>(...).
Get All Persona Summaries
await tavo.persona.all()
Returns an array of persona summary objects (each item includes id and name):
let personas = await tavo.persona.all()
console.log(personas[0].id) // e.g. 5
console.log(personas[0].name) // e.g. "Default User Persona"
Get a Single Persona
await tavo.persona.get(<personaId>)
Gets a persona object by persona ID. Returns null if not found:
let persona = await tavo.persona.get(5)
if (persona) {
console.log(persona.name)
console.log(persona.description)
}
Find Personas by Name
await tavo.persona.find(<name>[, <options>])
Finds personas by name and returns an array of persona objects. options.match supports: 'exact' | 'prefix' | 'suffix' | 'contains' (default: 'exact').
let personas = await tavo.persona.find('Default')
let personas2 = await tavo.persona.find('Def', { match: 'prefix' })
console.log(personas.length)
Create a Persona
await tavo.persona.create(<persona>)
Creates a persona and returns the new persona ID. persona.name and persona.description are required.
let id = await tavo.persona.create({
name: 'Detective Persona',
description: 'Detail-oriented and strong at structured reasoning.',
avatar: 'chara/persona-detective.png',
})
Update a Persona
await tavo.persona.update(<persona>)
Updates a persona. persona.id, persona.name, and persona.description are required.
await tavo.persona.update({
id: 5,
name: 'Default User Persona',
description: 'Use a more concise tone and prioritize actionable conclusions.',
avatar: 'chara/persona-default.png',
active: true,
})
Delete a Persona
await tavo.persona.delete(<personaId>)
Deletes a persona by persona ID:
await tavo.persona.delete(5)
await tavo.persona.delete(persona) // persona must be a persona object that includes id
Persona Object Fields
Persona objects (returned by get) commonly include:
{
id: 5, // Unique persona ID
name: 'Default User Persona', // Persona name (required)
description: '...', // Persona description (required)
avatar: 'xxx.png', // Persona avatar URL or path (optional)
active: true, // Whether this is the default persona
sortIndex: 12, // Sorting index
}
Example
Copy the following into any bubble to see it in action.
<h3>Persona API Demo</h3>
<pre id="log" style="background: #0006; font-size: 12px; padding: 1em 1.5em; min-height: 100px; max-height: 400px; overflow-y: auto;"></pre>
<div style="display: grid; grid-template-columns: auto auto auto auto auto auto;">
<button onclick="personaAll()">tavo.persona.all</button>
<button onclick="personaGet()">tavo.persona.get</button>
<button onclick="personaFind()">tavo.persona.find</button>
<button onclick="personaCreate()">tavo.persona.create</button>
<button onclick="personaUpdate()">tavo.persona.update</button>
<button onclick="personaDelete()">tavo.persona.delete</button>
</div>
<script>
const log = (...args) => {
const text = args.map(v => typeof v === 'string' ? v : JSON.stringify(v, null, 2)).join(' ');
document.getElementById('log').textContent = text + '\n\n';
};
async function personaAll() {
const personas = await tavo.persona.all();
log(personas);
}
async function personaGet() {
const id = prompt('Enter a persona ID (you can get it from tavo.persona.all())');
if (!id) return;
const persona = await tavo.persona.get(Number(id));
log(persona);
}
async function personaFind() {
const name = prompt('Enter the persona name to search');
if (!name) return;
const personas = await tavo.persona.find(name);
log(personas);
}
async function personaCreate() {
const id = await tavo.persona.create({
name: 'Demo Persona',
description: 'This is a test persona created via tavo.persona.create. It is concise and gives direct conclusions.',
});
const persona = await tavo.persona.get(id);
log(persona);
}
async function personaUpdate() {
const personas = await tavo.persona.find('Demo Persona');
if (!personas.length) return tavo.utils.toast('Click tavo.persona.create first to create a persona');
const persona = personas[0];
const newDesc = prompt('Edit persona description:', persona.description);
if (!newDesc) return;
persona.description = newDesc;
await tavo.persona.update(persona);
log(await tavo.persona.get(persona.id));
}
async function personaDelete() {
const personas = await tavo.persona.find('Demo Persona');
if (!personas.length) return tavo.utils.toast('Click tavo.persona.create first to create a persona');
const id = await tavo.persona.delete(personas[0].id);
if (!id) return;
tavo.utils.toast('Deleted Demo Persona');
log('Deleted');
}
</script>
🎛️ Presets
You can use this interface to manage presets. All preset interfaces are tavo.preset.<method>(...).
Get All Preset Summaries
await tavo.preset.all()
Returns an array of preset summary objects (each item includes id and name):
let presets = await tavo.preset.all()
console.log(presets[0].id) // e.g. 1
console.log(presets[0].name) // e.g. "Default"
Get a Single Preset
await tavo.preset.get(<presetId>)
Gets a preset object by preset ID. Returns null if not found:
let preset = await tavo.preset.get(1)
if (preset) {
console.log(preset.name)
console.log(preset.entries.length)
console.log(preset.basicPrompts.chatStart)
}
Find Presets by Name
await tavo.preset.find(<name>[, <options>])
Finds presets by name and returns an array of full preset objects. options.match supports: 'exact' | 'prefix' | 'suffix' | 'contains' (default: 'exact').
let presets = await tavo.preset.find('Default')
let presets2 = await tavo.preset.find('Def', { match: 'prefix' })
console.log(presets.length)
Create a Preset
await tavo.preset.create(<preset>)
Creates a preset and returns the new preset ID. preset.name is required. Other fields are optional; missing parts in preset.basicPrompts and preset.entries will be filled with built-in defaults.
let id = await tavo.preset.create({
name: 'My Preset',
basicPrompts: {
continueNudge: '[Continue your last message without repeating the original content.]',
},
entries: [
{
identifier: 'abc123',
name: '🌸 Style Control',
content: 'Adopt a refined and elegant narrative style, similar to popular high-quality female-oriented works on platforms like Jinjiang and Changpei.',
},
],
})
Update a Preset
await tavo.preset.update(<preset>)
Updates a preset. preset.id is required. The incoming entries will overwrite the existing entries. A typical flow is get → modify → update.
const preset = await tavo.preset.get(33);
preset.entries.find(e => e.identifier == 'main').content = 'Please reply in Chinese to all of {{user}}\'s questions.';
await tavo.preset.update(preset)
Delete a Preset
await tavo.preset.delete(<presetId>)
Deletes a preset by preset ID:
await tavo.preset.delete(1)
await tavo.preset.delete(preset) // preset must be a preset object that includes id
Preset Object Fields
Preset objects (returned by get / find) include:
{
id: 1, // Preset unique ID
name: 'Default', // Preset name (required)
basicPrompts: { /* BasicPrompts, see below */ },
entries: [], // PresetEntry[] prompt entries list (see below)
}
Basic Prompt Fields (BasicPrompts)
basicPrompts contains various system prompt templates. All fields are optional; missing fields use built-in defaults:
{
persona: '{{persona}}', // Format template for the user persona description
description: '{{description}}', // Format template for the character description
personality: '{{personality}}', // Format template for the character personality (insert location via {{personality}})
scenario: '{{scenario}}', // Format template for the scenario (insert location via {{scenario}})
exampleMessageStart: '[Example Chat]', // Example chat start marker
chatStart: '[Start a new Chat]', // Chat history start marker
groupChatStart: '[Start a new group chat. Group members: {{group}}]', // Group chat start marker
groupNudge: '[Write the next reply only as {{char}}.]', // Nudge a specific role in group chat
continueNudge: '[Continue your last message without repeating its original content.]', // Nudge the "continue" action
impersonation: '[Write your next reply from the point of view of {{user}}...]', // Impersonation prompt for acting as user
lorebook: '{0}', // Wrap template for lorebook entries (insert via {0})
}
Prompt Entry Fields (PresetEntry)
Each item in entries has this structure:
{
// -- Basic info ------------------------------------
identifier: 'main', // Unique entry identifier (built-in entries have fixed identifiers; see the table below)
name: 'Main Prompt', // Entry display name
content: '...', // Prompt body text (no `content` for marker type)
enabled: true, // Whether this entry is enabled (takes effect in the active list)
active: true, // Whether it is included in the active list (inactive entries are archived and do not participate in prompt building)
// -- Type -----------------------------------------
type: 'custom', // Entry type:
// 'builtin' - Built-in prompts (fixed identifier, e.g. main / jailbreak)
// 'marker' - Position marker (no content; marks where other content gets inserted)
// 'custom' - Custom prompt
// -- Role and injection (configurable for custom)-
role: 'system', // Message role: 'system' | 'user' | 'assistant'
injectionPosition: 'relative', // Injection position:
// 'relative' - relative position (follows preset entry order)
// 'absolute' - absolute position (insert at a specific depth in chat history)
injectionDepth: 4, // Injection depth (only effective when injectionPosition is 'absolute')
// 0 = after the last message, 1 = before the last message, and so on
}
Built-in Entry Identifier Table
The following identifier values correspond to built-in fixed prompts or position markers. You can reference them directly when creating/updating:
📚 Lorebooks
You can use this interface to manage lorebooks. All lorebook interfaces are tavo.lorebook.<method>(...).
Get All Lorebook Summaries
await tavo.lorebook.all()
Returns an array of lorebook summary objects (each item includes id, name, and entries):
let lorebooks = await tavo.lorebook.all()
console.log(lorebooks[0].id) // e.g. 3
console.log(lorebooks[0].name) // e.g. "City Setting"
console.log(lorebooks[0].entries) // e.g. 12 (entry count)
Get a Single Lorebook
await tavo.lorebook.get(<lorebookId>)
Gets a lorebook object by ID. Returns null if not found:
let lorebook = await tavo.lorebook.get(3)
if (lorebook) {
console.log(lorebook.name)
console.log(lorebook.entries.length)
}
Find Lorebooks by Name
await tavo.lorebook.find(<name>[, <options>])
Finds lorebooks by name and returns an array of lorebook objects. options.match supports: 'exact' | 'prefix' | 'suffix' | 'contains' (default: 'exact').
let lorebooks = await tavo.lorebook.find('City')
let lorebooks2 = await tavo.lorebook.find('City', { match: 'suffix' })
console.log(lorebooks.length)
Create a Lorebook
await tavo.lorebook.create(<lorebook>)
Creates a lorebook and returns the new lorebook ID. lorebook.name is required.
let id = await tavo.lorebook.create({
name: 'City Setting',
entries: [],
})
Update a Lorebook
await tavo.lorebook.update(<lorebook>)
Updates a lorebook. lorebook.id and lorebook.name are required.
await tavo.lorebook.update({
id: 3,
name: 'City Setting (Remastered)',
entries: [],
})
Delete a Lorebook
await tavo.lorebook.delete(<lorebookId>)
Deletes a lorebook by ID:
await tavo.lorebook.delete(3)
await tavo.lorebook.delete(lorebook) // lorebook must be a lorebook object that includes id
Lorebook Object Fields
Lorebook objects (returned by get / find) include:
{
id: 3, // Unique lorebook ID
name: 'City Setting', // Lorebook name (required)
entries: [], // LorebookEntry[] entry list (see below)
}
Entry Object Fields (LorebookEntry)
Each item in the entries array has this structure:
{
// -- Basic info ------------------------------------
identifier: 'entry-uuid', // Unique entry identifier (string)
name: 'City Overview', // Entry name (for display/search only)
content: 'This is a coastal city that often has thick fog at night.', // Main body injected into prompts
enabled: true, // Whether this entry is enabled
strategy: 'constant', // Trigger strategy: 'constant' (always on) | 'keyword' (keyword-triggered)
// -- Keywords --------------------------------------
keywords: ['city', 'port'], // Primary keyword list (effective when strategy is 'keyword')
secondaryKeywords: ['night', 'fog'], // Secondary keyword list
secondaryKeywordStrategy: 'none', // Secondary keyword matching strategy:
// 'none' - Disable secondary keywords
// 'andAny' - Primary matched and any secondary matched (default)
// 'andAll' - Primary matched and all secondary matched
// 'notAny' - Primary matched and none of secondary matched
// 'notAll' - Primary matched and not all secondary matched
scanDepth: 2, // Message depth for keyword scanning (default 2, max 1000)
caseSensitive: false, // Whether keyword matching is case-sensitive
matchWholeWord: true, // Whether to match whole words only
// -- Injection position ----------------------------
injectionPosition: 'lorebookBefore', // Injection position:
// 'lorebookBefore' - Above character description (↑Char)
// 'lorebookAfter' - Below character description (↓Char)
// 'topOfExampleMessages' - Before example dialog
// 'bottomOfExampleMessages'- After example dialog
// 'atDepth' - Absolute depth in chat history
injectionDepth: 4, // Injection depth, only effective when injectionPosition is 'atDepth'
injectionRole: 'system', // Injection role: 'system' | 'user' | 'assistant'
// -- Probability and behavior ----------------------
probability: 100, // Activation probability (0-100, default 100)
sticky: 0, // Message turns to keep active after activation (0 means no persistence)
cooldown: 0, // Cooldown turns after one activation (0 means no cooldown)
delay: 0, // Delayed activation turns (0 means immediate)
}
Example
Copy the following into any bubble to see it in action.
<h3>Lorebook API Demo</h3>
<pre id="log" style="background: #0006; font-size: 12px; padding: 1em 1.5em; min-height: 100px; max-height: 400px; overflow-y: auto;"></pre>
<div style="display: grid; grid-template-columns: auto auto auto;">
<button onclick="lorebookAll()">tavo.lorebook.all</button>
<button onclick="lorebookGet()">tavo.lorebook.get</button>
<button onclick="lorebookFind()">tavo.lorebook.find</button>
<button onclick="lorebookCreate()">tavo.lorebook.create</button>
<button onclick="lorebookUpdate()">tavo.lorebook.update</button>
<button onclick="lorebookDelete()">tavo.lorebook.delete</button>
</div>
<script>
const log = (...args) => {
const text = args.map(v => typeof v === 'string' ? v : JSON.stringify(v, null, 2)).join(' ');
document.getElementById('log').textContent = text + '\n\n';
};
async function lorebookAll() {
const lorebooks = await tavo.lorebook.all();
log(lorebooks);
}
async function lorebookGet() {
const id = prompt('Enter a lorebook ID (you can get it from tavo.lorebook.all())')
if (!id) return;
const lorebook = await tavo.lorebook.get(id);
log(lorebook);
}
async function lorebookFind() {
const name = prompt('Enter the lorebook name to search')
if (!name) return;
const lorebooks = await tavo.lorebook.find(name);
log(lorebooks);
}
async function lorebookCreate() {
const lorebook = { name: 'Demo Lorebook', entries: [] };
lorebook.entries.push({
identifier: 'abc123',
name: 'About this city',
content: 'This is a coastal city that often has thick fog at night.',
enabled: true,
strategy: 'constant',
});
const id = await tavo.lorebook.create(lorebook);
const lb = await tavo.lorebook.get(id);
log(lb)
const c = confirm('Set it as the lorebook for the current chat?');
if (!c) return;
const chat = await tavo.chat.current();
chat.lorebooks.push(lb);
tavo.chat.update(chat);
}
async function lorebookUpdate() {
const lorebooks = await tavo.lorebook.find('Demo Lorebook');
if (!lorebooks.length) return tavo.utils.toast('Click tavo.lorebook.create first to create a lorebook');
const lorebook = lorebooks[0];
const newContent = prompt(`Edit the content of the first entry in lorebook ${lorebook.name}:`, lorebook.entries[0].content);
lorebook.entries[0].content = newContent;
await tavo.lorebook.update(lorebook);
const lb = await tavo.lorebook.get(lorebook.id);
log(lb)
}
async function lorebookDelete() {
const lorebooks = await tavo.lorebook.find('Demo Lorebook');
if (!lorebooks.length) return tavo.utils.toast('Click tavo.lorebook.create first to create a lorebook');
const lorebook = lorebooks[0];
const id = await tavo.lorebook.delete(lorebook);
log(id)
}
</script>
🎨 Regex
You can use this interface to manage regex groups (a set of find/replace rules). All regex interfaces are tavo.regex.<method>(...).
Get All Regex (Summaries)
await tavo.regex.all()
Returns an array of regex summary objects (each item includes id, name, and entries; entries is the number of rules, not the rule entries array):
let list = await tavo.regex.all()
console.log(list[0].id) // e.g. 2
console.log(list[0].name) // e.g. "My Regex"
console.log(list[0].entries) // e.g. 5 (number of rules)
Get a Single Regex
await tavo.regex.get(<regexId>)
Gets a regex object by ID. Returns null if not found:
let r = await tavo.regex.get(2)
if (r) {
console.log(r.name)
console.log(r.entries.length)
}
Find Regex by Name
await tavo.regex.find(<name>[, <options>])
Finds regex groups by name and returns an array of full regex objects. options.match supports: 'exact' | 'prefix' | 'suffix' | 'contains' (default: 'exact').
let found = await tavo.regex.find('My')
let found2 = await tavo.regex.find('My', { match: 'contains' })
console.log(found.length)
Create a Regex Group
await tavo.regex.create(<regex>)
Creates a regex group and returns the new ID. regex.name is required; regex.entries can be omitted (treated as an empty list). A confirmation dialog will be shown before creating/updating.
let id = await tavo.regex.create({
name: 'Demo Regex',
entries: [
{
name: 'Status Bar',
findRegex: '/<status>(.*?)<\/status>/gim',
replaceString: '<pre>$1</pre>',
placements: ['char'],
timing: 'display',
},
],
})
Update a Regex Group
await tavo.regex.update(<regex>)
Updates a regex group. regex.id and regex.name are required (validated by the front-end wrapper). Typical flow: get → modify → update.
const r = await tavo.regex.get(2)
r.entries[0].enabled = false
await tavo.regex.update(r)
Delete a Regex Group
await tavo.regex.delete(<regexId>)
Deletes a regex group by ID. You can also pass a regex object that includes id:
await tavo.regex.delete(2)
await tavo.regex.delete({ id: 2 })
Regex Object Fields
Regex objects (returned by get / find) have this structure:
{
id: 2,
name: 'My Regex',
entries: [ /* RegexEntry[]; see below */ ],
}
Rule Entry Fields (RegexEntry)
Each item in entries:
{
name: 'Rule display name', // Required (string); otherwise parsing may fail
findRegex: 'pattern', // Find regex (supports JavaScript-like `/pattern/flags`)
replaceString: '', // Replacement string
trimStrings: [], // Additional strings to trim
placements: ['char'], // Target placements (can be multiple):
// 'user' - user input
// 'char' - AI output
// 'reasoning' - reasoning content
// 'lorebook' - lorebook injected content
timing: 'display', // Execution timing:
// 'display' - only display (do not write into persistent messages; similar to ST markdownOnly)
// 'send' - only send into the model (before generation)
// 'sendAndDisplay' - both display and send
// 'receive' - persist after receiving the reply (relevant for input/output)
// 'editAndReceive' - persist and rewrite when receiving edited messages
substitution: 'none', // Macro substitution mode: 'none' | 'raw' | 'escaped'
minDepth: null, // Optional: message depth lower bound (integer)
maxDepth: null, // Optional: message depth upper bound (integer)
enabled: true, // Whether this rule is enabled
}
If you omit fields, the side-end will fill reasonable defaults for findRegex, replaceString, trimStrings, placements, timing, substitution, enabled, etc. (e.g. placements: ['char'], timing: 'display').
🧠 Long-Term Memory
You can use this interface to read or modify long-term memory for the current chat. All interfaces are tavo.memory.<method>(...).
Get Current Memory
await tavo.memory.current()
Gets the current chat memory object:
const memory = await tavo.memory.current()
console.log(memory.enabled) // true / false
console.log(memory.memories.length) // Number of memory items
Update Memory
await tavo.memory.update(<memory>)
Updates current chat memory and returns the updated object. Updatable fields:
enabled: whether memory is enabledmemories: memory text array (string[])
const memory = await tavo.memory.current()
memory.enabled = true
memory.memories = [
'The user prefers concise answers with conclusions first',
'The user tends to keep the character calm and professional',
]
const updated = await tavo.memory.update(memory)
console.log(updated)
Memory Object Fields
current / update returns this object shape:
{
id: 12, // Memory record ID
enabled: true, // Whether long-term memory is enabled
memories: [ // Memory item list (string array)
'User prefers concise replies',
'Avoid repeating already-confirmed information'
],
}
Example
Copy the following into a bubble to see it in action:
<h3>Long-Term Memory API Demo</h3>
<button onclick="addOneMemory()">Add one memory</button>
<pre id="log" style="background: #0006; font-size: 12px; padding: 1em 1.5em; min-height: 80px; max-height: 300px; overflow-y: auto;"></pre>
<script>
const log = (...args) => {
const text = args.map(v => typeof v === 'string' ? v : JSON.stringify(v, null, 2)).join(' ');
document.getElementById('log').textContent = text + '\n\n';
};
async function addOneMemory() {
const memory = await tavo.memory.current();
if (!memory.enabled) {
memory.enabled = true;
tavo.utils.toast('Long-term memory enabled automatically')
}
memory.memories.push(`Watched a grand fireworks show with Guoguo ${new Date().toISOString()}`);
const id = await tavo.memory.update(memory);
tavo.utils.toast(`Added 1 memory item, now total ${memory.memories.length}`);
log(memory);
}
</script>
✨ Generation Requests
You can use this interface to trigger a one-off text generation directly. All generation requests use tavo.generate(...).
Start Generation
await tavo.generate(<prompt>, <options>)
prompttypestring: user input for this generationoptionstypeobject: generation options (pass{}if no extra config is needed)
The return type is string, i.e. text generated by the model.
const result = await tavo.generate('Summarize what happened today in one sentence', {})
console.log(result)
options Fields
options supports the following fields:
contexttypeboolean(defaultfalse):true: generate with current conversation context (reuses current chat state)false: AI generation request unrelated to current conversation (default)
presettypenumber | object(optional):- Pass a preset ID directly, e.g.
12 - If passing an object, only
idis recognized, e.g.{ id: 12 }
- Pass a preset ID directly, e.g.
settingstypeobject(optional): override model parameters for this request
Example:
const text = await tavo.generate(
'Based on our recent conversation, give me 3 action suggestions',
{
context: true,
preset: { id: 8 },
settings: {
temperature: 0.7,
topP: 0.9,
maxCompletionTokens: 300,
},
},
)
console.log(text)
Notes
- This interface is a one-off request that returns full text, not streaming chunks
- Generation requests use the model endpoint bound to the current chat; if no endpoint is available for the current chat, it returns
null
Example
Copy the following into a bubble to see it in action:
<h3>Generation Request API Demo</h3>
<pre id="log" style="background: #0006; font-size: 12px; padding: 1em 1.5em; min-height: 80px; max-height: 300px; overflow-y: auto;"></pre>
<button id="btn-generate" onclick="generate()">Generate Character Card</button>
<p id="status"></p>
<div id="actions" style="display:none; gap:8px;">
<button onclick="downloadJson()">Download JSON File</button>
<button onclick="createCharacter()">Create Character Card Directly</button>
</div>
<script>
let generatedCard = null;
const log = (...args) => {
const text = args.map(v => typeof v === 'string' ? v : JSON.stringify(v, null, 2)).join(' ');
document.getElementById('log').textContent = text + '\n\n';
};
function setUi(loading, status, showActions = false) {
document.getElementById('btn-generate').disabled = loading;
document.getElementById('status').textContent = status;
document.getElementById('actions').style.display = showActions ? 'flex' : 'none';
}
async function generate() {
const p = prompt('Enter the character traits you want');
if (!p) return;
setUi(true, 'Generating...');
try {
let text = await tavo.generate(`Generate a character card from the following info. Output JSON that follows Character Card Spec V3.\n${p}`);
log(text)
text = text.trim();
if (text.startsWith('```') && text.endsWith('```')) {
text = text.replace(/^```[a-zA-Z]*\n?/, '').replace(/```$/, '');
}
generatedCard = JSON.parse(text);
if (generatedCard.mes_example instanceof Array) generatedCard.mes_example = generatedCard.mes_example.join('\n')
setUi(false, `Character card "${generatedCard.name}" generated`, true);
} catch (e) {
log(e);
console.log(e);
setUi(false, `Character card generation failed`, false)
}
}
function downloadJson() {
tavo.utils.export(`${generatedCard.name}.json`, JSON.stringify(generatedCard));
}
async function createCharacter() {
await tavo.character.create(generatedCard);
}
</script>
⌨️ Input Box
You can use this interface to read or manipulate the chat input box. All input interfaces are tavo.input.<method>(...).
Read Input Box
await tavo.input.get()
Gets the current text content in the input box:
let text = await tavo.input.get() // Get current input box content
Write to Input Box
tavo.input.set(<text>)
Overwrites the input box content (clears existing content):
tavo.input.set('Hello!') // Replace input box content with "Hello!"
Append to Input Box
tavo.input.append(<text>)
Appends text to the end of current input box content:
tavo.input.append(' Let us keep chatting') // Append text to existing content
Clear Input Box
tavo.input.clear()
Clears input box content:
tavo.input.clear()
Send Message
tavo.input.send()
Triggers sending the current input box content:
tavo.input.set('Nice weather today')
tavo.input.send() // Send automatically
Example
Copy the following into any bubble to see it in action.
<h3>Input Box API Demo</h3>
<pre id="log" style="background: #0006; font-size: 12px; padding: 1em 1.5em; min-height: 60px; max-height: 200px; overflow-y: auto;"></pre>
<div style="display: grid; grid-template-columns: auto auto auto auto auto;">
<button onclick="inputGet()">tavo.input.get</button>
<button onclick="inputSet()">tavo.input.set</button>
<button onclick="inputAppend()">tavo.input.append</button>
<button onclick="inputClear()">tavo.input.clear</button>
<button onclick="inputSend()">tavo.input.send</button>
</div>
<script>
const log = (...args) => {
const text = args.map(v => typeof v === 'string' ? v : JSON.stringify(v, null, 2)).join(' ');
document.getElementById('log').textContent = text + '\n\n';
};
async function inputGet() {
const text = await tavo.input.get();
log('Current input content:', JSON.stringify(text));
}
function inputSet() {
const text = prompt('Enter content to write into the input box', 'Hello!');
if (text === null) return;
tavo.input.set(text);
tavo.utils.toast('Written to input box');
}
function inputAppend() {
const text = prompt('Enter content to append to the end of the input box', '(appended content)');
if (text === null) return;
tavo.input.append(text);
tavo.utils.toast('Appended to input box');
}
function inputClear() {
tavo.input.clear();
tavo.utils.toast('Input box cleared');
}
function inputSend() {
tavo.input.set('This message is automatically sent by tavo.input.send()');
tavo.input.send();
}
</script>
🛠️ Utilities
General utility interfaces. All utility interfaces are tavo.utils.<method>(...).
Toast
tavo.utils.toast(<text>)
Shows a lightweight toast notification that disappears automatically after a few seconds.
Open URL
tavo.utils.openUrl(<url>)
Opens a URL in an external browser:
tavo.utils.openUrl('https://example.com')
Export File
tavo.utils.export(<name>, <data>)
Exports data as a file and triggers system share/save. data can be Base64-encoded content (recommended) or plain text:
tavo.utils.export('Yeli Character Card', btoa('This is text or binary data converted to base64 using btoa')) // Pass base64 data (recommended)
tavo.utils.export('record.txt', 'This is plain text content') // Plain text
Example
Copy the following into any bubble to see it in action.
<h3>Utilities API Demo</h3>
<div style="display: grid; grid-template-columns: auto auto auto; gap: 8px;">
<button onclick="utilsToast()">tavo.utils.toast</button>
<button onclick="utilsOpenUrl()">tavo.utils.openUrl</button>
<button onclick="utilsExport()">tavo.utils.export</button>
</div>
<script>
function utilsToast() {
const text = prompt('Enter toast text to display', 'Hello, this is a Toast notification!');
if (!text) return;
tavo.utils.toast(text);
}
function utilsOpenUrl() {
const url = prompt('Enter a URL to open', 'https://example.com');
if (!url) return;
tavo.utils.openUrl(url);
}
function utilsExport() {
const filename = prompt('Enter export filename', 'demo.txt');
if (!filename) return;
const content = `This is a test file exported by tavo.utils.export.\nExport time: ${new Date().toLocaleString()}`;
tavo.utils.export(filename, content);
}
</script>
📱 App
You can use this interface to read some app properties. All interfaces are tavo.app.<method>(...).
Get Current App Version
await tavo.app.version(); // string: 0.77.0
await tavo.app.versionNumber(); // number: 770
⌛ Continuously Updated
The Tavo JavaScript API is still in an early beta stage and is actively evolving. If you have questions or great ideas, feel free to share feedback in the community.