Presets & Prompts
APIs for managing presets and assembling prompt messages with character cards and World Info.
Preset API
context.presets provides a unified preset management interface, replacing direct imports of the PresetManager internal module.
presets.list
presets.list(collection?: string): Array<PresetRef>Lists all saved presets in the specified collection. collection is the preset collection name (e.g., 'openai').
presets.getSelected
presets.getSelected(collection?: string): PresetRef | nullGets the currently selected preset reference. Returns null if the current selection is a runtime preset bound to a Character Card.
presets.getLive
presets.getLive(collection?: string): PresetBody | nullGets the preset content currently being edited in the UI (including unsaved changes). Use this to read the configuration currently in effect.
presets.getStored
presets.getStored(ref: { collection: string, name: string }): PresetBody | nullGets the saved content of a specific preset. Use this for cross-preset comparison or content copying.
presets.save
presets.save(
ref: { collection: string, name: string },
body: PresetBody
): Promise<void>Saves preset content.
presets.resolve
presets.resolve(
target?: PresetRef | string,
options?: { collection?: string, defaultCollection?: string, allowMissingName?: boolean }
): { collection: string, name: string } | nullNormalizes a preset reference into a canonical { collection, name } object. This is purely a name-resolution helper — it does not return connection information. Pass an existing PresetRef, a collection name as a string (resolves to that collection's currently selected preset), or null (resolves to the current preset of options.collection). Returns null when the collection or name can't be determined.
Looking for connection profile resolution?
This function does not return API endpoint, model, or secret information. To resolve a Connection Manager profile into the connection settings that sendOpenAIRequest consumes, use context.connectionProfiles.resolve instead.
presets.state
Plugin runtime/session data bound to a preset. Preset state lives next to the preset on disk and is NOT exported with the preset itself — they are strictly for plugin-side runtime state (e.g., the orchestrator's per-preset agent override, the preset assistant's last-used template). Do not place plugin data in the preset body; use presets.state.* instead.
All methods accept options.target (a PresetRef) and options.collection for cross-preset reads/writes; both default to the currently selected preset.
Behavior change (2026-06-28)
Preset state read/write APIs (get, getBatch, update, patch, delete, deleteAll) no longer throw on HTTP failures. They now return a {ok, ...} result object (matching the chat-state pattern). If your plugin previously wrote try { await ctx.presets.state.get(...) } catch (e) { ... }, switch to if (!result.ok) { ... }.
presets.state.get
presets.state.get(
namespace: string,
options?: { target?: PresetRef, collection?: string }
): Promise<
| { ok: true, state: object | null }
| { ok: false, state: null, reason: string, hint: string }
>Reads the preset state for a given namespace. On success returns {ok: true, state} where state is the stored value or null if no data exists for that namespace. On failure returns {ok: false, state: null, reason, hint} — see Error reasons below.
presets.state.getBatch
presets.state.getBatch(
namespaces: string[],
options?: { target?: PresetRef, collection?: string }
): Promise<
| { ok: true, results: Map<string, { ok: true, state: object | null }> }
| { ok: false, results: Map<string, never>, reason: string, hint: string }
>Reads preset state for multiple namespaces in batch. On success returns {ok: true, results} where results is a Map keyed by namespace; each entry is {ok: true, state} and missing namespaces map to {ok: true, state: null}. On failure (no active preset, transport error, HTTP error) returns {ok: false, results: <empty Map>, reason, hint}.
presets.state.update
presets.state.update(
namespace: string,
updater: (current: any) => any,
options?: { target?: PresetRef, collection?: string, maxOperations?: number, maxRetries?: number }
): Promise<
| { ok: true, state: object | null, updated: boolean }
| { ok: false, reason: string, hint: string }
>Recommended read-modify-write approach. The updater function receives the current state ({} when none exists) and returns the new state. The system computes the minimal incremental patch internally, so only the changed slice is transmitted. Returning null / undefined is treated as "no change" and resolves with {ok: true, updated: false}. 409 conflicts (concurrent edit) are retried automatically; the retry budget is controlled by options.maxRetries (default 1). On failure returns {ok: false, reason, hint} — see Error reasons below. This function never throws; exceptions inside the reducer are caught and surfaced as reason: 'VALIDATION_ARGS'.
await context.presets.state.update('my-plugin', (current = {}) => ({
...current,
lastUsedTemplate: 'compact',
updatedAt: Date.now(),
}));presets.state.patch
presets.state.patch(
namespace: string,
operations: object[],
options?: { target?: PresetRef, collection?: string }
): Promise<
| { ok: true }
| { ok: false, reason: string, hint: string }
>Applies RFC 6902 patch operations directly. Prefer update() for typical read-modify-write flows; use patch() only when the operation list is already available (e.g., re-applying a previously computed diff). On success returns {ok: true}; on failure returns {ok: false, reason, hint} — see Error reasons below.
presets.state.delete
presets.state.delete(
namespace: string,
options?: { target?: PresetRef, collection?: string }
): Promise<
| { ok: true }
| { ok: false, reason: string, hint: string }
>Deletes the preset state for a given namespace. Idempotent — succeeds when no state exists. On success returns {ok: true}; on failure returns {ok: false, reason, hint} — see Error reasons below.
presets.state.deleteAll
presets.state.deleteAll(target?: PresetRef | string | null): Promise<
| { ok: true }
| { ok: false, reason: string, hint: string }
>Wipes every namespace under the given preset. Use sparingly — typically only when the preset itself is being deleted or reset. On success returns {ok: true}; on failure returns {ok: false, reason, hint} — see Error reasons below.
Best Practices
- Use
presets.state.update()for read-modify-write instead of manually chainingget()+ a full overwrite — the helper transmits only the diff and handles the 409 retry. - Keep payloads as JSON-serializable plain objects; arrays and primitives at the top level are not supported.
- One namespace per logical state slice; don't pack unrelated data under a single namespace.
- Handle
ok: falsereturn values to keep your plugin UI resilient — switch onreasonrather than translatinghint(see Error reasons).
Error reasons
Preset state read/write methods (get, getBatch, update, patch, delete, deleteAll) return {ok: false, reason, hint} on failure. The reason field uses the same vocabulary as Chat State:
| Reason | When it fires | Suggested handling |
|---|---|---|
VALIDATION_ARGS | Bad arguments (empty namespace, non-function updater, non-array operations, reducer threw) | Fix caller — this is a programmer bug |
VALIDATION_TARGET | No active preset (apiId/name resolution failed) | Skip the write, retry once a preset is selected |
VALIDATION_COMMIT | floor-state only (not applicable to preset state) | — |
INSTANCE_DESTROYED | floor-state only (not applicable to preset state) | — |
CONFLICT | HTTP 409 even after retry | Re-read current state and try again |
HTTP_ERROR | Other non-2xx response | Inspect hint for the status code, user-visible toast acceptable |
TRANSPORT_ERROR | fetch threw (network, CORS, abort) | Retry or surface network error to user |
REPLAY_BROKEN | floor-state only (not applicable to preset state) | — |
LOG_WRITE_FAILED | floor-state only (not applicable to preset state) | — |
The hint field is English, no longer than 120 characters, and contains actionable diagnostic info. It is not localized — surface a localized message to users by switching on reason, not by translating hint.
Usage Rules
list()andgetSelected()only return saved presets- Use
getLive()for the preset currently being edited - Runtime presets bound to a Character Card are not considered "saved" —
getSelected()returnsnull, butgetLive()can still read them - Do not place plugin runtime data in the preset body; use
presets.state.*instead
Prompt and World Info Assembly
For a normal plugin LLM request, use generateTask. It performs profile resolution, World Info activation, prompt assembly, dispatch, and response normalization in one call.
The APIs below are the lower-level primitives. Use them when you need the assembled prompt without sending it, or dispatch through a transport that generateTask does not support.
buildPresetAwarePromptMessages
buildPresetAwarePromptMessages(options: {
messages: Array<{ role: string, content: string }>,
envelopeOptions?: {
includeCharacterCard?: boolean,
api?: string,
promptPresetName?: string,
},
promptPresetName?: string,
runtimeWorldInfo?: object,
}): PromptMessage[]Assembles plugin messages into a prompt message list ready to be sent to an API, arranged according to the prompt preset's ordering. This is an optional assembly tool; simple LLM calls do not require it. You only need it when you want to reuse character cards, world info, or prompt templates.
Parameters:
| Parameter | Description |
|---|---|
messages | Array of messages to send, each containing role ('system'/'user'/'assistant') and content |
envelopeOptions.includeCharacterCard | Whether to include the current Character Card's definitions in the prompt (default true) |
envelopeOptions.api | Specifies the API type to use (e.g., 'openai'); uses the current connection if not specified |
envelopeOptions.promptPresetName | Specifies the preset name to use; uses the current preset if not specified |
promptPresetName | Same as envelopeOptions.promptPresetName; top-level shortcut |
runtimeWorldInfo | Pre-resolved World Info activation results (obtained via resolveWorldInfoForMessages) |
Key Behaviors:
- Preserves content from the active preset outside of chat history (system prompt, character description, etc.)
- Only replaces the chat history portion with the
messagesyou provide - If
runtimeWorldInfois provided, World Info entries are injected at the corresponding positions - If
promptPresetNameis specified, that preset's prompt template is used instead of the current preset
Practical Example (based on the Memory Graph plugin's recall flow):
// 1. First resolve World Info activation results
const runtimeWorldInfo = await context.resolveWorldInfoForMessages(
resolverMessages,
{
type: 'quiet',
fallbackToCurrentChat: false,
postActivationHook: rewriteDepthWorldInfoToAfter, // Rewrite directive: move depth-type World Info entries to the after position
}
);
// 2. Assemble the prompt
const promptMessages = context.buildPresetAwarePromptMessages({
messages: [
{ role: 'system', content: 'You are a memory analysis assistant...' },
{ role: 'user', content: 'Please analyze the key information in the following conversation...' },
],
envelopeOptions: {
includeCharacterCard: true,
api: envelopeApi,
promptPresetName: selectedPromptPresetName,
},
promptPresetName: selectedPromptPresetName,
runtimeWorldInfo: runtimeWorldInfo,
});
// 3. Send to the LLM
import { sendOpenAIRequest } from '../../../openai.js';
const response = await sendOpenAIRequest('quiet', promptMessages, signal, {
requestScope: 'extension_internal',
});About the Post-Activation Hook (postActivationHook)
The postActivationHook parameter of resolveWorldInfoForMessages allows you to make arbitrary modifications to entries after World Info activation but before injection — including modifying content, adjusting injection positions and depth, or even adding/removing entries. The hook receives the fully normalized World Info payload and returns the modified version. For example, the Memory Graph plugin uses this hook to rewrite depth-type World Info entries to the after position, preventing them from being inserted into the chat depth and interfering with the plugin's own instructions.
generateTask accepts the same hook and applies it during its internal World Info resolution. The primitive below remains available when you assemble the prompt manually.
resolveWorldInfoForMessages
resolveWorldInfoForMessages(
messages: Array<
| { role: string, content: string }
| { mes: string, is_user?: boolean, is_system?: boolean, name?: string }
>,
options?: {
type?: string,
fallbackToCurrentChat?: boolean,
postActivationHook?: (entries: object) => object,
}
): Promise<object>Performs a World Info activation scan against the specified messages and returns the activation results. This is essentially a World Info "rescan" against custom messages.
Parameters:
| Parameter | Description |
|---|---|
messages | Message list used to trigger World Info keyword matching. Accepts either { role, content } or a SillyTavern chat record { mes, is_user, is_system, name } |
options.type | Activation type (e.g., 'quiet' for silent scan that does not affect the main conversation) |
options.fallbackToCurrentChat | Whether to fall back to current chat messages if messages is empty |
options.postActivationHook | Post-activation hook function that receives the full World Info payload; can modify entry content, positions, depth, or add/remove entries |
The returned object contains fields such as worldInfoBeforeEntries, worldInfoAfterEntries, and worldInfoDepth, which can be passed directly to the runtimeWorldInfo parameter of buildPresetAwarePromptMessages.
World Info Rescan
resolveWorldInfoForMessages is essentially a World Info rescan against custom messages. Plugins can use it to:
- Obtain relevant World Info entries for independent LLM calls
- Test which World Info entries a specific message would trigger
- Simulate World Info activation without affecting the main conversation
Independent LLM Calls
For a plugin popup or background task, call generateTask with the task messages and choose how World Info should be activated:
const context = Luker.getContext();
const result = await context.generateTask({
taskMessages: myCustomMessages,
worldInfoSource: 'chat',
includeCharacterCard: true,
});worldInfoSource: 'chat' activates World Info from the current chat history. Use 'task' when the task messages should drive activation, and 'custom' with customWorldInfoMessages when you supply the scan source explicitly. See Generation for the full option reference.
Manual assembly (advanced)
Use the primitive pair when you need the assembled prompt without sending it, need to inspect or modify that prompt, or need a transport that generateTask does not cover:
import { sendOpenAIRequest } from '../../../openai.js';
const context = Luker.getContext();
// 1. Resolve World Info activation results
const wi = await context.resolveWorldInfoForMessages(myCustomMessages, {
type: 'quiet',
fallbackToCurrentChat: false,
});
// 2. Assemble the prompt (inject character card, world info, arrange by prompt_order)
const requestMessages = context.buildPresetAwarePromptMessages({
messages: myCustomMessages,
runtimeWorldInfo: wi,
});
// 3. Send the request
const result = await sendOpenAIRequest('quiet', requestMessages, signal, {
requestScope: 'extension_internal',
});If you don't need character cards or world info, you can pass messages directly to sendOpenAIRequest. See Generation for sendOpenAIRequest details.
Prompt Structure Inspection
For diagnostic UIs and "preview what would be sent" tooling, plugins can read the assembled prompt structure and layout without dispatching a request.
getActivePromptPresetEnvelope
getActivePromptPresetEnvelope(options?: {
includeCharacterCard?: boolean,
api?: string,
promptPresetName?: string,
completionPresetName?: string,
contextPresetName?: string,
instructPresetName?: string,
syspromptPresetName?: string,
reasoningPresetName?: string,
}): PromptPresetEnvelopeReturns a snapshot of the resolved prompt configuration including:
mainApi/completionApi— active API identifierspresetRefs— names of resolved completion / context / instruct / sysprompt / reasoning presetspromptCore— extracted prompt-affecting fields per presetpromptLayout— the merged Luker layout (fromextensions.luker.prompt_layout)promptCatalog— map ofprompt.identifier→{ name, role, content, marker, systemPrompt }characterCard— current character fields (whenincludeCharacterCard !== false)
This is the same data that buildPresetAwarePromptMessages consumes internally. Use this to show users what a plugin's request will look like before it is sent.
getActivePromptLayout
getActivePromptLayout(options?: object): PromptLayoutEntry[]Convenience accessor returning only the merged prompt layout. Each entry has id, enabled, order, role, phase, source, content, path, promptIdentifier, tags.
formatPromptPresetEnvelope
formatPromptPresetEnvelope(envelope?: object, options?: { label?: string }): stringFormats a prompt structure as [[LABEL]]\n<json> for embedding into another prompt (e.g., when delegating to a meta-LLM that needs to reason about the user's prompt config). Defaults to the current prompt structure when none is supplied.
const ctx = Luker.getContext();
const envelope = ctx.getActivePromptPresetEnvelope({ includeCharacterCard: true });
const serialized = ctx.formatPromptPresetEnvelope(envelope);
console.log(serialized);Reasoning
Reasoning helpers parse and render the <thinking>...</thinking> style blocks emitted by reasoning models (Claude reasoning, DeepSeek-R1, o1, etc.).
parseReasoningFromString
parseReasoningFromString(
text: string,
options?: { strict?: boolean },
template?: ReasoningTemplate | null,
): { reasoning: string, content: string } | nullSplits a model output string into reasoning and content based on the prefix and suffix of a reasoning template (or the active power_user.reasoning template). Returns null if the template lacks prefix/suffix or the parse fails.
| Option | Description |
|---|---|
strict | When true (default), the prefix must appear at the start (after whitespace). When false, finds it anywhere |
getReasoningTemplateByName
getReasoningTemplateByName(name: string): ReasoningTemplateLooks up a reasoning template by name. Throws Error('Unknown reasoning template name: "<name>"') when not found. The returned template should be treated as read-only.
updateReasoningUI
updateReasoningUI(
messageIdOrElement: number | HTMLElement | JQuery,
options?: { reset?: boolean },
): voidTriggers a UI refresh of the reasoning block on a message. Pass a chat index, a raw DOM element, or a JQuery wrapper. reset: true skips reading the message's current reasoning state — used during swipes before the new reasoning has been written.
removeReasoningFromString
removeReasoningFromString(str: string): stringStrips the reasoning prefix/suffix block from a string using the active reasoning template. Returns the input unchanged when no template is configured or no reasoning span is found. Use when only the user-facing answer text is needed from a model output that may include <thinking>...</thinking>-style sections.
const ctx = Luker.getContext();
const parsed = ctx.parseReasoningFromString(modelOutput);
if (parsed) {
console.log('Reasoning:', parsed.reasoning);
console.log('Final answer:', parsed.content);
}Settings Views (Read-Only)
The following properties expose live settings objects. They are mutable references — write through them only through their canonical APIs (presets.save, saveSettingsDebounced, etc.). Direct mutation may not persist correctly.
chatCompletionSettings
context.chatCompletionSettings: objectReference to oai_settings. Read-only views useful for inspecting the active chat completion source, model, and parameters.
textCompletionSettings
context.textCompletionSettings: objectReference to textgenerationwebui_settings. Read-only views for the active text completion backend.
powerUserSettings
context.powerUserSettings: objectReference to power_user. Holds tokenizer choice, reasoning template selection, message-display preferences, and other user-level configuration.
Mutate via APIs, not directly
These views are exposed for inspection only. Writing to them directly may bypass debounced save logic. To change connection or model, use the user-facing UI or presets.save. To change generation parameters, use a chat completion preset.
Connection Helpers
context.openai
context.openai: {
proxies: Array<{ name: string, url: string, ... }>,
ZAI_ENDPOINT: Record<string, string>,
stripPresetConnectionFields(preset: object): object,
}Helpers for working with chat-completion connection state. proxies is the live list of user-configured reverse proxies. ZAI_ENDPOINT enumerates the well-known Zhipu / Z.AI endpoint URLs. stripPresetConnectionFields returns a clone of a preset with connection-specific fields (api source, model, proxy, etc.) removed — used when exporting a preset that should be portable across user setups.
const ctx = Luker.getContext();
const portable = ctx.openai.stripPresetConnectionFields(preset);
const json = JSON.stringify(portable, null, 2);context.textCompletion
context.textCompletion: {
types: Record<string, string>,
}textCompletion.types enumerates supported text-completion backend identifiers (OOBA, MANCER, APHRODITE, KOBOLDCPP, …). Use when branching behavior on which text-completion provider is active.