Repository navigation
Lua API Reference
Every function listed here is available as a global inside a Risu Lua script (see Lua Scripting for lifecycle, permissions, and examples). The Call it as column is the name you actually use in Lua. Some entries are wrapped automatically so they decode JSON or wait for a promise to resolve before returning to your script.
Access levels, from the "permission tiers" section on Lua Scripting:
-
None — works in every mode, including
editDisplay. -
Safe — every mode except
editDisplay. -
EditDisplay —
editDisplaymode only. Also included wherever Safe is listed, since those bindings accept either access level. - Low-level — requires low-level access to be turned on for the owning character/module, and is unavailable in edit-listener modes.
| Call it as | Args | Returns | Access |
|---|---|---|---|
getChatVar(id, key) |
key: string | string value (or "null") |
None |
setChatVar(id, key, value) |
key, value: string | — | Safe + EditDisplay |
setChatVarChanged(id, key, value) |
key, value: string |
true if the value actually changed, else nothing |
Safe + EditDisplay |
getGlobalVar(id, key) |
key: string | string value from the chat-independent global variable store | None |
getState(id, name) (Lua shim)
|
name: string | decoded Lua value from a "__"+name chat variable |
None (delegates to getChatVar) |
setState(id, name, value) (Lua shim)
|
name: string, value: any | — | Safe + EditDisplay (delegates to setChatVar) |
setStateChanged(id, name, value) (Lua shim)
|
name: string, value: any |
true if changed |
Safe + EditDisplay |
| Call it as | Args | Returns | Access |
|---|---|---|---|
getChat(id, index) (shim)
|
index: number (supports negative/.at() indexing) |
table {role, data, time, chatId?} or nil. chatId is read-only and present only when the message has one, see Message ids and setFullChat
|
None |
getChatData(id, index) |
index: number | message text, or ''
|
None |
getChatRole(id, index) |
index: number |
'user'/'char', or ''
|
None |
getRecentChats(id, count) (shim)
|
count: number | table array of the last count messages, {role, data, time, chatId?} each; chatId is read-only and present only when the message has one |
None |
getFullChat(id) (shim)
|
— | table array of every message, {role, data, time, chatId?} each; chatId is read-only and present only when the message has one |
None |
setFullChat(id, value) (shim)
|
value: table array of {role, data}, each optionally carrying the chatId that getFullChat returned |
replaces the whole chat message list. Messages are matched to the old ones so unchanged or edited messages keep their stored fields, see Message ids and setFullChat. Fork difference: an empty list also clears the chat's SupaMemory data
|
Safe |
getChatLength(id) |
— | number of messages | None |
setChat(id, index, value) |
index: number, value: string | overwrites message text at index | Safe |
setChatRole(id, index, value) |
index, value ('user' else treated as 'char') |
— | Safe |
cutChat(id, start, end) |
start, end: number | slices the message array to [start, end). Fork difference: keeps the SupaMemory resume point valid, see SupaMemory#when-messages-are-removed
|
Safe |
removeChat(id, index) |
index: number | removes one message. Fork difference: keeps the SupaMemory resume point valid, see SupaMemory#when-messages-are-removed | Safe |
addChat(id, role, value) |
role ('user' else 'char'), value: string |
appends a message | Safe |
insertChat(id, index, role, value) |
index, role, value | inserts a message at index | Safe |
getCharacterLastMessage(id) |
— | text of the most recent char-role message in the call's chat, falling back to that chat's owner's first message. Inside a trigger run or editInput, "the call's chat" is the run's own chat, re-read fresh on every call (not held from when the call began); outside a run it's the selected character's chat |
None |
getUserLastMessage(id) |
— | text of the most recent user-role message, or ''
|
None |
getAuthorsNote(id) |
— | the chat's author's note, or ''
|
None |
Fork difference (RisuTanium addition): chatId and the matching rule below do not exist in upstream RisuAI. The commit that added them notes that upstream pull request #1611 proposes the same idea and is unmerged.
getChat, getRecentChats and getFullChat return a chatId string on a message when the message has one. Some messages have none, for example older ones, so check for nil. A script cannot set, change or create a chatId. It is readable in every mode, like the other fields these getters return.
setFullChat replaces the message list with the table you pass. For each entry it decides which existing message the entry stands for, in two passes:
-
By id. An entry with a
chatIdtakes the first existing message that holds that id, unless an earlier entry already took that message. - By position. Every other entry (no id, an unknown id, a duplicate id, or an id already taken) takes the existing message at the same position, but only if that message has the same role and the same text and no other entry has taken it.
A message matched by either pass keeps every field it had (generation info, prompt info, speaker, name, the disabled and comment flags, its chatId and its time). Only its role and text are replaced with yours, and the alternatives (rerolls) that follow it stay attached. An entry that matches nothing becomes a new message with only a role and text, and no chatId. In your table, chatId is used only as a lookup key: an id you invent, or a time you add, is not stored.
-- append text to the last message and keep its stored fields
local chat = getFullChat(id)
chat[#chat].data = chat[#chat].data .. ' (edited)'
setFullChat(id, chat)Edge cases:
- A script that never sends ids (for example one written for upstream) is matched by position, role and text only. An unchanged message keeps its fields. An edited or moved message with no id counts as new and keeps none. In the example above, a last message that has no
chatIdwould lose its fields because its text changed. - If the table holds the same
chatIdtwice, only the first entry claims the message. The second is matched by position, role and text like an entry with no id. - A stale table whose ids are no longer known falls back to position, role and text. If those happen to match an unrelated message, that message's fields are kept.
- Each existing message is matched at most once.
| Call it as | Args | Returns | Access |
|---|---|---|---|
getName(id) |
— | the call's owner's name. Inside a trigger run or editInput, that's the run's own owner (the group itself, in a group call), re-resolved on every call; outside a run it's the selected character's |
None |
setName(id, name) |
name: string | throws if not a string | Safe |
getDescription(id) |
— | the call's owner's description, same origin rule as getName; throws if the owner is a group |
Safe |
setDescription(id, desc) |
desc: string | throws if it's a group; throws 'Invalid data type' when desc is not a string |
Safe |
getCharacterFirstMessage(id) |
— | the call's owner's first message, same origin rule as getName
|
None |
setCharacterFirstMessage(id, data) |
data: string |
true/false
|
Safe |
getPersonaName(id) |
— | active persona's display name | None |
getPersonaDescription(id) |
— | persona prompt, CBS-parsed against the call's owner: the run's own owner with an origin, the selected character without one | None |
getBackgroundEmbedding(id) |
— | the call's owner's background HTML content, same origin rule as getName
|
Safe |
setBackgroundEmbedding(id, data) |
data: string |
true/false
|
Safe |
getCharacterImage(id) (shim)
|
— |
{{inlayed::id}} CBS tag for the call's owner's portrait ('' for a group or a missing image), same origin rule as getName
|
None |
getPersonaImage(id) (shim)
|
— |
{{inlayed::id}} CBS tag for the active persona's icon, or ''
|
None |
| Call it as | Args | Returns | Access |
|---|---|---|---|
getLoreBooks(id, search) (shim)
|
search: string, matched against a lore entry's comment/title | table array of matching entries (local chat lore + global lore + module lore), content CBS-parsed | None |
upsertLocalLoreBook(id, name, content, options) |
name, content: string; options: {alwaysActive?, insertOrder?, key?, secondKey?, regex?}
|
inserts/replaces a local (per-chat) lore entry named name on the call's own chat (the group's own chat, in a group run); no-op when there is no chat to write to, or when the runner is a group and the call is not part of a member's trigger run (for example a group's editRequest/editDisplay/editOutput module script) |
Safe |
loadLoreBooks(id, reserve) (shim, awaits, low-level)
|
reserve: number of tokens to hold back from the model's max context length | table array of {role, data} for every currently-active lore entry that fits the remaining budget |
Low-level |
| Call it as | Args | Returns | Access |
|---|---|---|---|
LLM(id, prompt, useMultimodal, options) (shim)
|
prompt: array of {role, content} (system/sys, user, assistant/bot/char all normalize); useMultimodal: bool, expands {{inlay|inlayed|inlayeddata::x}} tags into image attachments; options: {streaming?}
|
{success, result} using the main model |
Low-level |
axLLM(id, prompt, useMultimodal, options) (shim)
|
same as LLM
|
{success, result} using the auxiliary ("otherAx") model slot |
Low-level |
simpleLLM(id, prompt) |
prompt: string |
{success, result} object (no JSON round-trip needed) using the main model, no multimodal support |
Low-level |
| Call it as | Args | Returns | Access |
|---|---|---|---|
generateImage(id, value, negValue) |
value: prompt, negValue: negative prompt (default '') |
{{inlay::id}} CBS tag, or an 'Error: ...' string |
Low-level |
similarity(id, source, value) |
source: string, value: string[] | array of value ranked by embedding similarity to source
|
Low-level |
request(id, url) |
url: string (see limits in Lua Scripting) | JSON string {status, data}
|
Low-level |
hash(id, value) |
value: string | hex SHA-256 digest | None |
getTokens(id, value) |
value: string | token count | Safe |
sleep(id, time) |
time: milliseconds | resolves true after the delay |
Safe |
cbs(value) (no id argument at all)
|
value: string |
value run through the CBS parser against the call's owner: the run's own owner with an origin, the selected character without one |
None |
log(value) (Lua shim)
|
any JSON-serializable Lua value | prints it to the browser console | None |
reloadDisplay(id) |
— | bumps the GUI reload counter | Safe |
reloadChat(id, index) |
index: number | bumps the reload counter for one chat message | Safe |
stopChat(id) |
— | marks the current generation to be stopped | Safe |
| Call it as | Args | Returns | Access |
|---|---|---|---|
alertError(id, value) |
string | — | Safe |
alertNormal(id, value) |
string | — | Safe |
alertInput(id, value) |
string prompt | the text the user typed | Safe |
alertSelect(id, value) |
value: string[] of options |
the chosen option | Safe |
alertConfirm(id, value) |
string prompt |
true/false
|
Safe |
| Call it as | Args | Notes |
|---|---|---|
listenEdit(type, func) |
type in 'editRequest' | 'editDisplay' | 'editInput' | 'editOutput', func(id, value, meta)
|
registers func in an internal list; every registered function for the matching type runs in order, threading the value through each; throws for any other type
|
A Python execution backend also exists internally, mirroring this Lua API, but there's currently no way to select it as a script language from the character or module editors.
- Lua Scripting — lifecycle, permission model, sandbox limits, and examples.
-
Trigger Script — where
triggerluafits among the trigger effect types.
Getting started
- RisuAI Basics
- Creating a Basic Bot
- Coming from upstream RisuAI? Migrating from upstream
- What changed from upstream? Changes from upstream
Syntax
Scripting
Characters and prompts
Tools
Memory
Plugins
Settings