Cache Keepalive: send ping as a user message (depth-0 injection) and update default message

6803ba16e2fd0e1a5c64a2d836107d430410fcda

permissionBRICK <permissionBRICK@gmail.com>

2 files changed, +25 -3Ignore whitespace
public/scripts/extensions/keepalive/index.js+24 -2
@@ -4,9 +4,12 @@ import {
4 deactivateSendButtons,4 deactivateSendButtons,
5 eventSource,5 eventSource,
6 event_types,6 event_types,
7 extension_prompt_roles,
8 extension_prompt_types,
7 generateQuietPrompt,9 generateQuietPrompt,
8 isGenerating,10 isGenerating,
9 saveSettingsDebounced,11 saveSettingsDebounced,
12 setExtensionPrompt,
10 setSendButtonState,13 setSendButtonState,
11 streamingProcessor,14 streamingProcessor,
12} from '../../../script.js';15} from '../../../script.js';
@@ -20,6 +23,12 @@ const MODULE = 'keepalive';
20// cache), but we never pay for a full completion.23// cache), but we never pay for a full completion.
21const KEEPALIVE_RESPONSE_LENGTH = 1;24const KEEPALIVE_RESPONSE_LENGTH = 1;
2225
26// Extension-prompt key used to inject the keepalive message as a USER message at depth 0.
27const KEEPALIVE_INJECT_ID = 'keepalive_ping';
28
29// Original default message; used to migrate untouched installs to the current default wording.
30const LEGACY_DEFAULT_MESSAGE = 'Keep the cache warm.';
31
23// Idle-tracking key used when no connection profile is active (manual API panel).32// Idle-tracking key used when no connection profile is active (manual API panel).
24const NONE_KEY = '__none__';33const NONE_KEY = '__none__';
2534
@@ -31,7 +40,7 @@ const EXPIRED_GRACE_MS = 3000;
31const defaultSettings = {40const defaultSettings = {
32 enabled: false,41 enabled: false,
33 timeoutSeconds: 295,42 timeoutSeconds: 295,
34 message: 'Keep the cache warm.',43 message: '[Note: just reply with an empty response to keep the session alive]',
35 // Per-profile enable map: { [profileId]: boolean }. Unlisted profiles default to enabled.44 // Per-profile enable map: { [profileId]: boolean }. Unlisted profiles default to enabled.
36 profiles: {},45 profiles: {},
37};46};
@@ -139,7 +148,12 @@ async function fireKeepalive() {
139 deactivateSendButtons();148 deactivateSendButtons();
140149
141 try {150 try {
142 await generateQuietPrompt({ quietPrompt: extension_settings[MODULE].message, responseLength: KEEPALIVE_RESPONSE_LENGTH });151 // Deliver the keepalive as a USER message at depth 0 (the quiet-prompt path forces a
152 // system role — openai.js hardcodes role:'system' for it), using the same depth+role
153 // injection Author's Note uses. The quiet prompt itself stays empty so the full chat
154 // context is still assembled (which is what keeps the cached prefix warm).
155 setExtensionPrompt(KEEPALIVE_INJECT_ID, extension_settings[MODULE].message, extension_prompt_types.IN_CHAT, 0, false, extension_prompt_roles.USER);
156 await generateQuietPrompt({ quietPrompt: '', responseLength: KEEPALIVE_RESPONSE_LENGTH });
143 // The ping kept this profile warm; treat it as fresh activity so the next157 // The ping kept this profile warm; treat it as fresh activity so the next
144 // keepalive for it is a full timeout away.158 // keepalive for it is a full timeout away.
145 lastActivity[getActiveProfileKey()] = Date.now();159 lastActivity[getActiveProfileKey()] = Date.now();
@@ -147,6 +161,7 @@ async function fireKeepalive() {
147 // Best-effort: any failure here is harmless and intentionally swallowed.161 // Best-effort: any failure here is harmless and intentionally swallowed.
148 console.debug('[Keepalive] Background generation ended:', error);162 console.debug('[Keepalive] Background generation ended:', error);
149 } finally {163 } finally {
164 setExtensionPrompt(KEEPALIVE_INJECT_ID, '', extension_prompt_types.IN_CHAT, 0, false, extension_prompt_roles.USER);
150 setSendButtonState(false);165 setSendButtonState(false);
151 activateSendButtons();166 activateSendButtons();
152 keepaliveActive = false;167 keepaliveActive = false;
@@ -209,6 +224,13 @@ function loadSettings() {
209 extension_settings[MODULE].profiles = {};224 extension_settings[MODULE].profiles = {};
210 }225 }
211226
227 // Migrate the original default message to the current default so installs that never
228 // customized it pick up the improved wording (custom messages are left untouched).
229 if (extension_settings[MODULE].message === LEGACY_DEFAULT_MESSAGE) {
230 extension_settings[MODULE].message = defaultSettings.message;
231 saveSettingsDebounced();
232 }
233
212 $('#keepalive_enabled').prop('checked', extension_settings[MODULE].enabled);234 $('#keepalive_enabled').prop('checked', extension_settings[MODULE].enabled);
213 $('#keepalive_timeout').val(extension_settings[MODULE].timeoutSeconds);235 $('#keepalive_timeout').val(extension_settings[MODULE].timeoutSeconds);
214 $('#keepalive_message').val(extension_settings[MODULE].message);236 $('#keepalive_message').val(extension_settings[MODULE].message);
public/scripts/extensions/keepalive/settings.html+1 -1
@@ -16,7 +16,7 @@
16 <label for="keepalive_message" data-i18n="ext_keepalive_message">Keepalive message</label>16 <label for="keepalive_message" data-i18n="ext_keepalive_message">Keepalive message</label>
17 <input id="keepalive_message" class="text_pole" type="text" />17 <input id="keepalive_message" class="text_pole" type="text" />
1818
19 <small data-i18n="ext_keepalive_help">After this many seconds without activity on the active connection profile, a hidden background prompt (the current chat plus this message) is sent to that profile to keep its server-side prompt cache warm. The completion is capped to a single token and is never saved to the chat. Sending is briefly locked while it runs. Only the profile you're currently using is pinged; each profile has its own idle timer.</small>19 <small data-i18n="ext_keepalive_help">After this many seconds without activity on the active connection profile, the current chat plus this message (sent as a hidden user message) is submitted to that profile to keep its server-side prompt cache warm. The completion is capped to a single token and is never saved to the chat. Sending is briefly locked while it runs. Only the profile you're currently using is pinged; each profile has its own idle timer.</small>
2020
21 <hr>21 <hr>
2222