[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>
Signed| @@ -87,6 +87,16 @@ const API_SILICONFLOW = 'https://api.siliconflow.com/v1'; | ||
| 87 | 87 | const API_OPENROUTER = 'https://openrouter.ai/api/v1'; |
| 88 | 88 | |
| 89 | 89 | /** |
| 90 | + * Module-scoped Claude caching configuration values. | |
| 91 | + */ | |
| 92 | +const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m'; | |
| 93 | +const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false, 'boolean'); | |
| 94 | +const cachingAtDepth = (() => { | |
| 95 | + const value = getConfigValue('claude.cachingAtDepth', -1, 'number'); | |
| 96 | + return Number.isInteger(value) && value >= 0 ? value : -1; | |
| 97 | +})(); | |
| 98 | + | |
| 99 | +/** | |
| 90 | 100 | * Cache for cacheable (writing) OpenRouter model IDs. |
| 91 | 101 | * @type {string[]} |
| 92 | 102 | */ |
| @@ -194,12 +204,6 @@ async function sendClaudeRequest(request, response) { | ||
| 194 | 204 | const apiUrl = new URL(request.body.reverse_proxy || API_CLAUDE).toString(); |
| 195 | 205 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE); |
| 196 | 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 | - } | |
| 203 | 207 | |
| 204 | 208 | if (!apiKey) { |
| 205 | 209 | console.warn(color.red(`Claude API key is missing.\n${divider}`)); |
| @@ -221,7 +225,6 @@ async function sendClaudeRequest(request, response) { | ||
| 221 | 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 | 226 | const isLimitedSampling = /^claude-(opus-4-1|sonnet-4-5|haiku-4-5|opus-4-5)/.test(request.body.model); |
| 223 | 227 | const useVerbosity = /^claude-(opus-4-5)/.test(request.body.model); |
| 224 | - const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m'; | |
| 225 | 228 | let fixThinkingPrefill = false; |
| 226 | 229 | // Add custom stop sequences |
| 227 | 230 | const stopSequences = []; |
| @@ -1365,6 +1368,18 @@ async function sendElectronHubRequest(request, response) { | ||
| 1365 | 1368 | }; |
| 1366 | 1369 | } |
| 1367 | 1370 | |
| 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 | 1383 | const requestBody = { |
| 1369 | 1384 | 'messages': request.body.messages, |
| 1370 | 1385 | 'model': request.body.model, |
| @@ -2101,11 +2116,7 @@ router.post('/generate', async function (request, response) { | ||
| 2101 | 2116 | }; |
| 2102 | 2117 | } |
| 2103 | 2118 | |
| 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 | 2120 | const isGemini = /google\/gemini/.test(request.body.model); |
| 2110 | 2121 | const isCacheableGemini = isGemini && await isOpenRouterModelCacheable(request.body.model); |
| 2111 | 2122 | const enableGeminiSystemPromptCache = getConfigValue('gemini.enableSystemPromptCache', false, 'boolean'); |
| @@ -2114,12 +2125,12 @@ router.post('/generate', async function (request, response) { | ||
| 2114 | 2125 | embedOpenRouterMedia(request.body.messages); |
| 2115 | 2126 | addOpenRouterSignatures(request.body.messages, request.body.model); |
| 2116 | 2127 | |
| 2117 | 2128 | if (isClaude3or4isClaude) { |
| 2118 | 2129 | if (enableSystemPromptCache) { |
| 2119 | 2130 | cachingSystemPromptForOpenRouter(request.body.messages, cacheTTL); |
| 2120 | 2131 | } |
| 2121 | 2132 | |
| 2122 | 2133 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >!== 0-1) { |
| 2123 | 2134 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth, cacheTTL); |
| 2124 | 2135 | } |
| 2125 | 2136 | } |
| @@ -2214,10 +2225,9 @@ router.post('/generate', async function (request, response) { | ||
| 2214 | 2225 | if (request.body.repetition_penalty !== undefined) { |
| 2215 | 2226 | bodyParams['repetition_penalty'] = request.body.repetition_penalty; |
| 2216 | 2227 | } |
| 2217 | - const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false, 'boolean'); | |
| 2228 | + | |
| 2218 | 2229 | const isClaude3or4isClaude = /^claude-(3|opus-4|sonnet-4|haiku-4)/.test(request.body.model); |
| 2219 | - const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m'; | |
| 2230 | + if (enableSystemPromptCache && isClaude) { | |
| 2220 | - if (enableSystemPromptCache && isClaude3or4) { | |
| 2221 | 2231 | bodyParams['cache_control'] = { |
| 2222 | 2232 | 'enabled': true, |
| 2223 | 2233 | 'ttl': cacheTTL, |