[Electron Hub] Prompt Caching Support for Claude models (#4918) * Prompt Caching support Claude models * Prompt Caching support Claude models * Diff clean-up --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

829db7f2d059daa5e9b95696e3029e87d4ba3ddf

Ngo Dinh Gia Bao <112873281+snowby666@users.noreply.github.com>

Signed
1 files changed, +28 -18Showing whitespace changes
src/endpoints/backends/chat-completions.js+28 -18
@@ -87,6 +87,16 @@ const API_SILICONFLOW = 'https://api.siliconflow.com/v1';
87const API_OPENROUTER = 'https://openrouter.ai/api/v1';87const API_OPENROUTER = 'https://openrouter.ai/api/v1';
8888
89/**89/**
90 * Module-scoped Claude caching configuration values.
91 */
92const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m';
93const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false, 'boolean');
94const cachingAtDepth = (() => {
95 const value = getConfigValue('claude.cachingAtDepth', -1, 'number');
96 return Number.isInteger(value) && value >= 0 ? value : -1;
97})();
98
99/**
90 * Cache for cacheable (writing) OpenRouter model IDs.100 * Cache for cacheable (writing) OpenRouter model IDs.
91 * @type {string[]}101 * @type {string[]}
92 */102 */
@@ -194,12 +204,6 @@ async function sendClaudeRequest(request, response) {
194 const apiUrl = new URL(request.body.reverse_proxy || API_CLAUDE).toString();204 const apiUrl = new URL(request.body.reverse_proxy || API_CLAUDE).toString();
195 const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE);205 const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE);
196 const divider = '-'.repeat(process.stdout.columns);206 const divider = '-'.repeat(process.stdout.columns);
197 const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false, 'boolean');
198 let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number');
199 // Disabled if not an integer or negative
200 if (!Number.isInteger(cachingAtDepth) || cachingAtDepth < 0) {
201 cachingAtDepth = -1;
202 }
203207
204 if (!apiKey) {208 if (!apiKey) {
205 console.warn(color.red(`Claude API key is missing.\n${divider}`));209 console.warn(color.red(`Claude API key is missing.\n${divider}`));
@@ -221,7 +225,6 @@ async function sendClaudeRequest(request, response) {
221 const useWebSearch = /^claude-(3-5|3-7|opus-4|sonnet-4|haiku-4-5|opus-4-5)/.test(request.body.model) && Boolean(request.body.enable_web_search);225 const useWebSearch = /^claude-(3-5|3-7|opus-4|sonnet-4|haiku-4-5|opus-4-5)/.test(request.body.model) && Boolean(request.body.enable_web_search);
222 const isLimitedSampling = /^claude-(opus-4-1|sonnet-4-5|haiku-4-5|opus-4-5)/.test(request.body.model);226 const isLimitedSampling = /^claude-(opus-4-1|sonnet-4-5|haiku-4-5|opus-4-5)/.test(request.body.model);
223 const useVerbosity = /^claude-(opus-4-5)/.test(request.body.model);227 const useVerbosity = /^claude-(opus-4-5)/.test(request.body.model);
224 const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m';
225 let fixThinkingPrefill = false;228 let fixThinkingPrefill = false;
226 // Add custom stop sequences229 // Add custom stop sequences
227 const stopSequences = [];230 const stopSequences = [];
@@ -1365,6 +1368,18 @@ async function sendElectronHubRequest(request, response) {
1365 };1368 };
1366 }1369 }
13671370
1371 const isClaude = /^claude-/.test(request.body.model);
1372
1373 if (Array.isArray(request.body.messages) && isClaude) {
1374 if (enableSystemPromptCache) {
1375 cachingSystemPromptForOpenRouter(request.body.messages, cacheTTL);
1376 }
1377
1378 if (cachingAtDepth !== -1) {
1379 cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth, cacheTTL);
1380 }
1381 }
1382
1368 const requestBody = {1383 const requestBody = {
1369 'messages': request.body.messages,1384 'messages': request.body.messages,
1370 'model': request.body.model,1385 'model': request.body.model,
@@ -2101,11 +2116,7 @@ router.post('/generate', async function (request, response) {
2101 };2116 };
2102 }2117 }
21032118
2104 const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false, 'boolean');2119 const isClaude = /^anthropic\/claude/.test(request.body.model);
2105 const cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number');
2106 const isClaude3or4 = /anthropic\/claude-(3|opus-4|sonnet-4|haiku-4)/.test(request.body.model);
2107 const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m';
2108
2109 const isGemini = /google\/gemini/.test(request.body.model);2120 const isGemini = /google\/gemini/.test(request.body.model);
2110 const isCacheableGemini = isGemini && await isOpenRouterModelCacheable(request.body.model);2121 const isCacheableGemini = isGemini && await isOpenRouterModelCacheable(request.body.model);
2111 const enableGeminiSystemPromptCache = getConfigValue('gemini.enableSystemPromptCache', false, 'boolean');2122 const enableGeminiSystemPromptCache = getConfigValue('gemini.enableSystemPromptCache', false, 'boolean');
@@ -2114,12 +2125,12 @@ router.post('/generate', async function (request, response) {
2114 embedOpenRouterMedia(request.body.messages);2125 embedOpenRouterMedia(request.body.messages);
2115 addOpenRouterSignatures(request.body.messages, request.body.model);2126 addOpenRouterSignatures(request.body.messages, request.body.model);
21162127
2117 if (isClaude3or4) {2128 if (isClaude) {
2118 if (enableSystemPromptCache) {2129 if (enableSystemPromptCache) {
2119 cachingSystemPromptForOpenRouter(request.body.messages, cacheTTL);2130 cachingSystemPromptForOpenRouter(request.body.messages, cacheTTL);
2120 }2131 }
21212132
2122 if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0) {2133 if (cachingAtDepth !== -1) {
2123 cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth, cacheTTL);2134 cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth, cacheTTL);
2124 }2135 }
2125 }2136 }
@@ -2214,10 +2225,9 @@ router.post('/generate', async function (request, response) {
2214 if (request.body.repetition_penalty !== undefined) {2225 if (request.body.repetition_penalty !== undefined) {
2215 bodyParams['repetition_penalty'] = request.body.repetition_penalty;2226 bodyParams['repetition_penalty'] = request.body.repetition_penalty;
2216 }2227 }
2217 const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false, 'boolean');2228
2218 const isClaude3or4 = /claude-(3|opus-4|sonnet-4|haiku-4)/.test(request.body.model);2229 const isClaude = /^claude-/.test(request.body.model);
2219 const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m';2230 if (enableSystemPromptCache && isClaude) {
2220 if (enableSystemPromptCache && isClaude3or4) {
2221 bodyParams['cache_control'] = {2231 bodyParams['cache_control'] = {
2222 'enabled': true,2232 'enabled': true,
2223 'ttl': cacheTTL,2233 'ttl': cacheTTL,