外掛整合
把外掛接入 Luker 流水線、以及外掛之間互通的 API:正則處理、搜尋工具、跨外掛 API 註冊表、事件系統。
正則執行時 API
外掛可以透過 registerManagedRegexProvider() 註冊託管的正則處理器,參與 Luker 的正則處理流程。該函數從正則引擎模組匯出:
import { registerManagedRegexProvider } from '../../extensions/regex/engine.js';
const handle = registerManagedRegexProvider('my-plugin', {
reloadOnChange: true,
});
// 添加正則腳本
handle.upsertScript({
id: 'my-rule-1',
scriptName: 'My Regex Rule',
findRegex: 'foo',
replaceString: 'bar',
// ...其他正則腳本欄位
});
// 卸載時取消註冊
handle.unregister();registerManagedRegexProvider 回傳的句柄提供 upsertScript、removeScript、setScripts、clearScripts 和 unregister 方法。
搜尋工具 API
搜尋外掛透過 Luker.searchTools 全域物件暴露 API,供其他外掛呼叫搜尋能力:
// 檢查搜尋外掛是否可用
if (globalThis?.Luker?.searchTools) {
// 取得可用的搜尋工具名稱列表
const toolNames = Luker.searchTools.toolNames;
// 取得工具定義(用於函數呼叫)
const toolDefs = Luker.searchTools.getToolDefs();
// 檢查某個工具名是否屬於搜尋工具
const isSearchTool = Luker.searchTools.isToolName('web_search');
}Luker.searchTools 暴露的是工具定義中繼資料,實際的搜尋執行透過內部的工具呼叫迴圈完成。詳見搜尋外掛。
擴充功能間通訊
registerExtensionApi
context.registerExtensionApi('my-plugin', {
doSomething: () => { /* ... */ },
getData: () => myData,
});將一個API物件註冊到指定名稱下,供其他擴充功能透過getExtensionApi取得。如果同名API已被註冊,會在控制台輸出警告並覆蓋。
getExtensionApi
const api = context.getExtensionApi('other-plugin');
if (api) {
api.doSomething();
}按名稱取得其他擴充功能註冊的API物件。如果該名稱尚未註冊,返回undefined。
典型用途
擴充功能間通訊最常見的場景是解耦:一個擴充功能提供能力,另一個擴充功能消費能力,而不需要硬編碼依賴。例如,CardApp Studio透過registerExtensionApi將自身的編輯器API暴露出來,其他擴充功能可以在Studio就緒後直接呼叫。
事件系統
eventSource
// 監聽
context.eventSource.on(eventName, handler, options?);
// 取消監聽
context.eventSource.off(eventName, handler);
// 確保最先執行
context.eventSource.makeFirst(eventName, handler);
// 確保最后執行
context.eventSource.makeLast(eventName, handler);
// 查看監聽器資訊(除錯用)
context.eventSource.getListenersMeta(eventName);
// 設定外掛排序
context.eventSource.setOrderConfig(config);監聽器選項
context.eventSource.on(eventName, handler, {
priority: 10, // 數字越大越先執行
});事件類型
所有事件類型透過 context.eventTypes 存取。完整集合涵蓋聊天生命週期、訊息事件、生成鉤子和 app 級訊號。
| 分組 | 範例 |
|---|---|
| 聊天生命週期 | CHAT_CHANGED、CHAT_LOADED、CHAT_BRANCH_CREATED |
| 訊息事件 | MESSAGE_SENT、MESSAGE_RECEIVED、MESSAGE_RENDERED、MESSAGE_EDITED、MESSAGE_UPDATED、MESSAGE_DELETED、MESSAGE_SWIPED、MESSAGE_SWIPE_DELETED |
| 生成鉤子 | GENERATION_STARTED、GENERATION_CONTEXT_READY、GENERATION_BEFORE_WORLD_INFO_SCAN、GENERATION_BEFORE_API_REQUEST、GENERATION_ENDED、GENERATION_STOPPED、WORLD_INFO_ACTIVATED |
| App 級 | APP_READY、SETTINGS_LOADED_AFTER、EXTENSIONS_FIRST_LOAD |
完整事件 payload 結構見 前端外掛開發 → 事件系統。
國際化(i18n)
外掛應透過 i18n 輔助函式本地化使用者可見字串。語系資料會在 zh-CN ↔ zh-TW 之間做退回。
t(template tag)
t`Tag ${name} not found`使用樣板字面量形式 'Tag ${0} not found' 作為查找鍵,再把 name 代回結果中。執行時翻譯最方便的 API。
translate
translate(text: string, key?: string | null): string在已載入的語系資料中查找 text(或 key,當提供時)。沒有對應條目時原樣回傳 text。當你有沒有插值的靜態字串時使用。
getCurrentLocale
getCurrentLocale(): string回傳啟動時解析出的小寫語系識別碼——通常是 'en'、'zh-cn'、'zh-tw'、'ja-jp' 等。如果你需要按語系分支行為,讀這個。
addLocaleData
addLocaleData(localeId: string, data: Record<string, string>): void把外掛提供的翻譯合併到已載入的語系資料中。在 i18n 系統啟動之後呼叫(例如在 APP_READY 上)。當 localeId 為主要語系時,條目總是覆寫;為退回語系時,條目只填補缺失鍵。
const ctx = Luker.getContext();
ctx.eventSource.on(ctx.eventTypes.APP_READY, () => {
ctx.addLocaleData('zh-cn', {
'My Plugin': '我的插件',
'Settings saved': '设置已保存',
});
ctx.addLocaleData('en', {
'My Plugin': 'My Plugin',
'Settings saved': 'Settings saved',
});
});設定與儲存
extensionSettings
context.extensionSettings: object擴充功能儲存設定的全域純物件。每個擴充功能通常用自己的命名空間鍵:
const ctx = Luker.getContext();
if (!ctx.extensionSettings.my_extension) {
ctx.extensionSettings.my_extension = { enabled: true, level: 1 };
}
ctx.extensionSettings.my_extension.level += 1;
ctx.saveSettingsDebounced();saveSettingsDebounced
context.saveSettingsDebounced(): void防抖的持久化觸發。在突變 extensionSettings 或任何設定物件後呼叫。多次快速呼叫會合併為單次儲存。
saveMetadataDebounced
context.saveMetadataDebounced(): voidsaveMetadata 的防抖包裝,用於 chat_metadata 的突變。
getExtensionManifest
getExtensionManifest(name: string): ExtensionManifest | null回傳指定擴充功能 manifest 的結構化拷貝。接受短名稱(SillyTavern-MyExt)或內部鍵(third-party/SillyTavern-MyExt);查找對大小寫和重音不敏感。找不到時回傳 null。
openThirdPartyExtensionMenu
openThirdPartyExtensionMenu(suggestUrl?: string): Promise<void>開啟第三方擴充功能的安裝對話框。提供 suggestUrl 時預填 URL 欄位。
accountStorage
context.accountStorage: {
getItem(key: string): string | null,
setItem(key: string, value: any): void,
removeItem(key: string): void,
getState(): object,
}帳號作用域的 key/value 儲存。值會被強制轉成字串。透過 saveSettingsDebounced 持久化。用於應跨聊天保留但不應隨角色卡匯出的使用者特定設定。
const ctx = Luker.getContext();
ctx.accountStorage.setItem('my-extension:last-seen', String(Date.now()));
const lastSeen = ctx.accountStorage.getItem('my-extension:last-seen');除錯函式
registerDebugFunction
registerDebugFunction(
functionId: string,
name: string,
description: string,
func: () => void | Promise<void>,
): void在使用者設定的除錯選單中加一個按鈕。點擊時呼叫 func。適合外掛維護動作(清快取、傾倒狀態、強制重載等)。
const ctx = Luker.getContext();
ctx.registerDebugFunction(
'my-plugin-clear-cache',
'Clear my-plugin cache',
'Removes all cached data stored by my-plugin.',
() => {
ctx.extensionSettings.my_plugin.cache = {};
ctx.saveSettingsDebounced();
toastr.success('Cache cleared');
},
);Data Bank Scraper
registerDataBankScraper
registerDataBankScraper(scraper: {
id: string,
name: string,
description: string,
iconClass: string,
iconAvailable: boolean,
init?: () => Promise<void>,
isAvailable: () => Promise<boolean>,
scrape: () => Promise<File[]>,
}): void註冊一個自訂 Data Bank 來源。當使用者在 Data Bank UI 中選中此 scraper 時,呼叫 scrape() 並把回傳的 File[] 加入到 bank 中。
| 欄位 | 說明 |
|---|---|
id | 唯一識別碼;重複註冊會被拒絕 |
name / description | 在 scraper 選擇器中顯示 |
iconClass | FontAwesome 類別(例如 'fa-solid fa-globe') |
iconAvailable | 圖示是否可渲染 |
init | 可選的一次性設置;延遲呼叫 |
isAvailable | scraper 當前是否可執行(前置條件已滿足) |
scrape | 執行抓取;回傳新檔案 |
Tokenization
用於 token 計算任務(預算控制、context 視窗計算)。
getTokenCountAsync
getTokenCountAsync(text: string, padding?: number): Promise<number>使用當前活躍 tokenizer 回傳 text 的 token 數。以 ${tokenizerType}-${hash}${modelHash}+${padding} 為鍵快取。空輸入回傳 0。
getTextTokens
getTextTokens(tokenizerType: number, text: string): Promise<number[]>回傳 token id。tokenizerType 是 context.tokenizers.* 之一(例如 context.tokenizers.OPENAI、context.tokenizers.LLAMA3)。不支援編碼的 tokenizer 回傳 []。
getTokenizerModel
getTokenizerModel(): string回傳 tokenizer 配置使用的模型識別碼(例如 'gpt-4o'、'claude'、'llama3')。
tokenizers
context.tokenizers: { NONE, GPT2, OPENAI, LLAMA, LLAMA3, MISTRAL, GEMMA, CLAUDE, ... }支援的 tokenizer 類型的數字列舉。作為 getTextTokens 的第一個引數。
工具輔助函式
uuidv4
uuidv4(): string回傳 RFC 4122 UUID v4。可用時使用 crypto.randomUUID(),否則用 hex 字串退回。
timestampToMoment
timestampToMoment(timestamp: string | number): moment.Moment回傳一個透過 getCurrentLocale() 本地化的 moment 物件。輸入無法解析時回傳 moment.invalid()。
humanizedDateTime
humanizedDateTime(timestamp?: number): string回傳檔名友善的時間戳字串,格式為 YYYY-MM-DD@HHhMMmSSsMSms。預設取 Date.now()。
isMobile
isMobile(): boolean行動或平板平台(依 UA 解析)回傳 true。
shouldSendOnEnter
shouldSendOnEnter(): boolean依使用者偏好和平台,按下 Enter 應該發送(而非插入換行)時回傳 true。
Symbols 與常數
context.symbols.ignore
context.symbols.ignore: typeof IGNORE_SYMBOL哨兵 symbol,用來在 patch 上下文中表示「保留此值不動」——這時 null 和 undefined 已有其他含義。
context.constants.unset
context.constants.unset: typeof UNSET_VALUE傳給 writeExtensionField / writeExtensionFieldBulk 的哨兵值,用來刪除一個鍵而非把它設為 null:
await ctx.writeExtensionField(chid, 'my_field', ctx.constants.unset); // 刪除
await ctx.writeExtensionField(chid, 'my_field', null); // 設為 nullCONNECT_API_MAP
context.CONNECT_API_MAP: Record<string, ConnectApiEntry>支援的 API source 識別碼(例如 'openai'、'claude'、'novel')及其 UI 中繼資料的唯讀目錄。在填充連線相關下拉選單時用得到。
mainApi / maxContext / menuType
context.mainApi: 'openai' | 'kobold' | 'novel' | 'textgenerationwebui'
context.maxContext: number
context.menuType: 'characters' | 'character_edit' | 'create' | 'group_create' | 'group_edit' | ''當前主要 API、配置的最大 context 大小、當前開啟的右側面板選單類型的實時唯讀視圖。