Refactor history injection to modify prompts instead of chat messages
This prevents any risk of injected context being accidentally saved to the chat. Instead of modifying chat[].mes directly, we now: 1. Build a context map during GENERATION_STARTED 2. Inject into the prompt string (GENERATE_AFTER_COMBINE_PROMPTS) for text completion 3. Inject into the message array (CHAT_COMPLETION_PROMPT_READY) for chat completion The original chat messages are never modified.
This commit is contained in:
@@ -37,9 +37,8 @@ let currentSuppressionState = false;
|
|||||||
// Track last chat length we committed at to prevent duplicate commits from streaming
|
// Track last chat length we committed at to prevent duplicate commits from streaming
|
||||||
let lastCommittedChatLength = -1;
|
let lastCommittedChatLength = -1;
|
||||||
|
|
||||||
// Store original message content for restoration after generation
|
// Store context map for prompt injection (used by event handlers)
|
||||||
// Map of message index -> original mes content
|
let pendingContextMap = new Map();
|
||||||
let originalMessageContent = new Map();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a map of historical context data from ST chat messages with rpg_companion_swipes data.
|
* Builds a map of historical context data from ST chat messages with rpg_companion_swipes data.
|
||||||
@@ -162,81 +161,171 @@ function buildHistoricalContextMap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injects historical context into chat messages by modifying them in-place.
|
* Prepares historical context for injection into prompts.
|
||||||
* Stores original content for restoration after generation.
|
* This builds the context map and stores it for use by prompt event handlers.
|
||||||
* This approach works for ALL API types (text completion and chat completion).
|
* Does NOT modify the original chat messages.
|
||||||
*/
|
*/
|
||||||
function injectHistoricalContextIntoChat() {
|
function prepareHistoricalContextInjection() {
|
||||||
const historyPersistence = extensionSettings.historyPersistence;
|
const historyPersistence = extensionSettings.historyPersistence;
|
||||||
if (!historyPersistence || !historyPersistence.enabled) {
|
if (!historyPersistence || !historyPersistence.enabled) {
|
||||||
// console.log('[RPG Companion] History persistence not enabled, skipping injection');
|
pendingContextMap = new Map();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSuppressionState || !extensionSettings.enabled) {
|
if (currentSuppressionState || !extensionSettings.enabled) {
|
||||||
// console.log('[RPG Companion] Skipping history injection: suppressed or disabled');
|
pendingContextMap = new Map();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const chat = context.chat;
|
const chat = context.chat;
|
||||||
if (!chat || chat.length < 2) {
|
if (!chat || chat.length < 2) {
|
||||||
// console.log('[RPG Companion] Chat too short, skipping history injection');
|
pendingContextMap = new Map();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the context map
|
// Build and store the context map for use by prompt handlers
|
||||||
const contextMap = buildHistoricalContextMap();
|
pendingContextMap = buildHistoricalContextMap();
|
||||||
if (contextMap.size === 0) {
|
}
|
||||||
// console.log('[RPG Companion] No historical context to inject');
|
|
||||||
return;
|
/**
|
||||||
|
* Injects historical context into a text completion prompt string.
|
||||||
|
* Searches for message content in the prompt and appends context after matches.
|
||||||
|
*
|
||||||
|
* @param {string} prompt - The text completion prompt
|
||||||
|
* @returns {string} - The modified prompt with injected context
|
||||||
|
*/
|
||||||
|
function injectContextIntoTextPrompt(prompt) {
|
||||||
|
if (pendingContextMap.size === 0) {
|
||||||
|
return prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(`[RPG Companion] Injecting historical context into ${contextMap.size} messages`);
|
const context = getContext();
|
||||||
|
const chat = context.chat;
|
||||||
// Clear any previous stored content
|
let modifiedPrompt = prompt;
|
||||||
originalMessageContent.clear();
|
|
||||||
|
|
||||||
let injectedCount = 0;
|
let injectedCount = 0;
|
||||||
for (const [msgIdx, ctxContent] of contextMap) {
|
|
||||||
|
// Process each message that needs context injection
|
||||||
|
for (const [msgIdx, ctxContent] of pendingContextMap) {
|
||||||
const message = chat[msgIdx];
|
const message = chat[msgIdx];
|
||||||
if (!message || typeof message.mes !== 'string') {
|
if (!message || typeof message.mes !== 'string') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store original content for restoration
|
// Find the message content in the prompt
|
||||||
originalMessageContent.set(msgIdx, message.mes);
|
// Use a portion of the message to find it (last 100 chars should be unique enough)
|
||||||
|
const searchContent = message.mes.length > 100
|
||||||
|
? message.mes.slice(-100)
|
||||||
|
: message.mes;
|
||||||
|
|
||||||
// Modify the message in-place
|
const searchIndex = modifiedPrompt.lastIndexOf(searchContent);
|
||||||
message.mes = message.mes + ctxContent;
|
if (searchIndex === -1) {
|
||||||
injectedCount++;
|
// Message not found in prompt (might be truncated)
|
||||||
// console.log(`[RPG Companion] Injected context into message ${msgIdx}`);
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(`[RPG Companion] Successfully injected historical context into ${injectedCount} messages`);
|
// Find the end of this message content in the prompt
|
||||||
|
const insertPosition = searchIndex + searchContent.length;
|
||||||
|
|
||||||
|
// Insert the context after the message
|
||||||
|
modifiedPrompt = modifiedPrompt.slice(0, insertPosition) + ctxContent + modifiedPrompt.slice(insertPosition);
|
||||||
|
injectedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (injectedCount > 0) {
|
||||||
|
console.log(`[RPG Companion] Injected historical context into ${injectedCount} positions in text prompt`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifiedPrompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores original message content after generation completes.
|
* Injects historical context into a chat completion message array.
|
||||||
* This ensures the injected context doesn't persist in the actual chat data.
|
* Modifies the content of messages in the array directly.
|
||||||
|
*
|
||||||
|
* @param {Array} chatMessages - The chat completion message array
|
||||||
|
* @returns {Array} - The modified message array with injected context
|
||||||
*/
|
*/
|
||||||
function restoreOriginalMessageContent() {
|
function injectContextIntoChatPrompt(chatMessages) {
|
||||||
if (originalMessageContent.size === 0) {
|
if (pendingContextMap.size === 0 || !Array.isArray(chatMessages)) {
|
||||||
return;
|
return chatMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const chat = context.chat;
|
const chat = context.chat;
|
||||||
|
let injectedCount = 0;
|
||||||
|
|
||||||
// console.log(`[RPG Companion] Restoring ${originalMessageContent.size} messages to original content`);
|
// Process each message that needs context injection
|
||||||
|
for (const [msgIdx, ctxContent] of pendingContextMap) {
|
||||||
|
const originalMessage = chat[msgIdx];
|
||||||
|
if (!originalMessage || typeof originalMessage.mes !== 'string') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (const [msgIdx, originalContent] of originalMessageContent) {
|
// Find this message in the chat completion array by matching content
|
||||||
if (chat[msgIdx]) {
|
// Use a portion of the message to find it
|
||||||
chat[msgIdx].mes = originalContent;
|
const searchContent = originalMessage.mes.length > 100
|
||||||
|
? originalMessage.mes.slice(-100)
|
||||||
|
: originalMessage.mes;
|
||||||
|
|
||||||
|
for (const promptMsg of chatMessages) {
|
||||||
|
if (promptMsg.content && typeof promptMsg.content === 'string' &&
|
||||||
|
promptMsg.content.includes(searchContent)) {
|
||||||
|
// Found the message - append context
|
||||||
|
promptMsg.content = promptMsg.content + ctxContent;
|
||||||
|
injectedCount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
originalMessageContent.clear();
|
if (injectedCount > 0) {
|
||||||
|
console.log(`[RPG Companion] Injected historical context into ${injectedCount} messages in chat prompt`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chatMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for GENERATE_AFTER_COMBINE_PROMPTS (text completion).
|
||||||
|
* Injects historical context into the prompt string.
|
||||||
|
*
|
||||||
|
* @param {Object} eventData - Event data with prompt property
|
||||||
|
*/
|
||||||
|
function onGenerateAfterCombinePrompts(eventData) {
|
||||||
|
if (!eventData || typeof eventData.prompt !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventData.dryRun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventData.prompt = injectContextIntoTextPrompt(eventData.prompt);
|
||||||
|
|
||||||
|
// Clear the pending context after injection
|
||||||
|
pendingContextMap = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for CHAT_COMPLETION_PROMPT_READY.
|
||||||
|
* Injects historical context into the chat message array.
|
||||||
|
*
|
||||||
|
* @param {Object} eventData - Event data with chat property
|
||||||
|
*/
|
||||||
|
function onChatCompletionPromptReady(eventData) {
|
||||||
|
if (!eventData || !Array.isArray(eventData.chat)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventData.dryRun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventData.chat = injectContextIntoChatPrompt(eventData.chat);
|
||||||
|
|
||||||
|
// Clear the pending context after injection
|
||||||
|
pendingContextMap = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -565,22 +654,24 @@ Ensure these details naturally reflect and influence the narrative. Character be
|
|||||||
// Set suppression state for the historical context injection
|
// Set suppression state for the historical context injection
|
||||||
currentSuppressionState = shouldSuppress;
|
currentSuppressionState = shouldSuppress;
|
||||||
|
|
||||||
// Inject historical context directly into chat messages
|
// Prepare historical context for injection into prompts
|
||||||
// This temporarily modifies messages and will be restored after generation
|
// This builds the context map but does NOT modify original chat messages
|
||||||
injectHistoricalContextIntoChat();
|
prepareHistoricalContextInjection();
|
||||||
|
|
||||||
// Register a one-time listener to restore messages after prompt is built
|
// Register one-time listeners to inject context into the actual prompt
|
||||||
// Using .once() so it auto-removes after firing
|
// These modify only the prompt sent to the API, not the stored chat data
|
||||||
eventSource.once(event_types.GENERATE_AFTER_COMBINE_PROMPTS, () => {
|
if (pendingContextMap.size > 0) {
|
||||||
restoreOriginalMessageContent();
|
eventSource.once(event_types.GENERATE_AFTER_COMBINE_PROMPTS, onGenerateAfterCombinePrompts);
|
||||||
});
|
eventSource.once(event_types.CHAT_COMPLETION_PROMPT_READY, onChatCompletionPromptReady);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when generation ends to restore original message content.
|
* Called when generation ends to clean up any pending context.
|
||||||
* This should be called from the GENERATION_ENDED event handler.
|
* This should be called from the GENERATION_ENDED event handler.
|
||||||
*/
|
*/
|
||||||
export function onGenerationEndedCleanup() {
|
export function onGenerationEndedCleanup() {
|
||||||
restoreOriginalMessageContent();
|
// Clear any pending context that wasn't used (e.g., if generation was cancelled)
|
||||||
|
pendingContextMap = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user