| |
| |
| |
| |
| |
|
|
| import fs from 'fs'; |
| import path from 'path'; |
| import config from '../config/config.js'; |
| import log from './logger.js'; |
|
|
| |
| const CACHE_DIR = path.join(process.cwd(), 'data', 'signature-cache'); |
|
|
| |
| const MAX_SIGNATURES_PER_MODEL = 3; |
|
|
| |
| |
| |
| function ensureCacheDir() { |
| if (!fs.existsSync(CACHE_DIR)) { |
| fs.mkdirSync(CACHE_DIR, { recursive: true }); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function makeModelKey(model) { |
| if (!model) return null; |
| const raw = String(model); |
| |
| |
| const baseModel = raw.replace(/-(?:1k|2k|4k|8k)$/i, ''); |
| |
| return baseModel.replace(/[<>:"/\\|?*]/g, '_'); |
| } |
|
|
| |
| |
| |
| |
| |
| function getCacheFilePath(modelKey) { |
| return path.join(CACHE_DIR, `${modelKey}.json`); |
| } |
|
|
| |
| |
| |
| |
| |
| function readModelCache(modelKey) { |
| if (!modelKey) return []; |
| |
| try { |
| const filePath = getCacheFilePath(modelKey); |
| if (!fs.existsSync(filePath)) return []; |
| |
| const data = JSON.parse(fs.readFileSync(filePath, 'utf8')); |
| return Array.isArray(data.signatures) ? data.signatures : []; |
| } catch (e) { |
| log.warn(`读取签名缓存失败 (${modelKey}):`, e?.message || e); |
| return []; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function writeModelCache(modelKey, signatures) { |
| if (!modelKey) return; |
| |
| try { |
| ensureCacheDir(); |
| const filePath = getCacheFilePath(modelKey); |
| const data = { |
| model: modelKey, |
| signatures: signatures, |
| lastModified: Date.now() |
| }; |
| fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8'); |
| } catch (e) { |
| log.warn(`写入签名缓存失败 (${modelKey}):`, e?.message || e); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function getLatestEntry(modelKey) { |
| if (!modelKey) return null; |
| |
| const signatures = readModelCache(modelKey); |
| if (signatures.length === 0) return null; |
| |
| return signatures[signatures.length - 1] || null; |
| } |
|
|
| |
| |
| |
| |
| |
| function pushEntry(modelKey, entry) { |
| if (!modelKey || !entry || !entry.signature) return; |
| |
| const signatures = readModelCache(modelKey); |
| |
| |
| if (signatures.length > 0 && signatures[signatures.length - 1]?.signature === entry.signature) { |
| return; |
| } |
| |
| |
| signatures.push(entry); |
| |
| |
| while (signatures.length > MAX_SIGNATURES_PER_MODEL) { |
| signatures.shift(); |
| } |
| |
| writeModelCache(modelKey, signatures); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function shouldCacheSignature({ hasTools = false, isImageModel = false } = {}) { |
| |
| if (config.cacheAllSignatures) return true; |
| |
| |
| if (config.cacheToolSignatures && hasTools) return true; |
| |
| |
| if (config.cacheImageSignatures && isImageModel) return true; |
| |
| return false; |
| } |
|
|
| |
| |
| |
| |
| |
| export function isImageModel(model) { |
| if (!model) return false; |
| const lowerModel = model.toLowerCase(); |
| |
| return lowerModel.includes('image'); |
| } |
|
|
| |
| |
| |
| |
| |
| function processThinkingContent(content) { |
| if (!config.cacheThinking) { |
| return ' '; |
| } |
| return content || ' '; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function setSignature(sessionId, model, signature, content = ' ', options = {}) { |
| if (!signature || !model) return; |
| |
| |
| const isImage = options.isImageModel ?? isImageModel(model); |
| const hasTools = options.hasTools ?? false; |
| |
| if (!shouldCacheSignature({ hasTools, isImageModel: isImage })) { |
| return; |
| } |
| |
| const processedContent = processThinkingContent(content); |
| pushEntry(makeModelKey(model), { signature, content: processedContent }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function getSignature(sessionId, model, options = {}) { |
| if (!model) return null; |
| |
| const entry = getLatestEntry(makeModelKey(model)); |
| if (!entry) return null; |
| |
| |
| return { |
| signature: entry.signature, |
| content: config.cacheThinking ? entry.content : ' ' |
| }; |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function setReasoningSignature(sessionId, model, signature, content = ' ', options = {}) { |
| setSignature(sessionId, model, signature, content, options); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function getReasoningSignature(sessionId, model, options = {}) { |
| return getSignature(sessionId, model, options); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function setToolSignature(sessionId, model, signature, content = ' ', options = {}) { |
| |
| setSignature(sessionId, model, signature, content, { ...options, hasTools: true }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function getToolSignature(sessionId, model, options = {}) { |
| return getSignature(sessionId, model, options); |
| } |
|
|
| |
| |
| |
| export function clearThoughtSignatureCaches() { |
| try { |
| if (fs.existsSync(CACHE_DIR)) { |
| const files = fs.readdirSync(CACHE_DIR); |
| for (const file of files) { |
| if (file.endsWith('.json')) { |
| fs.unlinkSync(path.join(CACHE_DIR, file)); |
| } |
| } |
| } |
| log.info('签名缓存已清除'); |
| } catch (e) { |
| log.warn('清除签名缓存失败:', e?.message || e); |
| } |
| } |
|
|
| |
| ensureCacheDir(); |
|
|