ai-helper-agent 1.15.5 → 1.15.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_locales/en/messages.json +1 -1
- package/dist/_locales/zh/messages.json +1 -1
- package/dist/assets/{agent-defaults-BEgEbjSw.js → agent-defaults-Ct1YxFzI.js} +2 -2
- package/dist/assets/{agent-defaults-BEgEbjSw.js.map → agent-defaults-Ct1YxFzI.js.map} +1 -1
- package/dist/assets/{agent-store-BRqpVAvZ.js → agent-store-D0Es5Ite.js} +2 -2
- package/dist/assets/{agent-store-BRqpVAvZ.js.map → agent-store-D0Es5Ite.js.map} +1 -1
- package/dist/assets/background.js +4 -4
- package/dist/assets/background.js.map +1 -1
- package/dist/assets/content.js +220 -38
- package/dist/assets/{i18n-BeZarAZC.js → i18n-De3tbtBv.js} +3 -3
- package/dist/assets/i18n-De3tbtBv.js.map +1 -0
- package/dist/assets/options-DpveyMnB.css +1 -0
- package/dist/assets/options.js +2 -2
- package/dist/assets/options.js.map +1 -1
- package/dist/assets/{session-manager-Cm411zzN.js → session-manager-UTq_z6mZ.js} +5 -5
- package/dist/assets/session-manager-UTq_z6mZ.js.map +1 -0
- package/dist/assets/session-manager-ui-DwceYZzF.js +71 -0
- package/dist/assets/session-manager-ui-DwceYZzF.js.map +1 -0
- package/dist/assets/side_panel.js +27 -27
- package/dist/assets/side_panel.js.map +1 -1
- package/dist/icons/badges/license-MIT.svg +21 -0
- package/dist/icons/badges/platform.svg +21 -0
- package/dist/icons/badges/prs-welcome.svg +21 -0
- package/dist/icons/badges/version.svg +21 -0
- package/dist/offscreen/offscreen.js +3 -3
- package/dist/options.html +4 -4
- package/dist/side_panel.html +11 -11
- package/package.json +1 -1
- package/dist/assets/i18n-BeZarAZC.js.map +0 -1
- package/dist/assets/options-COF7Lglv.css +0 -1
- package/dist/assets/session-manager-Cm411zzN.js.map +0 -1
- package/dist/assets/session-manager-ui-B9sOnTsW.js +0 -71
- package/dist/assets/session-manager-ui-B9sOnTsW.js.map +0 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"message": "AI Helper - Web Smart Assistant"
|
|
4
4
|
},
|
|
5
5
|
"extDescription": {
|
|
6
|
-
"message": "
|
|
6
|
+
"message": "LLM-powered web assistant: side panel chat, selection Q&A, ReAct loop with 40+ tools and MCP extensions for browser automation"
|
|
7
7
|
},
|
|
8
8
|
"commandToggleSidepanel": {
|
|
9
9
|
"message": "Toggle side panel"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e,c as t,l as n}from"./i18n-
|
|
1
|
+
import{a as e,c as t,l as n}from"./i18n-De3tbtBv.js";e(`zh`,{tokenCounter:{truncatedMiddle:`
|
|
2
2
|
|
|
3
3
|
... [中间 {tokens} tokens 已截断] ...
|
|
4
4
|
|
|
@@ -96,4 +96,4 @@ Writing principles:
|
|
|
96
96
|
- 示例先行:关键概念必须附带代码或配置示例
|
|
97
97
|
- 面向读者:根据目标受众调整技术深度
|
|
98
98
|
- 保持简洁:避免冗余,每段文字都应承载信息`,toolIds:[`page_content`,`clipboard`,`search_browser_data`,`search_chats`,`fetch_url`,`agent_file`,`agent_search`,`extract_data`,`capture_page`,`search_in_page`],allowSubDispatch:!1}];function Ne(){return`agent_`+Date.now().toString(36)+`_`+Math.random().toString(36).substring(2,6)}export{ge as A,te as B,pe as C,Y as D,G as E,v as F,le as G,C as H,_ as I,y as J,k as K,A as L,Ee as M,ae as N,be as O,M as P,ce as R,U as S,we as T,ie as U,g as V,S as W,ee as X,b as Y,Ce as _,Ae as a,_e as b,Se as c,X as d,ue as f,J as g,K as h,je as i,q as j,fe as k,Te as l,ye as m,Me as n,ke as o,he as p,oe as q,Ne as r,Oe as s,$ as t,W as u,Z as v,me as w,xe as x,H as y,D as z};
|
|
99
|
-
//# sourceMappingURL=agent-defaults-
|
|
99
|
+
//# sourceMappingURL=agent-defaults-Ct1YxFzI.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-defaults-BEgEbjSw.js","names":[],"sources":["../../src/shared/token-counter.js","../../src/storage/db.js","../../src/storage/token-store.js","../../src/shared/agent-defaults.js"],"sourcesContent":["import logger from './logger.js';\nimport { t, registerTranslations } from './i18n.js';\n\n// shared/token-counter.js - Token 估算工具\n// 使用字符数估算 token 数,无需引入 tiktoken 等重量级依赖\n\nregisterTranslations('zh', {\n tokenCounter: {\n truncatedMiddle: '\\n\\n... [中间 {tokens} tokens 已截断] ...\\n\\n',\n omittedTokens: '\\n\\n... [省略 {tokens} tokens] ...\\n\\n',\n omittedMiddleHtml: '\\n<!-- 省略中间内容 -->\\n',\n truncated: '[截断]',\n omittedKey: '...[省略]',\n moreFields: '还有 {count} 个字段',\n unknownTool: '未知工具',\n userQuestion: '- 用户问题:{question}',\n usedTools: '- 使用的工具:{tools}',\n historySummary: '[历史摘要]',\n },\n});\n\nregisterTranslations('en', {\n tokenCounter: {\n truncatedMiddle: '\\n\\n... [middle {tokens} tokens truncated] ...\\n\\n',\n omittedTokens: '\\n\\n... [{tokens} tokens omitted] ...\\n\\n',\n omittedMiddleHtml: '\\n<!-- middle content omitted -->\\n',\n truncated: '[truncated]',\n omittedKey: '...[omitted]',\n moreFields: '{count} more fields',\n unknownTool: 'Unknown tool',\n userQuestion: '- User question: {question}',\n usedTools: '- Tools used: {tools}',\n historySummary: '[History Summary]',\n },\n});\n\n// 估算常量:中文约 1.5 字符/token,英文约 4 字符/token\n// 参考 DeepSeek tokenizer 的行为特征\nconst CHINESE_CHAR_REGEX = /[\\u4e00-\\u9fff\\u3400-\\u4dbf\\uf900-\\ufaff]/g;\nconst CHARS_PER_TOKEN_CN = 1.5;\nconst CHARS_PER_TOKEN_EN = 4;\n\n// 每条消息的 role + 结构开销(约 4 tokens)\nconst MESSAGE_OVERHEAD = 4;\n\n// ============================================================\n// 图片 Token 估算(参考 OpenAI Vision 高分辨率模式)\n// 多数多模态模型(OpenAI/Claude/DeepSeek VL 等)的图片 token 与分辨率相关,\n// 而非 Base64 字符串长度。按 Base64 字符串估算会严重高估(约 20 倍)。\n// 公式:tiles = ceil(w/512) * ceil(h/512),tokens = 85 + 170 * tiles\n// ============================================================\nconst IMAGE_BASE_TOKENS = 85;\nconst IMAGE_TOKENS_PER_TILE = 170;\nconst IMAGE_TILE_SIZE = 512;\n\n/**\n * 估算单张图片的 token 数量\n * 优先用压缩后尺寸(width/height)按 tile 公式计算;\n * 缺失尺寸时按 Base64 字节大小兜底估算(经验值:JPEG 约 65 bytes/token)\n * @param {{ url?: string, width?: number, height?: number }} imageUrlObj\n * @returns {number}\n */\nfunction estimateImageTokens(imageUrlObj) {\n if (!imageUrlObj) return IMAGE_BASE_TOKENS;\n\n const w = Number(imageUrlObj.width);\n const h = Number(imageUrlObj.height);\n if (w > 0 && h > 0) {\n const tiles = Math.ceil(w / IMAGE_TILE_SIZE) * Math.ceil(h / IMAGE_TILE_SIZE);\n return IMAGE_BASE_TOKENS + IMAGE_TOKENS_PER_TILE * tiles;\n }\n\n // 兜底:按 Base64 字节估算(无尺寸信息,如旧数据)\n // 50KB JPEG ≈ 765 tokens(OpenAI),故 1 token ≈ 65 bytes\n const url = imageUrlObj.url || '';\n const base64Data = url.split(',')[1] || url;\n const bytes = (base64Data.length * 3) / 4; // Base64 每 4 字符 ≈ 3 字节\n return Math.max(IMAGE_BASE_TOKENS, Math.ceil(bytes / 65));\n}\n\n// ============================================================\n// 实时校准:基于 API 实际返回的 prompt_tokens 修正估算偏差\n// 使用指数加权移动平均(EWMA)平滑,避免单次异常波动\n// ============================================================\nlet calibrationFactor = 1.0;\nlet calibrationSamples = 0;\nconst CALIBRATION_MIN_SAMPLES = 3; // 至少 3 次采样后才启用校准\nconst CALIBRATION_RATIO_MIN = 0.3; // 异常比例下限\nconst CALIBRATION_RATIO_MAX = 3.0; // 异常比例上限\n\n/**\n * 更新 token 估算校准因子\n * 在每次 API 调用完成后,用 API 返回的实际 prompt_tokens 与我们的估算值对比\n * @param {number} estimated - 我们的估算值(不含输出预留)\n * @param {number} actual - API 返回的 prompt_tokens\n */\nexport function updateCalibration(estimated, actual) {\n if (!actual || actual <= 0 || !estimated || estimated <= 0) return;\n\n const ratio = actual / estimated;\n // 过滤异常比例(如估算或实际值明显错误)\n if (ratio < CALIBRATION_RATIO_MIN || ratio > CALIBRATION_RATIO_MAX) {\n logger.warn(`[TokenCounter] calibration ratioexception,ignore: estimated=${estimated}, actual=${actual}, ratio=${ratio.toFixed(3)}`);\n return;\n }\n\n calibrationSamples++;\n // EWMA:样本少时权重高(快速收敛),样本多时权重低(稳定)\n const alpha = Math.min(0.3, 1 / calibrationSamples);\n const oldFactor = calibrationFactor;\n calibrationFactor = calibrationFactor * (1 - alpha) + ratio * alpha;\n\n logger.debug(`[TokenCounter] Tokencalibration update: ratio=${ratio.toFixed(3)}, alpha=${alpha.toFixed(3)}, old=${oldFactor.toFixed(3)} → new=${calibrationFactor.toFixed(3)}, samples=${calibrationSamples}`);\n}\n\n/**\n * 获取校准后的 token 估算\n * 样本不足时不校准(返回原始估算值)\n * @param {number} rawEstimate - 原始估算值\n * @returns {number}\n */\nexport function getCalibratedTokens(rawEstimate) {\n if (calibrationSamples < CALIBRATION_MIN_SAMPLES || calibrationFactor <= 0) {\n return rawEstimate;\n }\n return Math.ceil(rawEstimate * calibrationFactor);\n}\n\n/**\n * 获取当前校准状态信息(用于诊断/日志)\n * @returns {{ factor: number, samples: number, active: boolean }}\n */\nexport function getCalibrationInfo() {\n return {\n factor: calibrationFactor,\n samples: calibrationSamples,\n active: calibrationSamples >= CALIBRATION_MIN_SAMPLES\n };\n}\n\n/**\n * 估算单段文本的 token 数量\n * @param {string} text\n * @returns {number}\n */\nexport function estimateTokens(text) {\n if (!text || typeof text !== 'string') return 0;\n const chineseMatches = text.match(CHINESE_CHAR_REGEX);\n const chineseChars = chineseMatches ? chineseMatches.length : 0;\n const otherChars = text.length - chineseChars;\n return Math.ceil(chineseChars / CHARS_PER_TOKEN_CN + otherChars / CHARS_PER_TOKEN_EN);\n}\n\n/**\n * 估算消息数组的总 token 数\n * 支持 multipart content(文本 + 图片):图片按分辨率估算,避免 Base64 字符串高估\n * @param {Array<{role: string, content: string|Array, tool_calls?: Array, tool_call_id?: string, reasoning_content?: string}>} messages\n * @returns {number}\n */\nexport function estimateMessagesTokens(messages) {\n if (!messages || messages.length === 0) return 0;\n return messages.reduce((sum, m) => {\n let tokens = 0;\n if (typeof m.content === 'string') {\n tokens = estimateTokens(m.content);\n } else if (Array.isArray(m.content)) {\n // 多模态 content:分别按 part 类型估算\n for (const part of m.content) {\n if (!part) continue;\n if (part.type === 'text') {\n tokens += estimateTokens(part.text || '');\n } else if (part.type === 'image_url' && part.image_url) {\n // 图片按分辨率估算,不把 Base64 字符串计入\n tokens += estimateImageTokens(part.image_url);\n } else {\n tokens += estimateTokens(JSON.stringify(part));\n }\n }\n } else if (m.content) {\n tokens = estimateTokens(JSON.stringify(m.content));\n }\n\n // 附加字段 token\n let extra = '';\n if (m.tool_calls) extra += JSON.stringify(m.tool_calls);\n if (m.tool_call_id) extra += m.tool_call_id;\n if (m.reasoning_content) extra += m.reasoning_content;\n if (extra) tokens += estimateTokens(extra);\n\n return sum + tokens + MESSAGE_OVERHEAD;\n }, 0);\n}\n\n/**\n * 按 token 上限截断文本,保留头部和尾部\n * @param {string} content - 原始文本\n * @param {number} maxTokens - 最大 token 数\n * @returns {string}\n */\nexport function truncateByTokens(content, maxTokens) {\n if (!content || typeof content !== 'string') return content;\n const currentTokens = estimateTokens(content);\n if (currentTokens <= maxTokens) return content;\n\n // 保留头部 70%,尾部 30%\n const headTokens = Math.floor(maxTokens * 0.7);\n const tailTokens = maxTokens - headTokens;\n\n const headChars = Math.floor(headTokens * CHARS_PER_TOKEN_EN);\n const tailChars = Math.floor(tailTokens * CHARS_PER_TOKEN_EN);\n\n const head = content.slice(0, headChars);\n const tail = content.slice(-tailChars);\n\n const truncatedTokens = currentTokens - maxTokens;\n return head + t('tokenCounter.truncatedMiddle', { tokens: truncatedTokens }) + tail;\n}\n\n/**\n * 智能工具结果截断:根据内容类型采用不同的保留策略\n * - HTML: 优先保留 <body> 内容\n * - JSON: 保留顶层 key 结构,值摘要\n * - 代码: 保留头部 + 关键结构 + 尾部\n * - 纯文本: 固定 60% / 20% 头尾截断\n * @param {string} content - 原始内容\n * @param {number} maxTokens - 最大 token 数\n * @param {string} [contentType] - 内容类型提示: 'html' | 'json' | 'code' | 'text'\n * @returns {string}\n */\nexport function truncateContentSmart(content, maxTokens, contentType) {\n if (!content || typeof content !== 'string') return content;\n if (estimateTokens(content) <= maxTokens) return content;\n\n // 自动检测类型\n if (!contentType) {\n const trimmed = content.trim();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n contentType = 'json';\n } else if (/<(!DOCTYPE|html|head|body)[\\s>]/i.test(trimmed.substring(0, 200))) {\n contentType = 'html';\n } else {\n contentType = 'text';\n }\n }\n\n if (contentType === 'html') {\n return truncateHtmlSmart(content, maxTokens);\n } else if (contentType === 'json') {\n return truncateJsonSmart(content, maxTokens);\n }\n // text/code: 保留前 60% 字符 + 后 20% 字符\n const headChars = Math.floor(maxTokens * 0.6 * CHARS_PER_TOKEN_EN);\n const tailChars = Math.floor(maxTokens * 0.2 * CHARS_PER_TOKEN_EN);\n const head = content.slice(0, headChars);\n const tail = content.slice(-tailChars);\n return head + t('tokenCounter.omittedTokens', { tokens: estimateTokens(content) - maxTokens }) + tail;\n}\n\n/**\n * HTML 智能截断:优先保留 <body> 内容\n */\nfunction truncateHtmlSmart(content, maxTokens) {\n const bodyMatch = content.match(/<body[\\s>][\\s\\S]*<\\/body>/i);\n if (bodyMatch) {\n const bodyContent = bodyMatch[0];\n const headContent = content.substring(0, content.indexOf(bodyContent));\n // body 内容占 80%,头部占 20%\n const bodyBudget = Math.floor(maxTokens * 0.8);\n const headBudget = maxTokens - bodyBudget;\n const headChars = Math.floor(headBudget * CHARS_PER_TOKEN_EN);\n const bodyChars = Math.floor(bodyBudget * CHARS_PER_TOKEN_EN);\n return content.slice(0, headChars) +\n bodyContent.slice(0, bodyChars) +\n t('tokenCounter.omittedMiddleHtml') +\n bodyContent.slice(-Math.floor(bodyChars * 0.3)) +\n content.slice(-Math.floor(headChars * 0.3));\n }\n // 无 body 标签,fallback 到通用截断\n return truncateByTokens(content, maxTokens);\n}\n\n/**\n * JSON 智能截断:保留顶层 key 结构,值摘要\n */\nfunction truncateJsonSmart(content, maxTokens) {\n try {\n const obj = JSON.parse(content);\n if (typeof obj !== 'object' || obj === null) {\n return truncateByTokens(content, maxTokens);\n }\n\n const summarized = {};\n const keys = Object.keys(obj);\n const maxKeys = Math.min(keys.length, 30);\n\n for (let i = 0; i < maxKeys; i++) {\n const key = keys[i];\n const val = obj[key];\n if (typeof val === 'string' && val.length > 200) {\n summarized[key] = val.substring(0, 200) + '...' + t('tokenCounter.truncated');\n } else if (typeof val === 'object' && val !== null) {\n summarized[key] = `[${Array.isArray(val) ? `Array(${val.length})` : `Object(${Object.keys(val).length} keys)`}]`;\n } else {\n summarized[key] = val;\n }\n }\n\n if (keys.length > maxKeys) {\n summarized['...[省略]'] = `还有 ${keys.length - maxKeys} 个字段`;\n }\n\n let result = JSON.stringify(summarized, null, 2);\n // 如果摘要后仍超预算,做最终字符截断\n if (estimateTokens(result) > maxTokens) {\n result = result.substring(0, Math.floor(maxTokens * CHARS_PER_TOKEN_EN));\n }\n return result;\n } catch {\n return truncateByTokens(content, maxTokens);\n }\n}\n\n/**\n * 模型上下文窗口配置(单位:tokens)\n * 作为内置默认值,可通过添加自定义模型时设置上下文窗口来覆盖\n */\nexport const MODEL_CONTEXT_WINDOWS = {\n 'deepseek-v4-pro': 128000,\n 'deepseek-v3': 128000,\n 'deepseek-chat': 64000,\n 'deepseek-reasoner': 64000,\n 'gpt-4o': 128000,\n 'gpt-4o-mini': 128000,\n 'gpt-4-turbo': 128000,\n 'gpt-4': 8192,\n 'gpt-3.5-turbo': 16385,\n 'claude-3.5-sonnet': 200000,\n 'claude-3-opus': 200000,\n 'claude-3-haiku': 200000,\n 'claude-3-sonnet': 200000,\n default: 64000\n};\n\n/**\n * 标准化自定义模型配置数组,向前兼容旧格式(string[] → object[])\n * @param {Array<string|{name: string, contextWindow?: number}>} customModels\n * @returns {Map<string, number>} modelName → contextWindow 的映射\n */\nexport function normalizeCustomModels(customModels) {\n const map = new Map();\n if (!customModels || !Array.isArray(customModels)) return map;\n for (const item of customModels) {\n if (typeof item === 'string') {\n // 旧格式:仅模型名,上下文窗口未知,不加入 map(回退到内置映射)\n continue;\n }\n if (item && typeof item === 'object' && item.name) {\n map.set(item.name, item.contextWindow || 0);\n }\n }\n return map;\n}\n\n/**\n * 获取模型上下文窗口大小\n * 优先级:自定义模型配置 > 用户全局配置 > 内置映射 > default\n * @param {string} modelName\n * @param {number} [userConfiguredWindow] - 用户手动配置的上下文窗口(全局覆盖)\n * @param {Map<string, number>} [customModelMap] - 自定义模型的上下文窗口映射\n * @returns {number}\n */\nexport function getContextWindow(modelName, userConfiguredWindow, customModelMap) {\n // 1. 优先使用自定义模型中该模型单独配置的上下文窗口\n if (customModelMap && customModelMap.has(modelName)) {\n const window = customModelMap.get(modelName);\n if (window && window > 0) return window;\n }\n // 2. 全局上下文窗口覆盖\n if (userConfiguredWindow && userConfiguredWindow > 0) return userConfiguredWindow;\n // 3. 内置模型映射\n return MODEL_CONTEXT_WINDOWS[modelName] || MODEL_CONTEXT_WINDOWS.default;\n}\n\n// ============================================================\n// 上下文预算分配\n// ============================================================\n\n// 系统提示词预留 token 数\nexport const SYSTEM_PROMPT_BUDGET = 2000;\n\n// 工具定义预留 token 数(工具数量 * 平均每个工具定义 ~200 tokens)\nexport function estimateToolsTokens(toolCount) {\n return toolCount * 200;\n}\n\n// 输出预留 token 数(给模型生成回答的空间)\nexport const OUTPUT_BUDGET = 4096;\n\n/**\n * 计算可用于消息历史的 token 预算\n * @param {string} modelName\n * @param {number} toolCount\n * @param {number} [userConfiguredWindow]\n * @param {Map<string, number>} [customModelMap] - 自定义模型的上下文窗口映射\n * @returns {number}\n */\nexport function getMessageBudget(modelName, toolCount = 0, userConfiguredWindow, customModelMap) {\n const contextWindow = getContextWindow(modelName, userConfiguredWindow, customModelMap);\n return contextWindow - SYSTEM_PROMPT_BUDGET - estimateToolsTokens(toolCount) - OUTPUT_BUDGET;\n}\n\n/**\n * 评估上下文压力等级\n * @param {number} usedTokens\n * @param {number} budget\n * @returns {{ level: 'safe'|'warning'|'critical', ratio: number }}\n */\nexport function assessContextPressure(usedTokens, budget) {\n const ratio = usedTokens / budget;\n if (ratio < 0.7) return { level: 'safe', ratio };\n if (ratio < 0.9) return { level: 'warning', ratio };\n return { level: 'critical', ratio };\n}\n\n/**\n * 基于 token 预算裁剪历史消息(从旧到新移除,确保 tool_calls/tool 配对)\n * 返回裁剪后的消息数组 + 被裁剪掉的消息摘要信息\n * @param {Array} messages - 包含 system message 的完整消息数组\n * @param {number} budget - token 预算上限\n * @param {Object} [options]\n * @param {boolean} [options.preserveSystem=true] - 是否保留 system message\n * @param {boolean} [options.generateSummary=true] - 是否生成被裁剪消息的摘要\n * @returns {{ messages: Array, trimmedCount: number, summary: string|null }}\n */\nexport function trimMessagesByBudget(messages, budget, options = {}) {\n const { preserveSystem = true, generateSummary = true } = options;\n const originalLen = messages.length;\n\n if (estimateMessagesTokens(messages) <= budget) {\n return { messages: [...messages], trimmedCount: 0, summary: null };\n }\n\n const systemMsg = preserveSystem && messages[0]?.role === 'system' ? [messages[0]] : [];\n const rest = systemMsg.length ? [...messages.slice(1)] : [...messages];\n\n const trimmedMessages = [];\n\n while (rest.length > 0) {\n const currentTokens = estimateMessagesTokens([...systemMsg, ...rest]);\n if (currentTokens <= budget) break;\n\n const removed = rest.shift();\n trimmedMessages.push(removed);\n\n // 如果移除的是 assistant(tool_calls),则后续的 tool 消息也要一并移除\n if (removed?.role === 'assistant' && removed.tool_calls) {\n while (rest.length > 0 && rest[0]?.role === 'tool') {\n trimmedMessages.push(rest.shift());\n }\n }\n }\n\n const result = [...systemMsg, ...rest];\n const summary = generateSummary ? generateMessagesSummary(trimmedMessages) : null;\n\n return {\n messages: result,\n trimmedCount: originalLen - result.length,\n summary\n };\n}\n\n/**\n * 从被裁剪的消息中生成规则式结构化摘要\n * @param {Array} trimmedMessages - 被裁剪的消息\n * @returns {string|null}\n */\nexport function generateMessagesSummary(trimmedMessages) {\n if (!trimmedMessages || trimmedMessages.length === 0) return null;\n\n const summaryParts = [];\n const userQuestions = [];\n const toolCalls = [];\n\n for (const msg of trimmedMessages) {\n if (msg.role === 'user') {\n const text = typeof msg.content === 'string'\n ? msg.content\n : (Array.isArray(msg.content)\n ? msg.content.filter(c => c.type === 'text').map(c => c.text).join('')\n : '');\n if (text && text.trim()) {\n const question = text.replace(/\\[选中内容\\]\\n[\\s\\S]*?\\n\\n\\[用户问题\\]\\n/, '')\n .replace(/\\[引用内容\\]\\n[\\s\\S]*?\\n\\n\\[用户问题\\]\\n/, '')\n .replace(/\\[选中内容摘要\\]\\n[\\s\\S]*?\\n\\n\\[用户问题\\]\\n/, '')\n .replace(/\\[引用内容摘要\\]\\n[\\s\\S]*?\\n\\n\\[用户问题\\]\\n/, '')\n .trim()\n .substring(0, 80);\n if (question) userQuestions.push(question);\n }\n } else if (msg.role === 'assistant' && msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n const name = tc.function?.name || tc.name || t('tokenCounter.unknownTool');\n toolCalls.push(name);\n }\n }\n }\n\n if (userQuestions.length > 0) {\n summaryParts.push(t('tokenCounter.userQuestion', { question: userQuestions[userQuestions.length - 1] }));\n }\n if (toolCalls.length > 0) {\n // 去重并限制数量\n const uniqueTools = [...new Set(toolCalls)].slice(0, 10);\n summaryParts.push(t('tokenCounter.usedTools', { tools: uniqueTools.join(', ') }));\n }\n\n if (summaryParts.length === 0) return null;\n return t('tokenCounter.historySummary') + '\\n' + summaryParts.join('\\n');\n}\n\n/**\n * 清洗消息 content 字符串中的非法 Unicode 字符\n * - 替换孤立代理对字符(lone surrogates,由 substring 截断 emoji 等导致)为 U+FFFD\n * - 某些 API 服务端 JSON 解析器(如支持 \\x 扩展的解析器)会因非法 Unicode 而解析失败\n * @param {string} str - 原始字符串\n * @returns {string} 清洗后的字符串\n */\nfunction sanitizeContentForApi(str) {\n if (typeof str !== 'string') return str;\n // 匹配:\n // 1. 高位代理 U+D800..U+DBFF 后面没有紧跟低位代理\n // 2. 低位代理 U+DC00..U+DFFF 前面没有高位代理\n // 使用 Unicode property escapes 可读性更好,但兼容性考虑用显式范围\n return str.replace(/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g, '\\uFFFD');\n}\n\nconst API_ALLOWED_FIELDS = new Set(['role', 'content', 'tool_calls', 'tool_call_id', 'name', 'reasoning_content', 'prefix']);\n\n/**\n * 过滤消息中的内部字段,确保消息格式符合 API 要求\n * 同时扫描并修复 assistant(tool_calls) 与 tool 消息的配对关系\n * @param {Array} messages - 原始消息数组\n * @returns {Array} 过滤后的消息数组\n */\nexport function filterApiMessages(messages) {\n const filtered = messages.map((msg, index) => {\n const result = {};\n for (const key of API_ALLOWED_FIELDS) {\n if (key in msg) {\n result[key] = msg[key];\n }\n }\n\n // 清理 multipart content 中 image_url 的内部字段\n // filterApiMessages 负责剥离 original_url(原图 Base64),保留 width/height 供 token 估算\n // 此函数在 token 估算之前调用,width/height 用于 estimateImageTokens 按分辨率精准估算(避免按 Base64 字节高估约 20 倍)\n if (Array.isArray(result.content)) {\n result.content = result.content.map(part => {\n if (part && part.type === 'image_url' && part.image_url) {\n const clean = { url: part.image_url.url };\n if (part.image_url.width != null) clean.width = part.image_url.width;\n if (part.image_url.height != null) clean.height = part.image_url.height;\n return { ...part, image_url: clean };\n }\n // 清洗 text part 中的非法 Unicode 字符\n if (part && part.type === 'text' && typeof part.text === 'string') {\n return { ...part, text: sanitizeContentForApi(part.text) };\n }\n return part;\n });\n }\n\n // 清洗 content 字符串中的非法 Unicode(如孤立的代理对字符)\n // 防止 substring 截断 emoji 等原因产生的非法字符导致 API 服务端 JSON 解析失败\n if (typeof result.content === 'string') {\n result.content = sanitizeContentForApi(result.content);\n }\n\n if (result.role === 'tool') {\n if (!result.tool_call_id) {\n logger.warn(`[Background] foundmessage ${index} missing tool_call_id,skipped`, msg);\n return null;\n }\n return result;\n }\n\n if (result.role === 'assistant' && Array.isArray(result.tool_calls)) {\n result.tool_calls = result.tool_calls.map(tc => ({\n id: tc.id,\n type: tc.type,\n function: tc.function\n }));\n } else if (result.role === 'assistant' && result.tool_calls !== undefined) {\n logger.warn(`[Background] foundmessage ${index} tool_calls non-group (type: ${typeof result.tool_calls}),cleared`);\n delete result.tool_calls;\n }\n\n if (result.role === 'assistant' && result.content === null) {\n result.content = '';\n }\n\n return result;\n }).filter(msg => msg !== null);\n\n // 扫描并修复 tool_calls/tool 配对:验证 tool_call_id 与 tool_calls[].id 严格匹配\n for (let i = 0; i < filtered.length; i++) {\n const msg = filtered[i];\n if (msg?.role === 'assistant' && msg.tool_calls) {\n // 收集该 assistant 之后连续出现的 tool 消息的 tool_call_id\n const followingToolIds = new Set();\n for (let j = i + 1; j < filtered.length; j++) {\n if (filtered[j].role === 'tool') {\n followingToolIds.add(filtered[j].tool_call_id);\n } else {\n break;\n }\n }\n\n // 检查 assistant.tool_calls 中的每个 id 是否都有对应的 tool 响应\n const matchedCalls = msg.tool_calls.filter(tc => followingToolIds.has(tc.id));\n\n if (matchedCalls.length === 0) {\n // 完全没有匹配的 tool 响应 → 清除所有 tool_calls\n logger.warn('[Background] filterApiMessages: ', i, ' assistant message tool_calls no matchconfig tool response,cleared');\n delete msg.tool_calls;\n if (!msg.content) {\n filtered.splice(i, 1);\n i--;\n }\n } else if (matchedCalls.length < msg.tool_calls.length) {\n // 部分匹配 → 只保留有响应的 tool_calls\n const unmatched = msg.tool_calls.filter(tc => !followingToolIds.has(tc.id));\n logger.warn('[Background] filterApiMessages: ', i, ' assistant message', unmatched.length, ' tool_call no corresponding response,removed,keep', matchedCalls.length, '');\n msg.tool_calls = matchedCalls;\n }\n }\n }\n\n return filtered;\n}\n\n/**\n * 清理 image_url 中的额外字段,仅保留标准 url 字段\n * 在实际构建 API 请求体时调用,移除 width/height 等所有非标准字段\n * 返回浅拷贝新数组,不修改原数组,不影响后续 token 估算和裁剪逻辑\n * @param {Array} messages - 消息数组\n * @returns {Array} 清理后的消息数组\n */\nexport function sanitizeImageUrlsForApi(messages) {\n if (!messages || messages.length === 0) return messages;\n return messages.map(msg => {\n if (!msg || !Array.isArray(msg.content)) return msg;\n let contentChanged = false;\n const newContent = msg.content.map(part => {\n if (part && part.type === 'image_url' && part.image_url) {\n contentChanged = true;\n return { type: 'image_url', image_url: { url: part.image_url.url } };\n }\n return part;\n });\n return contentChanged ? { ...msg, content: newContent } : msg;\n });\n}\n\n/**\n * 从消息 content 中移除图片数据,仅保留文本部分\n * 用于发送历史消息时避免携带已无用的 Base64 图片\n * @param {string|Array} content - 消息内容\n * @returns {string|Array} 仅含文本的内容\n */\nexport function stripImagesFromContent(content) {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n const textParts = content.filter(c => c.type === 'text');\n return textParts.length === 1 ? textParts[0].text : textParts;\n }\n return content;\n}\n\n/**\n * 从消息 content 中提取纯文本字符串\n * 用于日志、摘要、预筛选、反思等\"把 content 当文本用\"的场景,\n * 避免把含 Base64 图片的数组 content 直接 JSON.stringify 导致图片数据污染文本\n * - 字符串:原样返回\n * - 数组(多模态):拼接所有 text part,忽略 image_url 等非文本部分\n * - null/undefined:返回空字符串\n * - 其他对象:JSON.stringify 兜底\n * @param {string|Array|*} content\n * @returns {string}\n */\nexport function extractTextFromContent(content) {\n if (content == null) return '';\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content\n .filter(part => part && part.type === 'text' && part.text)\n .map(part => part.text)\n .join('\\n');\n }\n return JSON.stringify(content);\n}\n\n// ============================================================\n// 引用内容压缩\n// ============================================================\n\n// 引用内容保留的最大 token 数(超过此值的引用将压缩)\nconst MAX_QUOTED_CONTEXT_TOKENS = 2000;\n\n/**\n * 压缩引用/选中内容,防止大段内容永久占据上下文\n * @param {string} ctx - 原始引用内容\n * @returns {{ compressed: string, wasCompressed: boolean }}\n */\nexport function compressQuotedContext(ctx) {\n if (!ctx) return { compressed: ctx, wasCompressed: false };\n const tokens = estimateTokens(ctx);\n if (tokens <= MAX_QUOTED_CONTEXT_TOKENS) {\n return { compressed: ctx, wasCompressed: false };\n }\n const truncated = truncateByTokens(ctx, MAX_QUOTED_CONTEXT_TOKENS);\n logger.debug(`[TokenCounter] referenced contentcompress: ${tokens} → ${estimateTokens(truncated)} tokens`);\n return { compressed: truncated, wasCompressed: true };\n}\n","import logger from '../shared/logger.js';\nimport { t, registerTranslations } from '../shared/i18n.js';\n\n// storage/db.js - IndexedDB 封装层\n// 提供 Promise 化的 IndexedDB 操作,支持 side panel 和 service worker 共享访问\n\nregisterTranslations('zh', {\n db: {\n sessionLabel: '会话 {id}',\n archivedLabel: '[归档] {label}',\n },\n});\n\nregisterTranslations('en', {\n db: {\n sessionLabel: 'Session {id}',\n archivedLabel: '[Archived] {label}',\n },\n});\n\nconst DB_NAME = 'ai-helper-db';\nconst DB_VERSION = 5;\n\n// ReAct Checkpoint TTL:7 天\nexport const REACT_CHECKPOINT_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\n// chrome.storage.local 中 checkpoint 的 key 前缀(后备存储)\n// 当 IndexedDB 的 reactCheckpoints store 不可用时,使用 chrome.storage.local 作为可靠后备\nconst CHECKPOINT_KEY_PREFIX = 'react_checkpoint_';\n\n/**\n * 获取数据库连接(单例)\n * @returns {Promise<IDBDatabase>}\n */\nfunction openDB() {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onupgradeneeded = (event) => {\n const db = event.target.result;\n\n // 活跃会话存储(keyPath: id)\n if (!db.objectStoreNames.contains('sessions')) {\n const sessionsStore = db.createObjectStore('sessions', { keyPath: 'id' });\n sessionsStore.createIndex('updatedAt', 'updatedAt', { unique: false });\n }\n\n // 活跃会话 ID(单条记录,keyPath: key)\n if (!db.objectStoreNames.contains('activeSession')) {\n db.createObjectStore('activeSession', { keyPath: 'key' });\n }\n\n // 归档会话存储(keyPath: id)\n if (!db.objectStoreNames.contains('archivedSessions')) {\n const archiveStore = db.createObjectStore('archivedSessions', { keyPath: 'id' });\n archiveStore.createIndex('createdAt', 'createdAt', { unique: false });\n }\n\n // UI 原型存储(keyPath: id)\n if (!db.objectStoreNames.contains('uiPrototypes')) {\n const prototypeStore = db.createObjectStore('uiPrototypes', { keyPath: 'id' });\n prototypeStore.createIndex('createdAt', 'createdAt', { unique: false });\n prototypeStore.createIndex('sessionId', 'sessionId', { unique: false });\n }\n\n // Token 使用统计存储(keyPath: id)\n if (!db.objectStoreNames.contains('tokenStats')) {\n const tokenStatsStore = db.createObjectStore('tokenStats', { keyPath: 'id' });\n tokenStatsStore.createIndex('sessionId', 'sessionId', { unique: false });\n tokenStatsStore.createIndex('timestamp', 'timestamp', { unique: false });\n }\n\n // ReAct Checkpoint 存储(keyPath: sessionId)—— 用于任务中断后续接\n if (!db.objectStoreNames.contains('reactCheckpoints')) {\n const checkpointStore = db.createObjectStore('reactCheckpoints', { keyPath: 'sessionId' });\n checkpointStore.createIndex('updatedAt', 'updatedAt', { unique: false });\n }\n\n // 消息收藏存储(keyPath: id)\n if (!db.objectStoreNames.contains('bookmarks')) {\n const bookmarksStore = db.createObjectStore('bookmarks', { keyPath: 'id' });\n bookmarksStore.createIndex('sessionId', 'sessionId', { unique: false });\n bookmarksStore.createIndex('createdAt', 'createdAt', { unique: false });\n }\n };\n\n request.onsuccess = (event) => {\n const db = event.target.result;\n\n // 关键:当其他连接(如 side_panel 持有的旧连接)触发版本变更时,\n // 主动关闭当前连接,避免阻塞 DB 升级。\n // 没有此处理会导致 SW 升级 DB 时被旧连接阻塞,reactCheckpoints store 无法创建,\n // 表现为 checkpoint 写入静默失败 → 恢复时\"未找到 checkpoint\"。\n db.onversionchange = (event) => {\n logger.debug('[IDB] detected versionchange event,maindynamicclosecurrent connection as allows upgrade');\n try { event.target.close(); } catch (e) {}\n // 重置单例缓存,下次 getDB() 会重新打开\n dbPromise = null;\n };\n\n resolve(db);\n };\n\n request.onerror = (event) => {\n logger.error('[IDB] opendatabase failed:', event.target.error);\n reject(event.target.error);\n };\n\n // 处理被阻塞的升级(其他连接持有旧版本时触发)\n request.onblocked = () => {\n logger.warn('[IDB] databaseupgrade blocked (other connections notclose),waitingretry...');\n // 不直接 reject,让调用方稍后重试 getDB() 时自然获得新连接\n };\n });\n}\n\nlet dbPromise = null;\n\n/**\n * 重置数据库连接缓存(用于 Service Worker 重启后恢复)\n */\nfunction resetDBConnection() {\n if (dbPromise) {\n dbPromise = null;\n logger.debug('[IDB] databaseconnectionreset');\n }\n}\n\n/**\n * 获取数据库实例(缓存单例)\n */\nexport function getDB() {\n if (!dbPromise) {\n dbPromise = openDB();\n }\n return dbPromise;\n}\n\n/**\n * 通用事务包装器(带连接恢复重试)\n * @param {string} storeName - objectStore 名称\n * @param {'readonly'|'readwrite'} mode\n * @param {function(IDBObjectStore, function): void} callback\n * @param {boolean} [isRetry=false] - 是否为重试调用(内部使用)\n * @returns {Promise<any>}\n */\nexport function withStore(storeName, mode, callback, isRetry = false) {\n return getDB().then((db) => {\n return new Promise((resolve, reject) => {\n let resolvedValue = null;\n let settled = false;\n\n // 检查 store 是否存在;不存在时强制重置连接并重试(触发 DB 升级创建缺失的 store)\n // 这是 \"reactCheckpoints store 不存在导致 checkpoint 静默失败\" 的根因修复\n if (!db.objectStoreNames.contains(storeName)) {\n logger.error(`[IDB] store \"${storeName}\" not found!current DB version: ${db.version}, store column table: [${Array.from(db.objectStoreNames).join(', ')}]`);\n if (!isRetry) {\n settled = true;\n // 强制重置连接,下次 getDB() 会重新打开并触发 onupgradeneeded(如果版本有变化)\n // 但如果 DB 已经是最新版本但 store 仍然缺失(连接陈旧),需要先关闭再重开\n try { db.close(); } catch (e) {}\n resetDBConnection();\n // 延迟重试,给 DB 重开一点时间\n setTimeout(() => {\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n }, 50);\n return;\n } else {\n settled = true;\n reject(new Error(`Store \"${storeName}\" does not exist in database (version ${db.version})`));\n return;\n }\n }\n\n let tx;\n try {\n tx = db.transaction(storeName, mode);\n } catch (e) {\n // transaction() 可能抛出同步异常(如连接已关闭)\n logger.error(`[IDB] create transaction failed (${storeName}):`, e.message);\n if (!isRetry) {\n settled = true;\n resetDBConnection();\n setTimeout(() => {\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n }, 50);\n return;\n } else {\n settled = true;\n reject(e);\n return;\n }\n }\n const store = tx.objectStore(storeName);\n\n // 回调中的 resolve 仅保存结果,实际 resolve 在 tx.oncomplete 中执行\n // 确保事务已提交后再返回,防止调用方读取到未持久化的数据\n callback(store, (result) => {\n resolvedValue = result;\n });\n\n tx.oncomplete = () => {\n if (!settled) {\n settled = true;\n resolve(resolvedValue);\n }\n };\n\n tx.onerror = (event) => {\n if (settled) return;\n settled = true;\n const err = event.target.error || new Error(`Transaction error on ${storeName}`);\n logger.error(`[IDB] transaction error (${storeName}):`, err);\n\n if (!isRetry) {\n // Service Worker 重启后连接可能已关闭,重置并重试一次\n resetDBConnection();\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n } else {\n reject(err);\n }\n };\n\n tx.onabort = () => {\n if (settled) return;\n settled = true;\n\n if (!isRetry) {\n resetDBConnection();\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n } else {\n reject(new Error(`Transaction aborted on ${storeName}`));\n }\n };\n });\n });\n}\n\n// ==================== Sessions CRUD ====================\n\n/**\n * 获取所有活跃会话\n */\nexport function getAllSessions() {\n return withStore('sessions', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 获取单个会话\n */\nexport function getSession(id) {\n return withStore('sessions', 'readonly', (store, resolve) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result || null);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 从会话的 messageHistory 中直接删除指定消息(不依赖 saveCurrentSession 的完整保存流程)\n * @param {string} sessionId\n * @param {string} messageId\n * @returns {Promise<boolean>}\n */\nexport async function deleteMessageFromSession(sessionId, messageId) {\n if (!sessionId || !messageId) return false;\n try {\n const session = await getSession(sessionId);\n if (!session || !session.messageHistory) return false;\n\n const idx = session.messageHistory.findIndex(\n msg => (msg.messageId || msg.id) === messageId\n );\n if (idx === -1) return false;\n\n session.messageHistory.splice(idx, 1);\n return await putSession(session);\n } catch (e) {\n logger.error('[DB] deleteMessageFromSession failed:', e);\n return false;\n }\n}\n\n/**\n * 保存/更新会话(put = insert or update)\n */\nexport function putSession(session) {\n return withStore('sessions', 'readwrite', (store, resolve) => {\n const request = store.put(session);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 删除会话\n */\nexport function deleteSession(id) {\n return withStore('sessions', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 批量保存/更新多个会话\n */\nexport function putSessions(sessions) {\n return getDB().then((db) => {\n return new Promise((resolve) => {\n const tx = db.transaction('sessions', 'readwrite');\n const store = tx.objectStore('sessions');\n sessions.forEach((s) => store.put(s));\n tx.oncomplete = () => resolve(true);\n tx.onerror = () => resolve(false);\n });\n });\n}\n\n// ==================== Active Session ID ====================\n\n/**\n * 获取当前活跃会话 ID\n */\nexport function getActiveSessionId() {\n return withStore('activeSession', 'readonly', (store, resolve) => {\n const request = store.get('active');\n request.onsuccess = () => resolve(request.result?.sessionId || null);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 设置当前活跃会话 ID\n */\nexport function setActiveSessionId(sessionId) {\n return withStore('activeSession', 'readwrite', (store, resolve) => {\n const request = store.put({ key: 'active', sessionId });\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n// ==================== Archived Sessions CRUD ====================\n\n/**\n * 获取所有归档会话\n */\nexport function getAllArchivedSessions() {\n return withStore('archivedSessions', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 保存归档会话\n */\nexport function putArchivedSession(session) {\n return withStore('archivedSessions', 'readwrite', (store, resolve) => {\n const request = store.put(session);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 删除归档会话\n */\nexport function deleteArchivedSession(id) {\n return withStore('archivedSessions', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 清空并批量写入归档会话(用于截断超出 20 条的场景)\n */\nexport function replaceArchivedSessions(sessions) {\n return getDB().then((db) => {\n return new Promise((resolve) => {\n const tx = db.transaction('archivedSessions', 'readwrite');\n const store = tx.objectStore('archivedSessions');\n\n // 清空\n const clearReq = store.clear();\n clearReq.onsuccess = () => {\n // 批量写入\n sessions.forEach((s) => store.put(s));\n };\n tx.oncomplete = () => resolve(true);\n tx.onerror = () => resolve(false);\n });\n });\n}\n\n// ==================== UI Prototypes CRUD ====================\n\n/**\n * 保存 UI 原型\n */\nexport function saveUiPrototype(prototype) {\n const now = Date.now();\n const data = {\n ...prototype,\n createdAt: prototype.createdAt || now,\n updatedAt: now\n };\n return withStore('uiPrototypes', 'readwrite', (store, resolve) => {\n const request = store.put(data);\n request.onsuccess = () => resolve(data);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 获取单个 UI 原型\n */\nexport function getUiPrototype(id) {\n return withStore('uiPrototypes', 'readonly', (store, resolve) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result || null);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 获取所有 UI 原型(按创建时间倒序)\n */\nexport function listUiPrototypes(sessionId = null) {\n return withStore('uiPrototypes', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => {\n let results = request.result || [];\n if (sessionId) {\n results = results.filter(p => p.sessionId === sessionId);\n }\n results.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));\n resolve(results);\n };\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 删除 UI 原型\n */\nexport function deleteUiPrototype(id) {\n return withStore('uiPrototypes', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n// ==================== 搜索 ====================\n\n/**\n * 从所有活跃会话中搜索消息\n * @param {string} sessionId - 限定会话 ID,null 表示搜索所有活跃会话\n * @returns {Promise<Array<{session: string, index: number, role: string, content: string}>>}\n */\nexport function searchActiveSessionsMessages(sessionId) {\n if (sessionId) {\n // 指定 sessionId 时使用 O(1) 主键查询,避免全量加载所有会话\n return getSession(sessionId).then((session) => {\n if (!session) return [];\n const label = session.title || t('db.sessionLabel', { id: session.id?.slice(0, 8) });\n return (session.messageHistory || []).filter(msg => msg.content).map((msg, idx) => ({\n session: session.id,\n sessionLabel: label,\n index: idx,\n role: msg.role,\n content: msg.content,\n }));\n });\n }\n return getAllSessions().then((all) => {\n const sessions = all;\n const results = [];\n sessions.forEach((session) => {\n const label = session.title || t('db.sessionLabel', { id: session.id?.slice(0, 8) });\n (session.messageHistory || []).forEach((msg, idx) => {\n if (msg.content) {\n results.push({\n session: session.id,\n sessionLabel: label,\n index: idx,\n role: msg.role,\n content: msg.content,\n });\n }\n });\n });\n return results;\n });\n}\n\n/**\n * 获取所有归档会话的消息\n * @returns {Promise<Array<{session: string, index: number, role: string, content: string}>>}\n */\nexport function getArchivedSessionsMessages() {\n return getAllArchivedSessions().then((archiveSessions) => {\n const results = [];\n archiveSessions.forEach((session) => {\n const label = t('db.archivedLabel', { label: session.title || t('db.sessionLabel', { id: session.id?.slice(0, 8) }) });\n (session.messages || []).forEach((msg, idx) => {\n if (msg.content) {\n results.push({\n session: session.id,\n sessionLabel: label,\n index: idx,\n role: msg.role,\n content: msg.content,\n });\n }\n });\n });\n return results;\n });\n}\n\n// ==================== 数据迁移 ====================\n\n/**\n * 从 chrome.storage.local 迁移数据到 IndexedDB\n * 迁移后清除 chrome.storage.local 中的大数据 key\n * @returns {Promise<boolean>}\n */\nexport function migrateFromChromeStorage() {\n return new Promise((resolve) => {\n chrome.storage.local.get(['sessions', 'conversationSessions'], async (result) => {\n let migrated = false;\n\n // 迁移活跃会话\n if (result.sessions?.list?.length > 0) {\n const { list, activeSessionId } = result.sessions;\n await putSessions(list);\n if (activeSessionId) {\n await setActiveSessionId(activeSessionId);\n }\n migrated = true;\n logger.debug('[IDB] migrated activesession:', list.length, '');\n }\n\n // 迁移归档会话\n if (result.conversationSessions?.sessions?.length > 0) {\n await replaceArchivedSessions(result.conversationSessions.sessions);\n migrated = true;\n logger.debug('[IDB] migrated archivesession:', result.conversationSessions.sessions.length, '');\n }\n\n if (migrated) {\n // 清除 chrome.storage 中的大数据 key,保留其他配置\n chrome.storage.local.remove(['sessions', 'conversationSessions', 'chatHistory'], () => {\n logger.debug('[IDB] cleaned chrome.storage.local olddata');\n });\n }\n\n resolve(migrated);\n });\n });\n}\n\n/**\n * 检查 IndexedDB 是否有数据,没有则尝试从 chrome.storage 迁移\n * @returns {Promise<void>}\n */\nexport async function ensureMigration() {\n const sessions = await getAllSessions();\n if (sessions.length > 0) return; // 已有数据\n\n const activeId = await getActiveSessionId();\n if (activeId) return; // 已有活跃会话标记\n\n // 尝试迁移\n await migrateFromChromeStorage();\n}\n\n// ==================== React Checkpoints CRUD ====================\n//\n// checkpoint 数据结构:\n// {\n// sessionId: string, // 主键\n// currentMessages: Array, // 完整的 API 消息数组(含 tool_calls/tool 消息对)\n// executionLog: Array, // 执行日志\n// iteration: number, // 当前迭代次数\n// globalIteration: { value }, // 全局迭代计数\n// subtaskPlan: Object|null, // 任务拆解计划\n// currentSubtaskIndex: number|null, // 当前子任务索引\n// totalReflectionRounds: number, // 反思轮数\n// model: string, // 使用的模型\n// apiParams: Object, // API 参数\n// taskContext: Object|null, // 任务上下文(子任务/子代理)\n// tabId: number|null, // 标签页 ID\n// callId: string|null, // 调用 ID\n// interruptedReason: string, // 中断原因\n// createdAt: number, // 创建时间戳\n// updatedAt: number, // 更新时间戳\n// }\n\n/**\n * 保存/更新 ReAct checkpoint\n * 采用双写策略:IndexedDB + chrome.storage.local(后备)\n * 当 IndexedDB 的 reactCheckpoints store 不存在或写入失败时,chrome.storage.local 确保数据不丢失\n * @param {Object} checkpoint - 必须包含 sessionId\n * @returns {Promise<boolean>}\n */\nexport async function saveReactCheckpoint(checkpoint) {\n if (!checkpoint?.sessionId) return false;\n const now = Date.now();\n const data = {\n ...checkpoint,\n updatedAt: now,\n createdAt: checkpoint.createdAt || now,\n };\n const key = CHECKPOINT_KEY_PREFIX + checkpoint.sessionId;\n\n // 策略:先写 chrome.storage.local(可靠),再尝试 IndexedDB(性能优化)\n // 这样即使 IndexedDB 完全不可用,checkpoint 也不会丢失\n let storageOk = false;\n try {\n await chrome.storage.local.set({ [key]: data });\n storageOk = true;\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local save checkpoint failed:', e.message);\n }\n\n // 同时写入 IndexedDB(如果可用),供 getAllReactCheckpoints 高效查询\n let idbOk = false;\n try {\n idbOk = await withStore('reactCheckpoints', 'readwrite', (store, resolve) => {\n const request = store.put(data);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n } catch (e) {\n // IndexedDB 不可用(store 不存在等),不影响主流程\n logger.warn('[Storage] IndexedDB save checkpoint failed (downgraded to chrome.storage.local):', e.message);\n }\n\n if (!storageOk && !idbOk) {\n logger.error('[Storage] checkpoint save complete failed:chrome.storage.local and IndexedDB averagenon- with ');\n return false;\n }\n return true;\n}\n\n/**\n * 获取指定会话的 ReAct checkpoint\n * 优先从 IndexedDB 读取,不存在时降级到 chrome.storage.local\n * @param {string} sessionId\n * @returns {Promise<Object|null>}\n */\nexport async function getReactCheckpoint(sessionId) {\n if (!sessionId) return null;\n\n // 优先尝试 IndexedDB\n try {\n const result = await withStore('reactCheckpoints', 'readonly', (store, resolve) => {\n const request = store.get(sessionId);\n request.onsuccess = () => resolve(request.result || null);\n request.onerror = () => resolve(null);\n });\n if (result) return result;\n } catch (e) {\n // IndexedDB 不可用,继续尝试 chrome.storage.local\n }\n\n // 降级:从 chrome.storage.local 读取\n try {\n const key = CHECKPOINT_KEY_PREFIX + sessionId;\n const data = await chrome.storage.local.get(key);\n return data[key] || null;\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local read checkpoint failed:', e.message);\n return null;\n }\n}\n\n/**\n * 删除指定会话的 ReAct checkpoint\n * 同时从 IndexedDB 和 chrome.storage.local 中删除\n * @param {string} sessionId\n * @returns {Promise<boolean>}\n */\nexport async function deleteReactCheckpoint(sessionId) {\n if (!sessionId) return false;\n let ok = false;\n\n // 从 chrome.storage.local 删除\n try {\n const key = CHECKPOINT_KEY_PREFIX + sessionId;\n await chrome.storage.local.remove(key);\n ok = true;\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local delete checkpoint failed:', e.message);\n }\n\n // 从 IndexedDB 删除\n try {\n await withStore('reactCheckpoints', 'readwrite', (store, resolve) => {\n const request = store.delete(sessionId);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n ok = true;\n } catch (e) {\n // IndexedDB 不可用,不影响\n }\n\n return ok;\n}\n\n/**\n * 获取所有 ReAct checkpoint(用于 TTL 清理和诊断)\n * 合并 IndexedDB 和 chrome.storage.local 中的结果\n * @returns {Promise<Array>}\n */\nexport async function getAllReactCheckpoints() {\n // 从 chrome.storage.local 获取所有 checkpoint\n let storageCheckpoints = [];\n try {\n const all = await chrome.storage.local.get(null);\n for (const [key, value] of Object.entries(all)) {\n if (key.startsWith(CHECKPOINT_KEY_PREFIX) && value?.sessionId) {\n storageCheckpoints.push(value);\n }\n }\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local get all checkpoint failed:', e.message);\n }\n\n // 从 IndexedDB 获取\n let idbCheckpoints = [];\n try {\n idbCheckpoints = await withStore('reactCheckpoints', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n } catch (e) {\n // IndexedDB 不可用\n }\n\n // 合并去重(以 sessionId 为键,IndexedDB 优先)\n const merged = new Map();\n for (const cp of storageCheckpoints) {\n merged.set(cp.sessionId, cp);\n }\n for (const cp of idbCheckpoints) {\n merged.set(cp.sessionId, cp);\n }\n return Array.from(merged.values());\n}\n\n/**\n * 清理过期的 ReAct checkpoint(TTL: 7 天)\n * 应在 SW 启动时调用\n * @returns {Promise<number>} 清理的条数\n */\nexport async function cleanupExpiredReactCheckpoints() {\n const all = await getAllReactCheckpoints();\n const now = Date.now();\n const expired = all.filter(c => (c.updatedAt || 0) < now - REACT_CHECKPOINT_TTL_MS);\n if (expired.length === 0) return 0;\n\n let deleted = 0;\n for (const cp of expired) {\n const ok = await deleteReactCheckpoint(cp.sessionId);\n if (ok) deleted++;\n }\n logger.debug(`[IDB] cleanup ${deleted} expired ReAct checkpoint`);\n return deleted;\n}\n\n// ==================== Bookmarks CRUD ====================\n\n/**\n * 获取所有收藏\n * @returns {Promise<Array>}\n */\nexport function getAllBookmarks() {\n return withStore('bookmarks', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 保存收藏\n * @param {Object} bookmark - { id, sessionId, messageId, content, sessionTitle, createdAt, pinned }\n * @returns {Promise<boolean>}\n */\nexport function putBookmark(bookmark) {\n return withStore('bookmarks', 'readwrite', (store, resolve) => {\n const request = store.put(bookmark);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 删除收藏\n * @param {string} id\n * @returns {Promise<boolean>}\n */\nexport function deleteBookmark(id) {\n return withStore('bookmarks', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 更新收藏的置顶状态\n * @param {string} id\n * @param {boolean} pinned\n * @returns {Promise<boolean>}\n */\nexport function updateBookmarkPin(id, pinned) {\n return withStore('bookmarks', 'readwrite', (store, resolve) => {\n const getReq = store.get(id);\n getReq.onsuccess = () => {\n const bm = getReq.result;\n if (!bm) { resolve(false); return; }\n bm.pinned = pinned;\n const putReq = store.put(bm);\n putReq.onsuccess = () => resolve(true);\n putReq.onerror = () => resolve(false);\n };\n getReq.onerror = () => resolve(false);\n });\n}\n","// storage/token-store.js - Token 使用统计的 IndexedDB CRUD\n\nimport { getDB, withStore } from './db.js';\n\nconst STORE_NAME = 'tokenStats';\n\n// 全局汇总存储 key\nconst OVERALL_KEY = 'overall';\n\n// 系统级会话 ID:不计入\"总会话数\",但其 Token 消耗仍计入总量\nconst SYSTEM_SESSION_IDS = new Set(['selection_toolbar']);\n\n/**\n * 记录单次 API 调用的 token 使用\n * @param {Object} record\n * @param {string} record.sessionId\n * @param {string} record.model\n * @param {number} record.promptTokens\n * @param {number} record.completionTokens\n * @param {number} record.totalTokens\n * @param {number} record.contextWindow\n * @param {string} record.callType - 'react_loop' | 'non_stream' | 'reflection' | 'tool_reflection' | 'subtask_reflection'\n */\nexport function recordTokenCall(record) {\n const entry = {\n id: `tcr_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,\n sessionId: record.sessionId,\n timestamp: new Date().toISOString(),\n model: record.model || 'unknown',\n promptTokens: record.promptTokens || 0,\n completionTokens: record.completionTokens || 0,\n totalTokens: record.totalTokens || 0,\n contextWindow: record.contextWindow || 128000,\n contextUsageRate: record.contextWindow > 0\n ? ((record.promptTokens || 0) / record.contextWindow)\n : 0,\n callType: record.callType || 'unknown'\n };\n\n return withStore(STORE_NAME, 'readwrite', (store, resolve) => {\n const req = store.put(entry);\n req.onsuccess = () => resolve(true);\n req.onerror = () => resolve(false);\n });\n}\n\n/**\n * 获取某个会话的 token 汇总\n * @param {string} sessionId\n * @returns {Promise<Object>}\n */\nexport function getSessionTokenSummary(sessionId) {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = (req.result || []).filter(r => \n r.sessionId === sessionId || \n (sessionId && r.sessionId?.startsWith(sessionId + '_'))\n );\n if (records.length === 0) {\n resolve({\n sessionId,\n totalPromptTokens: 0,\n totalCompletionTokens: 0,\n totalTokens: 0,\n apiCallCount: 0,\n avgContextUsageRate: 0,\n maxContextUsageRate: 0,\n minContextUsageRate: 0,\n records: []\n });\n return;\n }\n\n const usageRates = records.map(r => r.contextUsageRate || 0).filter(r => r > 0);\n resolve({\n sessionId,\n totalPromptTokens: records.reduce((s, r) => s + (r.promptTokens || 0), 0),\n totalCompletionTokens: records.reduce((s, r) => s + (r.completionTokens || 0), 0),\n totalTokens: records.reduce((s, r) => s + (r.totalTokens || 0), 0),\n apiCallCount: records.length,\n avgContextUsageRate: usageRates.length > 0\n ? usageRates.reduce((s, r) => s + r, 0) / usageRates.length\n : 0,\n maxContextUsageRate: usageRates.length > 0 ? Math.max(...usageRates) : 0,\n minContextUsageRate: usageRates.length > 0 ? Math.min(...usageRates) : 0,\n records: records.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)).slice(0, 20)\n });\n };\n req.onerror = () => resolve(null);\n });\n}\n\n/**\n * 获取全局 token 汇总\n * @returns {Promise<Object>}\n */\nexport function getOverallTokenSummary() {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n if (records.length === 0) {\n resolve({\n totalPromptTokens: 0,\n totalCompletionTokens: 0,\n totalTokens: 0,\n totalApiCalls: 0,\n totalSessions: 0,\n lastUpdated: null\n });\n return;\n }\n\n const sessionIds = new Set(\n records.map(r => r.sessionId).filter(id => id && !SYSTEM_SESSION_IDS.has(id))\n );\n resolve({\n totalPromptTokens: records.reduce((s, r) => s + (r.promptTokens || 0), 0),\n totalCompletionTokens: records.reduce((s, r) => s + (r.completionTokens || 0), 0),\n totalTokens: records.reduce((s, r) => s + (r.totalTokens || 0), 0),\n totalApiCalls: records.length,\n totalSessions: sessionIds.size,\n lastUpdated: records.reduce((latest, r) => {\n return r.timestamp > latest ? r.timestamp : latest;\n }, '')\n });\n };\n req.onerror = () => resolve(null);\n });\n}\n\n/**\n * 清除某个会话的 token 统计\n * @param {string} sessionId\n */\nexport function clearSessionTokenStats(sessionId) {\n return withStore(STORE_NAME, 'readwrite', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n const toDelete = records.filter(r => r.sessionId === sessionId);\n let deleted = 0;\n toDelete.forEach(r => {\n store.delete(r.id);\n deleted++;\n });\n resolve(deleted);\n };\n req.onerror = () => resolve(0);\n });\n}\n\n/**\n * 清除所有 token 统计\n */\nexport function clearAllTokenStats() {\n return withStore(STORE_NAME, 'readwrite', (store, resolve) => {\n const req = store.clear();\n req.onsuccess = () => resolve(true);\n req.onerror = () => resolve(false);\n });\n}\n\n/**\n * 获取最近 N 条 token 记录(全局)\n * @param {number} limit\n */\nexport function getRecentTokenRecords(limit = 20) {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n records.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));\n resolve(records.slice(0, limit));\n };\n req.onerror = () => resolve([]);\n });\n}\n","// shared/agent-defaults.js - 内置 Agent 定义和模板\n\n/**\n * 内置默认 Agent(不可删除)\n */\nexport const BUILTIN_AGENTS = [\n {\n id: 'default',\n name: 'Default Assistant',\n description: 'All-purpose AI assistant with all tool capabilities',\n icon: '🤖',\n systemPrompt: null, // null = 使用全局 systemPrompt\n toolIds: null, // null = 使用全局 enabledTools\n skillIds: null, // null = 使用全部启用技能\n isBuiltin: true,\n allowSubDispatch: false,\n model: null,\n temperature: null,\n topP: null,\n }\n];\n\n/**\n * Agent 模板(供用户快速创建参考)\n */\nexport const AGENT_TEMPLATES = [\n {\n name: 'Code Review Expert',\n icon: '🔍',\n description: 'Focused on code review and quality assurance',\n systemPrompt: `You are a senior Code Review Expert. Your responsibilities:\n1. Review code for logical correctness, performance, and security\n2. Check code style, naming conventions, and best practices\n3. Identify potential bugs, memory leaks, and concurrency issues\n4. Provide actionable improvement suggestions with example code\n\nResponse principles:\n- Always indicate severity (critical/moderate/suggestion)\n- Provide specific code modification suggestions, avoid vague comments\n- Focus on maintainability, not just functional correctness`,\n systemPromptZh: `你是一位资深代码审查专家。你的职责:\n1. 审查代码的逻辑正确性、性能和安全性\n2. 检查代码风格、命名规范和最佳实践\n3. 识别潜在的 Bug、内存泄漏和并发问题\n4. 提供可操作的改进建议,并附带示例代码\n\n回复原则:\n- 始终标注严重程度(严重/中等/建议)\n- 提供具体的代码修改建议,避免模糊评论\n- 关注可维护性,而非仅关注功能正确性`,\n toolIds: ['page_content', 'search_browser_data', 'agent_file', 'agent_search', 'search_chats', 'fetch_url', 'plan_task'],\n allowSubDispatch: false,\n },\n {\n name: 'Web Automation Assistant',\n icon: '🌐',\n description: 'Focused on web interaction and automation',\n systemPrompt: `You are a web automation expert. Your strengths:\n1. Automate web page interactions based on user needs (clicking, form filling, scrolling, etc.)\n2. Extract and analyze web page content\n3. Handle multi-step web interaction flows\n\nOperating principles:\n- Understand page structure before acting; prefer query_elements first\n- Verify results after actions to ensure they take effect\n- Analyze errors and try alternative approaches\n- Never assume elements exist; fetch page info first when uncertain\n- For dialogs (alert/confirm/prompt), call handle_dialog BEFORE the action that triggers them, then use interact_element to proceed`,\n systemPromptZh: `你是一位网页自动化专家。你的优势:\n1. 根据用户需求自动化网页交互(点击、表单填写、滚动等)\n2. 提取和分析网页内容\n3. 处理多步骤的网页交互流程\n\n操作原则:\n- 行动前先理解页面结构,优先使用 query_elements\n- 操作后验证结果,确保生效\n- 分析错误并尝试替代方案\n- 不要假设元素存在,不确定时先获取页面信息\n- 涉及 alert/confirm/prompt 弹窗时,先在触发弹窗的操作前调用 handle_dialog,再通过 interact_element 继续`,\n toolIds: ['page_content', 'query_elements', 'interact_element', 'fill_form', 'scroll_to', 'wait_element', 'keyboard_input', 'select_dropdown', 'capture_page', 'extract_data', 'search_in_page', 'wait_navigation', 'scroll_collect', 'drag_drop', 'file_upload', 'iframe_content', 'manage_tab', 'handle_dialog'],\n allowSubDispatch: false,\n },\n {\n name: 'Data Analyst',\n icon: '📊',\n description: 'Focused on data extraction, analysis, and visualization',\n systemPrompt: `You are a data analyst. Your strengths:\n1. Extract structured data from web pages (tables, lists, JSON)\n2. Analyze and summarize data patterns\n3. Present analysis results in clear formats\n\nAnalysis principles:\n- Understand data structure before starting analysis\n- Use the most appropriate extraction method (tables → extract_data with dataType=table; links/images/forms → extract_data with dataType=links/images/forms; text/HTML → page_content with format=text/html)\n- Support analysis results with data; avoid subjective assumptions\n- Present conclusions using tables or charts`,\n systemPromptZh: `你是一位数据分析师。你的优势:\n1. 从网页中提取结构化数据(表格、列表、JSON)\n2. 分析和总结数据模式\n3. 以清晰格式呈现分析结果\n\n分析原则:\n- 开始分析前先理解数据结构\n- 使用最合适的提取方法(表格使用 extract_data 配合 dataType=table;链接/图片/表单等结构化数据使用 extract_data 配合 dataType=links/images/forms;纯文本/HTML 使用 page_content 配合 format=text/html)\n- 用数据支撑分析结果,避免主观臆断\n- 使用表格或图表呈现结论`,\n toolIds: ['page_content', 'extract_data', 'query_elements', 'search_in_page', 'scroll_collect', 'fetch_url', 'iframe_content', 'clipboard'],\n allowSubDispatch: false,\n },\n {\n name: 'Documentation Assistant',\n icon: '📝',\n description: 'Focused on technical documentation and content organization',\n systemPrompt: `You are a technical documentation expert. Your strengths:\n1. Write clear technical documentation (API docs, READMEs, user guides)\n2. Distill and summarize technical information\n3. Transform complex concepts into easy-to-understand documentation\n\nWriting principles:\n- Clear structure: Use headings, lists, and tables to organize content\n- Examples first: Key concepts must include code or configuration examples\n- Reader-oriented: Adjust technical depth based on target audience\n- Stay concise: Avoid redundancy; every paragraph should carry information`,\n systemPromptZh: `你是一位技术文档专家。你的优势:\n1. 编写清晰的技术文档(API 文档、README、用户指南)\n2. 提炼和总结技术信息\n3. 将复杂概念转化为易于理解的文档\n\n编写原则:\n- 结构清晰:使用标题、列表和表格组织内容\n- 示例先行:关键概念必须附带代码或配置示例\n- 面向读者:根据目标受众调整技术深度\n- 保持简洁:避免冗余,每段文字都应承载信息`,\n toolIds: ['page_content', 'clipboard', 'search_browser_data', 'search_chats', 'fetch_url', 'agent_file', 'agent_search', 'extract_data', 'capture_page', 'search_in_page'],\n allowSubDispatch: false,\n },\n];\n\n/**\n * 生成新的 Agent ID\n */\nexport function generateAgentId() {\n return 'agent_' + Date.now().toString(36) + '_' + Math.random().toString(36).substring(2, 6);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agent-defaults-Ct1YxFzI.js","names":[],"sources":["../../src/shared/token-counter.js","../../src/storage/db.js","../../src/storage/token-store.js","../../src/shared/agent-defaults.js"],"sourcesContent":["import logger from './logger.js';\nimport { t, registerTranslations } from './i18n.js';\n\n// shared/token-counter.js - Token 估算工具\n// 使用字符数估算 token 数,无需引入 tiktoken 等重量级依赖\n\nregisterTranslations('zh', {\n tokenCounter: {\n truncatedMiddle: '\\n\\n... [中间 {tokens} tokens 已截断] ...\\n\\n',\n omittedTokens: '\\n\\n... [省略 {tokens} tokens] ...\\n\\n',\n omittedMiddleHtml: '\\n<!-- 省略中间内容 -->\\n',\n truncated: '[截断]',\n omittedKey: '...[省略]',\n moreFields: '还有 {count} 个字段',\n unknownTool: '未知工具',\n userQuestion: '- 用户问题:{question}',\n usedTools: '- 使用的工具:{tools}',\n historySummary: '[历史摘要]',\n },\n});\n\nregisterTranslations('en', {\n tokenCounter: {\n truncatedMiddle: '\\n\\n... [middle {tokens} tokens truncated] ...\\n\\n',\n omittedTokens: '\\n\\n... [{tokens} tokens omitted] ...\\n\\n',\n omittedMiddleHtml: '\\n<!-- middle content omitted -->\\n',\n truncated: '[truncated]',\n omittedKey: '...[omitted]',\n moreFields: '{count} more fields',\n unknownTool: 'Unknown tool',\n userQuestion: '- User question: {question}',\n usedTools: '- Tools used: {tools}',\n historySummary: '[History Summary]',\n },\n});\n\n// 估算常量:中文约 1.5 字符/token,英文约 4 字符/token\n// 参考 DeepSeek tokenizer 的行为特征\nconst CHINESE_CHAR_REGEX = /[\\u4e00-\\u9fff\\u3400-\\u4dbf\\uf900-\\ufaff]/g;\nconst CHARS_PER_TOKEN_CN = 1.5;\nconst CHARS_PER_TOKEN_EN = 4;\n\n// 每条消息的 role + 结构开销(约 4 tokens)\nconst MESSAGE_OVERHEAD = 4;\n\n// ============================================================\n// 图片 Token 估算(参考 OpenAI Vision 高分辨率模式)\n// 多数多模态模型(OpenAI/Claude/DeepSeek VL 等)的图片 token 与分辨率相关,\n// 而非 Base64 字符串长度。按 Base64 字符串估算会严重高估(约 20 倍)。\n// 公式:tiles = ceil(w/512) * ceil(h/512),tokens = 85 + 170 * tiles\n// ============================================================\nconst IMAGE_BASE_TOKENS = 85;\nconst IMAGE_TOKENS_PER_TILE = 170;\nconst IMAGE_TILE_SIZE = 512;\n\n/**\n * 估算单张图片的 token 数量\n * 优先用压缩后尺寸(width/height)按 tile 公式计算;\n * 缺失尺寸时按 Base64 字节大小兜底估算(经验值:JPEG 约 65 bytes/token)\n * @param {{ url?: string, width?: number, height?: number }} imageUrlObj\n * @returns {number}\n */\nfunction estimateImageTokens(imageUrlObj) {\n if (!imageUrlObj) return IMAGE_BASE_TOKENS;\n\n const w = Number(imageUrlObj.width);\n const h = Number(imageUrlObj.height);\n if (w > 0 && h > 0) {\n const tiles = Math.ceil(w / IMAGE_TILE_SIZE) * Math.ceil(h / IMAGE_TILE_SIZE);\n return IMAGE_BASE_TOKENS + IMAGE_TOKENS_PER_TILE * tiles;\n }\n\n // 兜底:按 Base64 字节估算(无尺寸信息,如旧数据)\n // 50KB JPEG ≈ 765 tokens(OpenAI),故 1 token ≈ 65 bytes\n const url = imageUrlObj.url || '';\n const base64Data = url.split(',')[1] || url;\n const bytes = (base64Data.length * 3) / 4; // Base64 每 4 字符 ≈ 3 字节\n return Math.max(IMAGE_BASE_TOKENS, Math.ceil(bytes / 65));\n}\n\n// ============================================================\n// 实时校准:基于 API 实际返回的 prompt_tokens 修正估算偏差\n// 使用指数加权移动平均(EWMA)平滑,避免单次异常波动\n// ============================================================\nlet calibrationFactor = 1.0;\nlet calibrationSamples = 0;\nconst CALIBRATION_MIN_SAMPLES = 3; // 至少 3 次采样后才启用校准\nconst CALIBRATION_RATIO_MIN = 0.3; // 异常比例下限\nconst CALIBRATION_RATIO_MAX = 3.0; // 异常比例上限\n\n/**\n * 更新 token 估算校准因子\n * 在每次 API 调用完成后,用 API 返回的实际 prompt_tokens 与我们的估算值对比\n * @param {number} estimated - 我们的估算值(不含输出预留)\n * @param {number} actual - API 返回的 prompt_tokens\n */\nexport function updateCalibration(estimated, actual) {\n if (!actual || actual <= 0 || !estimated || estimated <= 0) return;\n\n const ratio = actual / estimated;\n // 过滤异常比例(如估算或实际值明显错误)\n if (ratio < CALIBRATION_RATIO_MIN || ratio > CALIBRATION_RATIO_MAX) {\n logger.warn(`[TokenCounter] calibration ratioexception,ignore: estimated=${estimated}, actual=${actual}, ratio=${ratio.toFixed(3)}`);\n return;\n }\n\n calibrationSamples++;\n // EWMA:样本少时权重高(快速收敛),样本多时权重低(稳定)\n const alpha = Math.min(0.3, 1 / calibrationSamples);\n const oldFactor = calibrationFactor;\n calibrationFactor = calibrationFactor * (1 - alpha) + ratio * alpha;\n\n logger.debug(`[TokenCounter] Tokencalibration update: ratio=${ratio.toFixed(3)}, alpha=${alpha.toFixed(3)}, old=${oldFactor.toFixed(3)} → new=${calibrationFactor.toFixed(3)}, samples=${calibrationSamples}`);\n}\n\n/**\n * 获取校准后的 token 估算\n * 样本不足时不校准(返回原始估算值)\n * @param {number} rawEstimate - 原始估算值\n * @returns {number}\n */\nexport function getCalibratedTokens(rawEstimate) {\n if (calibrationSamples < CALIBRATION_MIN_SAMPLES || calibrationFactor <= 0) {\n return rawEstimate;\n }\n return Math.ceil(rawEstimate * calibrationFactor);\n}\n\n/**\n * 获取当前校准状态信息(用于诊断/日志)\n * @returns {{ factor: number, samples: number, active: boolean }}\n */\nexport function getCalibrationInfo() {\n return {\n factor: calibrationFactor,\n samples: calibrationSamples,\n active: calibrationSamples >= CALIBRATION_MIN_SAMPLES\n };\n}\n\n/**\n * 估算单段文本的 token 数量\n * @param {string} text\n * @returns {number}\n */\nexport function estimateTokens(text) {\n if (!text || typeof text !== 'string') return 0;\n const chineseMatches = text.match(CHINESE_CHAR_REGEX);\n const chineseChars = chineseMatches ? chineseMatches.length : 0;\n const otherChars = text.length - chineseChars;\n return Math.ceil(chineseChars / CHARS_PER_TOKEN_CN + otherChars / CHARS_PER_TOKEN_EN);\n}\n\n/**\n * 估算消息数组的总 token 数\n * 支持 multipart content(文本 + 图片):图片按分辨率估算,避免 Base64 字符串高估\n * @param {Array<{role: string, content: string|Array, tool_calls?: Array, tool_call_id?: string, reasoning_content?: string}>} messages\n * @returns {number}\n */\nexport function estimateMessagesTokens(messages) {\n if (!messages || messages.length === 0) return 0;\n return messages.reduce((sum, m) => {\n let tokens = 0;\n if (typeof m.content === 'string') {\n tokens = estimateTokens(m.content);\n } else if (Array.isArray(m.content)) {\n // 多模态 content:分别按 part 类型估算\n for (const part of m.content) {\n if (!part) continue;\n if (part.type === 'text') {\n tokens += estimateTokens(part.text || '');\n } else if (part.type === 'image_url' && part.image_url) {\n // 图片按分辨率估算,不把 Base64 字符串计入\n tokens += estimateImageTokens(part.image_url);\n } else {\n tokens += estimateTokens(JSON.stringify(part));\n }\n }\n } else if (m.content) {\n tokens = estimateTokens(JSON.stringify(m.content));\n }\n\n // 附加字段 token\n let extra = '';\n if (m.tool_calls) extra += JSON.stringify(m.tool_calls);\n if (m.tool_call_id) extra += m.tool_call_id;\n if (m.reasoning_content) extra += m.reasoning_content;\n if (extra) tokens += estimateTokens(extra);\n\n return sum + tokens + MESSAGE_OVERHEAD;\n }, 0);\n}\n\n/**\n * 按 token 上限截断文本,保留头部和尾部\n * @param {string} content - 原始文本\n * @param {number} maxTokens - 最大 token 数\n * @returns {string}\n */\nexport function truncateByTokens(content, maxTokens) {\n if (!content || typeof content !== 'string') return content;\n const currentTokens = estimateTokens(content);\n if (currentTokens <= maxTokens) return content;\n\n // 保留头部 70%,尾部 30%\n const headTokens = Math.floor(maxTokens * 0.7);\n const tailTokens = maxTokens - headTokens;\n\n const headChars = Math.floor(headTokens * CHARS_PER_TOKEN_EN);\n const tailChars = Math.floor(tailTokens * CHARS_PER_TOKEN_EN);\n\n const head = content.slice(0, headChars);\n const tail = content.slice(-tailChars);\n\n const truncatedTokens = currentTokens - maxTokens;\n return head + t('tokenCounter.truncatedMiddle', { tokens: truncatedTokens }) + tail;\n}\n\n/**\n * 智能工具结果截断:根据内容类型采用不同的保留策略\n * - HTML: 优先保留 <body> 内容\n * - JSON: 保留顶层 key 结构,值摘要\n * - 代码: 保留头部 + 关键结构 + 尾部\n * - 纯文本: 固定 60% / 20% 头尾截断\n * @param {string} content - 原始内容\n * @param {number} maxTokens - 最大 token 数\n * @param {string} [contentType] - 内容类型提示: 'html' | 'json' | 'code' | 'text'\n * @returns {string}\n */\nexport function truncateContentSmart(content, maxTokens, contentType) {\n if (!content || typeof content !== 'string') return content;\n if (estimateTokens(content) <= maxTokens) return content;\n\n // 自动检测类型\n if (!contentType) {\n const trimmed = content.trim();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n contentType = 'json';\n } else if (/<(!DOCTYPE|html|head|body)[\\s>]/i.test(trimmed.substring(0, 200))) {\n contentType = 'html';\n } else {\n contentType = 'text';\n }\n }\n\n if (contentType === 'html') {\n return truncateHtmlSmart(content, maxTokens);\n } else if (contentType === 'json') {\n return truncateJsonSmart(content, maxTokens);\n }\n // text/code: 保留前 60% 字符 + 后 20% 字符\n const headChars = Math.floor(maxTokens * 0.6 * CHARS_PER_TOKEN_EN);\n const tailChars = Math.floor(maxTokens * 0.2 * CHARS_PER_TOKEN_EN);\n const head = content.slice(0, headChars);\n const tail = content.slice(-tailChars);\n return head + t('tokenCounter.omittedTokens', { tokens: estimateTokens(content) - maxTokens }) + tail;\n}\n\n/**\n * HTML 智能截断:优先保留 <body> 内容\n */\nfunction truncateHtmlSmart(content, maxTokens) {\n const bodyMatch = content.match(/<body[\\s>][\\s\\S]*<\\/body>/i);\n if (bodyMatch) {\n const bodyContent = bodyMatch[0];\n const headContent = content.substring(0, content.indexOf(bodyContent));\n // body 内容占 80%,头部占 20%\n const bodyBudget = Math.floor(maxTokens * 0.8);\n const headBudget = maxTokens - bodyBudget;\n const headChars = Math.floor(headBudget * CHARS_PER_TOKEN_EN);\n const bodyChars = Math.floor(bodyBudget * CHARS_PER_TOKEN_EN);\n return content.slice(0, headChars) +\n bodyContent.slice(0, bodyChars) +\n t('tokenCounter.omittedMiddleHtml') +\n bodyContent.slice(-Math.floor(bodyChars * 0.3)) +\n content.slice(-Math.floor(headChars * 0.3));\n }\n // 无 body 标签,fallback 到通用截断\n return truncateByTokens(content, maxTokens);\n}\n\n/**\n * JSON 智能截断:保留顶层 key 结构,值摘要\n */\nfunction truncateJsonSmart(content, maxTokens) {\n try {\n const obj = JSON.parse(content);\n if (typeof obj !== 'object' || obj === null) {\n return truncateByTokens(content, maxTokens);\n }\n\n const summarized = {};\n const keys = Object.keys(obj);\n const maxKeys = Math.min(keys.length, 30);\n\n for (let i = 0; i < maxKeys; i++) {\n const key = keys[i];\n const val = obj[key];\n if (typeof val === 'string' && val.length > 200) {\n summarized[key] = val.substring(0, 200) + '...' + t('tokenCounter.truncated');\n } else if (typeof val === 'object' && val !== null) {\n summarized[key] = `[${Array.isArray(val) ? `Array(${val.length})` : `Object(${Object.keys(val).length} keys)`}]`;\n } else {\n summarized[key] = val;\n }\n }\n\n if (keys.length > maxKeys) {\n summarized['...[省略]'] = `还有 ${keys.length - maxKeys} 个字段`;\n }\n\n let result = JSON.stringify(summarized, null, 2);\n // 如果摘要后仍超预算,做最终字符截断\n if (estimateTokens(result) > maxTokens) {\n result = result.substring(0, Math.floor(maxTokens * CHARS_PER_TOKEN_EN));\n }\n return result;\n } catch {\n return truncateByTokens(content, maxTokens);\n }\n}\n\n/**\n * 模型上下文窗口配置(单位:tokens)\n * 作为内置默认值,可通过添加自定义模型时设置上下文窗口来覆盖\n */\nexport const MODEL_CONTEXT_WINDOWS = {\n 'deepseek-v4-pro': 128000,\n 'deepseek-v3': 128000,\n 'deepseek-chat': 64000,\n 'deepseek-reasoner': 64000,\n 'gpt-4o': 128000,\n 'gpt-4o-mini': 128000,\n 'gpt-4-turbo': 128000,\n 'gpt-4': 8192,\n 'gpt-3.5-turbo': 16385,\n 'claude-3.5-sonnet': 200000,\n 'claude-3-opus': 200000,\n 'claude-3-haiku': 200000,\n 'claude-3-sonnet': 200000,\n default: 64000\n};\n\n/**\n * 标准化自定义模型配置数组,向前兼容旧格式(string[] → object[])\n * @param {Array<string|{name: string, contextWindow?: number}>} customModels\n * @returns {Map<string, number>} modelName → contextWindow 的映射\n */\nexport function normalizeCustomModels(customModels) {\n const map = new Map();\n if (!customModels || !Array.isArray(customModels)) return map;\n for (const item of customModels) {\n if (typeof item === 'string') {\n // 旧格式:仅模型名,上下文窗口未知,不加入 map(回退到内置映射)\n continue;\n }\n if (item && typeof item === 'object' && item.name) {\n map.set(item.name, item.contextWindow || 0);\n }\n }\n return map;\n}\n\n/**\n * 获取模型上下文窗口大小\n * 优先级:自定义模型配置 > 用户全局配置 > 内置映射 > default\n * @param {string} modelName\n * @param {number} [userConfiguredWindow] - 用户手动配置的上下文窗口(全局覆盖)\n * @param {Map<string, number>} [customModelMap] - 自定义模型的上下文窗口映射\n * @returns {number}\n */\nexport function getContextWindow(modelName, userConfiguredWindow, customModelMap) {\n // 1. 优先使用自定义模型中该模型单独配置的上下文窗口\n if (customModelMap && customModelMap.has(modelName)) {\n const window = customModelMap.get(modelName);\n if (window && window > 0) return window;\n }\n // 2. 全局上下文窗口覆盖\n if (userConfiguredWindow && userConfiguredWindow > 0) return userConfiguredWindow;\n // 3. 内置模型映射\n return MODEL_CONTEXT_WINDOWS[modelName] || MODEL_CONTEXT_WINDOWS.default;\n}\n\n// ============================================================\n// 上下文预算分配\n// ============================================================\n\n// 系统提示词预留 token 数\nexport const SYSTEM_PROMPT_BUDGET = 2000;\n\n// 工具定义预留 token 数(工具数量 * 平均每个工具定义 ~200 tokens)\nexport function estimateToolsTokens(toolCount) {\n return toolCount * 200;\n}\n\n// 输出预留 token 数(给模型生成回答的空间)\nexport const OUTPUT_BUDGET = 4096;\n\n/**\n * 计算可用于消息历史的 token 预算\n * @param {string} modelName\n * @param {number} toolCount\n * @param {number} [userConfiguredWindow]\n * @param {Map<string, number>} [customModelMap] - 自定义模型的上下文窗口映射\n * @returns {number}\n */\nexport function getMessageBudget(modelName, toolCount = 0, userConfiguredWindow, customModelMap) {\n const contextWindow = getContextWindow(modelName, userConfiguredWindow, customModelMap);\n return contextWindow - SYSTEM_PROMPT_BUDGET - estimateToolsTokens(toolCount) - OUTPUT_BUDGET;\n}\n\n/**\n * 评估上下文压力等级\n * @param {number} usedTokens\n * @param {number} budget\n * @returns {{ level: 'safe'|'warning'|'critical', ratio: number }}\n */\nexport function assessContextPressure(usedTokens, budget) {\n const ratio = usedTokens / budget;\n if (ratio < 0.7) return { level: 'safe', ratio };\n if (ratio < 0.9) return { level: 'warning', ratio };\n return { level: 'critical', ratio };\n}\n\n/**\n * 基于 token 预算裁剪历史消息(从旧到新移除,确保 tool_calls/tool 配对)\n * 返回裁剪后的消息数组 + 被裁剪掉的消息摘要信息\n * @param {Array} messages - 包含 system message 的完整消息数组\n * @param {number} budget - token 预算上限\n * @param {Object} [options]\n * @param {boolean} [options.preserveSystem=true] - 是否保留 system message\n * @param {boolean} [options.generateSummary=true] - 是否生成被裁剪消息的摘要\n * @returns {{ messages: Array, trimmedCount: number, summary: string|null }}\n */\nexport function trimMessagesByBudget(messages, budget, options = {}) {\n const { preserveSystem = true, generateSummary = true } = options;\n const originalLen = messages.length;\n\n if (estimateMessagesTokens(messages) <= budget) {\n return { messages: [...messages], trimmedCount: 0, summary: null };\n }\n\n const systemMsg = preserveSystem && messages[0]?.role === 'system' ? [messages[0]] : [];\n const rest = systemMsg.length ? [...messages.slice(1)] : [...messages];\n\n const trimmedMessages = [];\n\n while (rest.length > 0) {\n const currentTokens = estimateMessagesTokens([...systemMsg, ...rest]);\n if (currentTokens <= budget) break;\n\n const removed = rest.shift();\n trimmedMessages.push(removed);\n\n // 如果移除的是 assistant(tool_calls),则后续的 tool 消息也要一并移除\n if (removed?.role === 'assistant' && removed.tool_calls) {\n while (rest.length > 0 && rest[0]?.role === 'tool') {\n trimmedMessages.push(rest.shift());\n }\n }\n }\n\n const result = [...systemMsg, ...rest];\n const summary = generateSummary ? generateMessagesSummary(trimmedMessages) : null;\n\n return {\n messages: result,\n trimmedCount: originalLen - result.length,\n summary\n };\n}\n\n/**\n * 从被裁剪的消息中生成规则式结构化摘要\n * @param {Array} trimmedMessages - 被裁剪的消息\n * @returns {string|null}\n */\nexport function generateMessagesSummary(trimmedMessages) {\n if (!trimmedMessages || trimmedMessages.length === 0) return null;\n\n const summaryParts = [];\n const userQuestions = [];\n const toolCalls = [];\n\n for (const msg of trimmedMessages) {\n if (msg.role === 'user') {\n const text = typeof msg.content === 'string'\n ? msg.content\n : (Array.isArray(msg.content)\n ? msg.content.filter(c => c.type === 'text').map(c => c.text).join('')\n : '');\n if (text && text.trim()) {\n const question = text.replace(/\\[选中内容\\]\\n[\\s\\S]*?\\n\\n\\[用户问题\\]\\n/, '')\n .replace(/\\[引用内容\\]\\n[\\s\\S]*?\\n\\n\\[用户问题\\]\\n/, '')\n .replace(/\\[选中内容摘要\\]\\n[\\s\\S]*?\\n\\n\\[用户问题\\]\\n/, '')\n .replace(/\\[引用内容摘要\\]\\n[\\s\\S]*?\\n\\n\\[用户问题\\]\\n/, '')\n .trim()\n .substring(0, 80);\n if (question) userQuestions.push(question);\n }\n } else if (msg.role === 'assistant' && msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n const name = tc.function?.name || tc.name || t('tokenCounter.unknownTool');\n toolCalls.push(name);\n }\n }\n }\n\n if (userQuestions.length > 0) {\n summaryParts.push(t('tokenCounter.userQuestion', { question: userQuestions[userQuestions.length - 1] }));\n }\n if (toolCalls.length > 0) {\n // 去重并限制数量\n const uniqueTools = [...new Set(toolCalls)].slice(0, 10);\n summaryParts.push(t('tokenCounter.usedTools', { tools: uniqueTools.join(', ') }));\n }\n\n if (summaryParts.length === 0) return null;\n return t('tokenCounter.historySummary') + '\\n' + summaryParts.join('\\n');\n}\n\n/**\n * 清洗消息 content 字符串中的非法 Unicode 字符\n * - 替换孤立代理对字符(lone surrogates,由 substring 截断 emoji 等导致)为 U+FFFD\n * - 某些 API 服务端 JSON 解析器(如支持 \\x 扩展的解析器)会因非法 Unicode 而解析失败\n * @param {string} str - 原始字符串\n * @returns {string} 清洗后的字符串\n */\nfunction sanitizeContentForApi(str) {\n if (typeof str !== 'string') return str;\n // 匹配:\n // 1. 高位代理 U+D800..U+DBFF 后面没有紧跟低位代理\n // 2. 低位代理 U+DC00..U+DFFF 前面没有高位代理\n // 使用 Unicode property escapes 可读性更好,但兼容性考虑用显式范围\n return str.replace(/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g, '\\uFFFD');\n}\n\nconst API_ALLOWED_FIELDS = new Set(['role', 'content', 'tool_calls', 'tool_call_id', 'name', 'reasoning_content', 'prefix']);\n\n/**\n * 过滤消息中的内部字段,确保消息格式符合 API 要求\n * 同时扫描并修复 assistant(tool_calls) 与 tool 消息的配对关系\n * @param {Array} messages - 原始消息数组\n * @returns {Array} 过滤后的消息数组\n */\nexport function filterApiMessages(messages) {\n const filtered = messages.map((msg, index) => {\n const result = {};\n for (const key of API_ALLOWED_FIELDS) {\n if (key in msg) {\n result[key] = msg[key];\n }\n }\n\n // 清理 multipart content 中 image_url 的内部字段\n // filterApiMessages 负责剥离 original_url(原图 Base64),保留 width/height 供 token 估算\n // 此函数在 token 估算之前调用,width/height 用于 estimateImageTokens 按分辨率精准估算(避免按 Base64 字节高估约 20 倍)\n if (Array.isArray(result.content)) {\n result.content = result.content.map(part => {\n if (part && part.type === 'image_url' && part.image_url) {\n const clean = { url: part.image_url.url };\n if (part.image_url.width != null) clean.width = part.image_url.width;\n if (part.image_url.height != null) clean.height = part.image_url.height;\n return { ...part, image_url: clean };\n }\n // 清洗 text part 中的非法 Unicode 字符\n if (part && part.type === 'text' && typeof part.text === 'string') {\n return { ...part, text: sanitizeContentForApi(part.text) };\n }\n return part;\n });\n }\n\n // 清洗 content 字符串中的非法 Unicode(如孤立的代理对字符)\n // 防止 substring 截断 emoji 等原因产生的非法字符导致 API 服务端 JSON 解析失败\n if (typeof result.content === 'string') {\n result.content = sanitizeContentForApi(result.content);\n }\n\n if (result.role === 'tool') {\n if (!result.tool_call_id) {\n logger.warn(`[Background] foundmessage ${index} missing tool_call_id,skipped`, msg);\n return null;\n }\n return result;\n }\n\n if (result.role === 'assistant' && Array.isArray(result.tool_calls)) {\n result.tool_calls = result.tool_calls.map(tc => ({\n id: tc.id,\n type: tc.type,\n function: tc.function\n }));\n } else if (result.role === 'assistant' && result.tool_calls !== undefined) {\n logger.warn(`[Background] foundmessage ${index} tool_calls non-group (type: ${typeof result.tool_calls}),cleared`);\n delete result.tool_calls;\n }\n\n if (result.role === 'assistant' && result.content === null) {\n result.content = '';\n }\n\n return result;\n }).filter(msg => msg !== null);\n\n // 扫描并修复 tool_calls/tool 配对:验证 tool_call_id 与 tool_calls[].id 严格匹配\n for (let i = 0; i < filtered.length; i++) {\n const msg = filtered[i];\n if (msg?.role === 'assistant' && msg.tool_calls) {\n // 收集该 assistant 之后连续出现的 tool 消息的 tool_call_id\n const followingToolIds = new Set();\n for (let j = i + 1; j < filtered.length; j++) {\n if (filtered[j].role === 'tool') {\n followingToolIds.add(filtered[j].tool_call_id);\n } else {\n break;\n }\n }\n\n // 检查 assistant.tool_calls 中的每个 id 是否都有对应的 tool 响应\n const matchedCalls = msg.tool_calls.filter(tc => followingToolIds.has(tc.id));\n\n if (matchedCalls.length === 0) {\n // 完全没有匹配的 tool 响应 → 清除所有 tool_calls\n logger.warn('[Background] filterApiMessages: ', i, ' assistant message tool_calls no matchconfig tool response,cleared');\n delete msg.tool_calls;\n if (!msg.content) {\n filtered.splice(i, 1);\n i--;\n }\n } else if (matchedCalls.length < msg.tool_calls.length) {\n // 部分匹配 → 只保留有响应的 tool_calls\n const unmatched = msg.tool_calls.filter(tc => !followingToolIds.has(tc.id));\n logger.warn('[Background] filterApiMessages: ', i, ' assistant message', unmatched.length, ' tool_call no corresponding response,removed,keep', matchedCalls.length, '');\n msg.tool_calls = matchedCalls;\n }\n }\n }\n\n return filtered;\n}\n\n/**\n * 清理 image_url 中的额外字段,仅保留标准 url 字段\n * 在实际构建 API 请求体时调用,移除 width/height 等所有非标准字段\n * 返回浅拷贝新数组,不修改原数组,不影响后续 token 估算和裁剪逻辑\n * @param {Array} messages - 消息数组\n * @returns {Array} 清理后的消息数组\n */\nexport function sanitizeImageUrlsForApi(messages) {\n if (!messages || messages.length === 0) return messages;\n return messages.map(msg => {\n if (!msg || !Array.isArray(msg.content)) return msg;\n let contentChanged = false;\n const newContent = msg.content.map(part => {\n if (part && part.type === 'image_url' && part.image_url) {\n contentChanged = true;\n return { type: 'image_url', image_url: { url: part.image_url.url } };\n }\n return part;\n });\n return contentChanged ? { ...msg, content: newContent } : msg;\n });\n}\n\n/**\n * 从消息 content 中移除图片数据,仅保留文本部分\n * 用于发送历史消息时避免携带已无用的 Base64 图片\n * @param {string|Array} content - 消息内容\n * @returns {string|Array} 仅含文本的内容\n */\nexport function stripImagesFromContent(content) {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n const textParts = content.filter(c => c.type === 'text');\n return textParts.length === 1 ? textParts[0].text : textParts;\n }\n return content;\n}\n\n/**\n * 从消息 content 中提取纯文本字符串\n * 用于日志、摘要、预筛选、反思等\"把 content 当文本用\"的场景,\n * 避免把含 Base64 图片的数组 content 直接 JSON.stringify 导致图片数据污染文本\n * - 字符串:原样返回\n * - 数组(多模态):拼接所有 text part,忽略 image_url 等非文本部分\n * - null/undefined:返回空字符串\n * - 其他对象:JSON.stringify 兜底\n * @param {string|Array|*} content\n * @returns {string}\n */\nexport function extractTextFromContent(content) {\n if (content == null) return '';\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content\n .filter(part => part && part.type === 'text' && part.text)\n .map(part => part.text)\n .join('\\n');\n }\n return JSON.stringify(content);\n}\n\n// ============================================================\n// 引用内容压缩\n// ============================================================\n\n// 引用内容保留的最大 token 数(超过此值的引用将压缩)\nconst MAX_QUOTED_CONTEXT_TOKENS = 2000;\n\n/**\n * 压缩引用/选中内容,防止大段内容永久占据上下文\n * @param {string} ctx - 原始引用内容\n * @returns {{ compressed: string, wasCompressed: boolean }}\n */\nexport function compressQuotedContext(ctx) {\n if (!ctx) return { compressed: ctx, wasCompressed: false };\n const tokens = estimateTokens(ctx);\n if (tokens <= MAX_QUOTED_CONTEXT_TOKENS) {\n return { compressed: ctx, wasCompressed: false };\n }\n const truncated = truncateByTokens(ctx, MAX_QUOTED_CONTEXT_TOKENS);\n logger.debug(`[TokenCounter] referenced contentcompress: ${tokens} → ${estimateTokens(truncated)} tokens`);\n return { compressed: truncated, wasCompressed: true };\n}\n","import logger from '../shared/logger.js';\nimport { t, registerTranslations } from '../shared/i18n.js';\n\n// storage/db.js - IndexedDB 封装层\n// 提供 Promise 化的 IndexedDB 操作,支持 side panel 和 service worker 共享访问\n\nregisterTranslations('zh', {\n db: {\n sessionLabel: '会话 {id}',\n archivedLabel: '[归档] {label}',\n },\n});\n\nregisterTranslations('en', {\n db: {\n sessionLabel: 'Session {id}',\n archivedLabel: '[Archived] {label}',\n },\n});\n\nconst DB_NAME = 'ai-helper-db';\nconst DB_VERSION = 5;\n\n// ReAct Checkpoint TTL:7 天\nexport const REACT_CHECKPOINT_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\n// chrome.storage.local 中 checkpoint 的 key 前缀(后备存储)\n// 当 IndexedDB 的 reactCheckpoints store 不可用时,使用 chrome.storage.local 作为可靠后备\nconst CHECKPOINT_KEY_PREFIX = 'react_checkpoint_';\n\n/**\n * 获取数据库连接(单例)\n * @returns {Promise<IDBDatabase>}\n */\nfunction openDB() {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onupgradeneeded = (event) => {\n const db = event.target.result;\n\n // 活跃会话存储(keyPath: id)\n if (!db.objectStoreNames.contains('sessions')) {\n const sessionsStore = db.createObjectStore('sessions', { keyPath: 'id' });\n sessionsStore.createIndex('updatedAt', 'updatedAt', { unique: false });\n }\n\n // 活跃会话 ID(单条记录,keyPath: key)\n if (!db.objectStoreNames.contains('activeSession')) {\n db.createObjectStore('activeSession', { keyPath: 'key' });\n }\n\n // 归档会话存储(keyPath: id)\n if (!db.objectStoreNames.contains('archivedSessions')) {\n const archiveStore = db.createObjectStore('archivedSessions', { keyPath: 'id' });\n archiveStore.createIndex('createdAt', 'createdAt', { unique: false });\n }\n\n // UI 原型存储(keyPath: id)\n if (!db.objectStoreNames.contains('uiPrototypes')) {\n const prototypeStore = db.createObjectStore('uiPrototypes', { keyPath: 'id' });\n prototypeStore.createIndex('createdAt', 'createdAt', { unique: false });\n prototypeStore.createIndex('sessionId', 'sessionId', { unique: false });\n }\n\n // Token 使用统计存储(keyPath: id)\n if (!db.objectStoreNames.contains('tokenStats')) {\n const tokenStatsStore = db.createObjectStore('tokenStats', { keyPath: 'id' });\n tokenStatsStore.createIndex('sessionId', 'sessionId', { unique: false });\n tokenStatsStore.createIndex('timestamp', 'timestamp', { unique: false });\n }\n\n // ReAct Checkpoint 存储(keyPath: sessionId)—— 用于任务中断后续接\n if (!db.objectStoreNames.contains('reactCheckpoints')) {\n const checkpointStore = db.createObjectStore('reactCheckpoints', { keyPath: 'sessionId' });\n checkpointStore.createIndex('updatedAt', 'updatedAt', { unique: false });\n }\n\n // 消息收藏存储(keyPath: id)\n if (!db.objectStoreNames.contains('bookmarks')) {\n const bookmarksStore = db.createObjectStore('bookmarks', { keyPath: 'id' });\n bookmarksStore.createIndex('sessionId', 'sessionId', { unique: false });\n bookmarksStore.createIndex('createdAt', 'createdAt', { unique: false });\n }\n };\n\n request.onsuccess = (event) => {\n const db = event.target.result;\n\n // 关键:当其他连接(如 side_panel 持有的旧连接)触发版本变更时,\n // 主动关闭当前连接,避免阻塞 DB 升级。\n // 没有此处理会导致 SW 升级 DB 时被旧连接阻塞,reactCheckpoints store 无法创建,\n // 表现为 checkpoint 写入静默失败 → 恢复时\"未找到 checkpoint\"。\n db.onversionchange = (event) => {\n logger.debug('[IDB] detected versionchange event,maindynamicclosecurrent connection as allows upgrade');\n try { event.target.close(); } catch (e) {}\n // 重置单例缓存,下次 getDB() 会重新打开\n dbPromise = null;\n };\n\n resolve(db);\n };\n\n request.onerror = (event) => {\n logger.error('[IDB] opendatabase failed:', event.target.error);\n reject(event.target.error);\n };\n\n // 处理被阻塞的升级(其他连接持有旧版本时触发)\n request.onblocked = () => {\n logger.warn('[IDB] databaseupgrade blocked (other connections notclose),waitingretry...');\n // 不直接 reject,让调用方稍后重试 getDB() 时自然获得新连接\n };\n });\n}\n\nlet dbPromise = null;\n\n/**\n * 重置数据库连接缓存(用于 Service Worker 重启后恢复)\n */\nfunction resetDBConnection() {\n if (dbPromise) {\n dbPromise = null;\n logger.debug('[IDB] databaseconnectionreset');\n }\n}\n\n/**\n * 获取数据库实例(缓存单例)\n */\nexport function getDB() {\n if (!dbPromise) {\n dbPromise = openDB();\n }\n return dbPromise;\n}\n\n/**\n * 通用事务包装器(带连接恢复重试)\n * @param {string} storeName - objectStore 名称\n * @param {'readonly'|'readwrite'} mode\n * @param {function(IDBObjectStore, function): void} callback\n * @param {boolean} [isRetry=false] - 是否为重试调用(内部使用)\n * @returns {Promise<any>}\n */\nexport function withStore(storeName, mode, callback, isRetry = false) {\n return getDB().then((db) => {\n return new Promise((resolve, reject) => {\n let resolvedValue = null;\n let settled = false;\n\n // 检查 store 是否存在;不存在时强制重置连接并重试(触发 DB 升级创建缺失的 store)\n // 这是 \"reactCheckpoints store 不存在导致 checkpoint 静默失败\" 的根因修复\n if (!db.objectStoreNames.contains(storeName)) {\n logger.error(`[IDB] store \"${storeName}\" not found!current DB version: ${db.version}, store column table: [${Array.from(db.objectStoreNames).join(', ')}]`);\n if (!isRetry) {\n settled = true;\n // 强制重置连接,下次 getDB() 会重新打开并触发 onupgradeneeded(如果版本有变化)\n // 但如果 DB 已经是最新版本但 store 仍然缺失(连接陈旧),需要先关闭再重开\n try { db.close(); } catch (e) {}\n resetDBConnection();\n // 延迟重试,给 DB 重开一点时间\n setTimeout(() => {\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n }, 50);\n return;\n } else {\n settled = true;\n reject(new Error(`Store \"${storeName}\" does not exist in database (version ${db.version})`));\n return;\n }\n }\n\n let tx;\n try {\n tx = db.transaction(storeName, mode);\n } catch (e) {\n // transaction() 可能抛出同步异常(如连接已关闭)\n logger.error(`[IDB] create transaction failed (${storeName}):`, e.message);\n if (!isRetry) {\n settled = true;\n resetDBConnection();\n setTimeout(() => {\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n }, 50);\n return;\n } else {\n settled = true;\n reject(e);\n return;\n }\n }\n const store = tx.objectStore(storeName);\n\n // 回调中的 resolve 仅保存结果,实际 resolve 在 tx.oncomplete 中执行\n // 确保事务已提交后再返回,防止调用方读取到未持久化的数据\n callback(store, (result) => {\n resolvedValue = result;\n });\n\n tx.oncomplete = () => {\n if (!settled) {\n settled = true;\n resolve(resolvedValue);\n }\n };\n\n tx.onerror = (event) => {\n if (settled) return;\n settled = true;\n const err = event.target.error || new Error(`Transaction error on ${storeName}`);\n logger.error(`[IDB] transaction error (${storeName}):`, err);\n\n if (!isRetry) {\n // Service Worker 重启后连接可能已关闭,重置并重试一次\n resetDBConnection();\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n } else {\n reject(err);\n }\n };\n\n tx.onabort = () => {\n if (settled) return;\n settled = true;\n\n if (!isRetry) {\n resetDBConnection();\n withStore(storeName, mode, callback, true)\n .then(resolve)\n .catch(reject);\n } else {\n reject(new Error(`Transaction aborted on ${storeName}`));\n }\n };\n });\n });\n}\n\n// ==================== Sessions CRUD ====================\n\n/**\n * 获取所有活跃会话\n */\nexport function getAllSessions() {\n return withStore('sessions', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 获取单个会话\n */\nexport function getSession(id) {\n return withStore('sessions', 'readonly', (store, resolve) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result || null);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 从会话的 messageHistory 中直接删除指定消息(不依赖 saveCurrentSession 的完整保存流程)\n * @param {string} sessionId\n * @param {string} messageId\n * @returns {Promise<boolean>}\n */\nexport async function deleteMessageFromSession(sessionId, messageId) {\n if (!sessionId || !messageId) return false;\n try {\n const session = await getSession(sessionId);\n if (!session || !session.messageHistory) return false;\n\n const idx = session.messageHistory.findIndex(\n msg => (msg.messageId || msg.id) === messageId\n );\n if (idx === -1) return false;\n\n session.messageHistory.splice(idx, 1);\n return await putSession(session);\n } catch (e) {\n logger.error('[DB] deleteMessageFromSession failed:', e);\n return false;\n }\n}\n\n/**\n * 保存/更新会话(put = insert or update)\n */\nexport function putSession(session) {\n return withStore('sessions', 'readwrite', (store, resolve) => {\n const request = store.put(session);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 删除会话\n */\nexport function deleteSession(id) {\n return withStore('sessions', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 批量保存/更新多个会话\n */\nexport function putSessions(sessions) {\n return getDB().then((db) => {\n return new Promise((resolve) => {\n const tx = db.transaction('sessions', 'readwrite');\n const store = tx.objectStore('sessions');\n sessions.forEach((s) => store.put(s));\n tx.oncomplete = () => resolve(true);\n tx.onerror = () => resolve(false);\n });\n });\n}\n\n// ==================== Active Session ID ====================\n\n/**\n * 获取当前活跃会话 ID\n */\nexport function getActiveSessionId() {\n return withStore('activeSession', 'readonly', (store, resolve) => {\n const request = store.get('active');\n request.onsuccess = () => resolve(request.result?.sessionId || null);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 设置当前活跃会话 ID\n */\nexport function setActiveSessionId(sessionId) {\n return withStore('activeSession', 'readwrite', (store, resolve) => {\n const request = store.put({ key: 'active', sessionId });\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n// ==================== Archived Sessions CRUD ====================\n\n/**\n * 获取所有归档会话\n */\nexport function getAllArchivedSessions() {\n return withStore('archivedSessions', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 保存归档会话\n */\nexport function putArchivedSession(session) {\n return withStore('archivedSessions', 'readwrite', (store, resolve) => {\n const request = store.put(session);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 删除归档会话\n */\nexport function deleteArchivedSession(id) {\n return withStore('archivedSessions', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 清空并批量写入归档会话(用于截断超出 20 条的场景)\n */\nexport function replaceArchivedSessions(sessions) {\n return getDB().then((db) => {\n return new Promise((resolve) => {\n const tx = db.transaction('archivedSessions', 'readwrite');\n const store = tx.objectStore('archivedSessions');\n\n // 清空\n const clearReq = store.clear();\n clearReq.onsuccess = () => {\n // 批量写入\n sessions.forEach((s) => store.put(s));\n };\n tx.oncomplete = () => resolve(true);\n tx.onerror = () => resolve(false);\n });\n });\n}\n\n// ==================== UI Prototypes CRUD ====================\n\n/**\n * 保存 UI 原型\n */\nexport function saveUiPrototype(prototype) {\n const now = Date.now();\n const data = {\n ...prototype,\n createdAt: prototype.createdAt || now,\n updatedAt: now\n };\n return withStore('uiPrototypes', 'readwrite', (store, resolve) => {\n const request = store.put(data);\n request.onsuccess = () => resolve(data);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 获取单个 UI 原型\n */\nexport function getUiPrototype(id) {\n return withStore('uiPrototypes', 'readonly', (store, resolve) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result || null);\n request.onerror = () => resolve(null);\n });\n}\n\n/**\n * 获取所有 UI 原型(按创建时间倒序)\n */\nexport function listUiPrototypes(sessionId = null) {\n return withStore('uiPrototypes', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => {\n let results = request.result || [];\n if (sessionId) {\n results = results.filter(p => p.sessionId === sessionId);\n }\n results.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));\n resolve(results);\n };\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 删除 UI 原型\n */\nexport function deleteUiPrototype(id) {\n return withStore('uiPrototypes', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n// ==================== 搜索 ====================\n\n/**\n * 从所有活跃会话中搜索消息\n * @param {string} sessionId - 限定会话 ID,null 表示搜索所有活跃会话\n * @returns {Promise<Array<{session: string, index: number, role: string, content: string}>>}\n */\nexport function searchActiveSessionsMessages(sessionId) {\n if (sessionId) {\n // 指定 sessionId 时使用 O(1) 主键查询,避免全量加载所有会话\n return getSession(sessionId).then((session) => {\n if (!session) return [];\n const label = session.title || t('db.sessionLabel', { id: session.id?.slice(0, 8) });\n return (session.messageHistory || []).filter(msg => msg.content).map((msg, idx) => ({\n session: session.id,\n sessionLabel: label,\n index: idx,\n role: msg.role,\n content: msg.content,\n }));\n });\n }\n return getAllSessions().then((all) => {\n const sessions = all;\n const results = [];\n sessions.forEach((session) => {\n const label = session.title || t('db.sessionLabel', { id: session.id?.slice(0, 8) });\n (session.messageHistory || []).forEach((msg, idx) => {\n if (msg.content) {\n results.push({\n session: session.id,\n sessionLabel: label,\n index: idx,\n role: msg.role,\n content: msg.content,\n });\n }\n });\n });\n return results;\n });\n}\n\n/**\n * 获取所有归档会话的消息\n * @returns {Promise<Array<{session: string, index: number, role: string, content: string}>>}\n */\nexport function getArchivedSessionsMessages() {\n return getAllArchivedSessions().then((archiveSessions) => {\n const results = [];\n archiveSessions.forEach((session) => {\n const label = t('db.archivedLabel', { label: session.title || t('db.sessionLabel', { id: session.id?.slice(0, 8) }) });\n (session.messages || []).forEach((msg, idx) => {\n if (msg.content) {\n results.push({\n session: session.id,\n sessionLabel: label,\n index: idx,\n role: msg.role,\n content: msg.content,\n });\n }\n });\n });\n return results;\n });\n}\n\n// ==================== 数据迁移 ====================\n\n/**\n * 从 chrome.storage.local 迁移数据到 IndexedDB\n * 迁移后清除 chrome.storage.local 中的大数据 key\n * @returns {Promise<boolean>}\n */\nexport function migrateFromChromeStorage() {\n return new Promise((resolve) => {\n chrome.storage.local.get(['sessions', 'conversationSessions'], async (result) => {\n let migrated = false;\n\n // 迁移活跃会话\n if (result.sessions?.list?.length > 0) {\n const { list, activeSessionId } = result.sessions;\n await putSessions(list);\n if (activeSessionId) {\n await setActiveSessionId(activeSessionId);\n }\n migrated = true;\n logger.debug('[IDB] migrated activesession:', list.length, '');\n }\n\n // 迁移归档会话\n if (result.conversationSessions?.sessions?.length > 0) {\n await replaceArchivedSessions(result.conversationSessions.sessions);\n migrated = true;\n logger.debug('[IDB] migrated archivesession:', result.conversationSessions.sessions.length, '');\n }\n\n if (migrated) {\n // 清除 chrome.storage 中的大数据 key,保留其他配置\n chrome.storage.local.remove(['sessions', 'conversationSessions', 'chatHistory'], () => {\n logger.debug('[IDB] cleaned chrome.storage.local olddata');\n });\n }\n\n resolve(migrated);\n });\n });\n}\n\n/**\n * 检查 IndexedDB 是否有数据,没有则尝试从 chrome.storage 迁移\n * @returns {Promise<void>}\n */\nexport async function ensureMigration() {\n const sessions = await getAllSessions();\n if (sessions.length > 0) return; // 已有数据\n\n const activeId = await getActiveSessionId();\n if (activeId) return; // 已有活跃会话标记\n\n // 尝试迁移\n await migrateFromChromeStorage();\n}\n\n// ==================== React Checkpoints CRUD ====================\n//\n// checkpoint 数据结构:\n// {\n// sessionId: string, // 主键\n// currentMessages: Array, // 完整的 API 消息数组(含 tool_calls/tool 消息对)\n// executionLog: Array, // 执行日志\n// iteration: number, // 当前迭代次数\n// globalIteration: { value }, // 全局迭代计数\n// subtaskPlan: Object|null, // 任务拆解计划\n// currentSubtaskIndex: number|null, // 当前子任务索引\n// totalReflectionRounds: number, // 反思轮数\n// model: string, // 使用的模型\n// apiParams: Object, // API 参数\n// taskContext: Object|null, // 任务上下文(子任务/子代理)\n// tabId: number|null, // 标签页 ID\n// callId: string|null, // 调用 ID\n// interruptedReason: string, // 中断原因\n// createdAt: number, // 创建时间戳\n// updatedAt: number, // 更新时间戳\n// }\n\n/**\n * 保存/更新 ReAct checkpoint\n * 采用双写策略:IndexedDB + chrome.storage.local(后备)\n * 当 IndexedDB 的 reactCheckpoints store 不存在或写入失败时,chrome.storage.local 确保数据不丢失\n * @param {Object} checkpoint - 必须包含 sessionId\n * @returns {Promise<boolean>}\n */\nexport async function saveReactCheckpoint(checkpoint) {\n if (!checkpoint?.sessionId) return false;\n const now = Date.now();\n const data = {\n ...checkpoint,\n updatedAt: now,\n createdAt: checkpoint.createdAt || now,\n };\n const key = CHECKPOINT_KEY_PREFIX + checkpoint.sessionId;\n\n // 策略:先写 chrome.storage.local(可靠),再尝试 IndexedDB(性能优化)\n // 这样即使 IndexedDB 完全不可用,checkpoint 也不会丢失\n let storageOk = false;\n try {\n await chrome.storage.local.set({ [key]: data });\n storageOk = true;\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local save checkpoint failed:', e.message);\n }\n\n // 同时写入 IndexedDB(如果可用),供 getAllReactCheckpoints 高效查询\n let idbOk = false;\n try {\n idbOk = await withStore('reactCheckpoints', 'readwrite', (store, resolve) => {\n const request = store.put(data);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n } catch (e) {\n // IndexedDB 不可用(store 不存在等),不影响主流程\n logger.warn('[Storage] IndexedDB save checkpoint failed (downgraded to chrome.storage.local):', e.message);\n }\n\n if (!storageOk && !idbOk) {\n logger.error('[Storage] checkpoint save complete failed:chrome.storage.local and IndexedDB averagenon- with ');\n return false;\n }\n return true;\n}\n\n/**\n * 获取指定会话的 ReAct checkpoint\n * 优先从 IndexedDB 读取,不存在时降级到 chrome.storage.local\n * @param {string} sessionId\n * @returns {Promise<Object|null>}\n */\nexport async function getReactCheckpoint(sessionId) {\n if (!sessionId) return null;\n\n // 优先尝试 IndexedDB\n try {\n const result = await withStore('reactCheckpoints', 'readonly', (store, resolve) => {\n const request = store.get(sessionId);\n request.onsuccess = () => resolve(request.result || null);\n request.onerror = () => resolve(null);\n });\n if (result) return result;\n } catch (e) {\n // IndexedDB 不可用,继续尝试 chrome.storage.local\n }\n\n // 降级:从 chrome.storage.local 读取\n try {\n const key = CHECKPOINT_KEY_PREFIX + sessionId;\n const data = await chrome.storage.local.get(key);\n return data[key] || null;\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local read checkpoint failed:', e.message);\n return null;\n }\n}\n\n/**\n * 删除指定会话的 ReAct checkpoint\n * 同时从 IndexedDB 和 chrome.storage.local 中删除\n * @param {string} sessionId\n * @returns {Promise<boolean>}\n */\nexport async function deleteReactCheckpoint(sessionId) {\n if (!sessionId) return false;\n let ok = false;\n\n // 从 chrome.storage.local 删除\n try {\n const key = CHECKPOINT_KEY_PREFIX + sessionId;\n await chrome.storage.local.remove(key);\n ok = true;\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local delete checkpoint failed:', e.message);\n }\n\n // 从 IndexedDB 删除\n try {\n await withStore('reactCheckpoints', 'readwrite', (store, resolve) => {\n const request = store.delete(sessionId);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n ok = true;\n } catch (e) {\n // IndexedDB 不可用,不影响\n }\n\n return ok;\n}\n\n/**\n * 获取所有 ReAct checkpoint(用于 TTL 清理和诊断)\n * 合并 IndexedDB 和 chrome.storage.local 中的结果\n * @returns {Promise<Array>}\n */\nexport async function getAllReactCheckpoints() {\n // 从 chrome.storage.local 获取所有 checkpoint\n let storageCheckpoints = [];\n try {\n const all = await chrome.storage.local.get(null);\n for (const [key, value] of Object.entries(all)) {\n if (key.startsWith(CHECKPOINT_KEY_PREFIX) && value?.sessionId) {\n storageCheckpoints.push(value);\n }\n }\n } catch (e) {\n logger.warn('[Storage] chrome.storage.local get all checkpoint failed:', e.message);\n }\n\n // 从 IndexedDB 获取\n let idbCheckpoints = [];\n try {\n idbCheckpoints = await withStore('reactCheckpoints', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n } catch (e) {\n // IndexedDB 不可用\n }\n\n // 合并去重(以 sessionId 为键,IndexedDB 优先)\n const merged = new Map();\n for (const cp of storageCheckpoints) {\n merged.set(cp.sessionId, cp);\n }\n for (const cp of idbCheckpoints) {\n merged.set(cp.sessionId, cp);\n }\n return Array.from(merged.values());\n}\n\n/**\n * 清理过期的 ReAct checkpoint(TTL: 7 天)\n * 应在 SW 启动时调用\n * @returns {Promise<number>} 清理的条数\n */\nexport async function cleanupExpiredReactCheckpoints() {\n const all = await getAllReactCheckpoints();\n const now = Date.now();\n const expired = all.filter(c => (c.updatedAt || 0) < now - REACT_CHECKPOINT_TTL_MS);\n if (expired.length === 0) return 0;\n\n let deleted = 0;\n for (const cp of expired) {\n const ok = await deleteReactCheckpoint(cp.sessionId);\n if (ok) deleted++;\n }\n logger.debug(`[IDB] cleanup ${deleted} expired ReAct checkpoint`);\n return deleted;\n}\n\n// ==================== Bookmarks CRUD ====================\n\n/**\n * 获取所有收藏\n * @returns {Promise<Array>}\n */\nexport function getAllBookmarks() {\n return withStore('bookmarks', 'readonly', (store, resolve) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => resolve([]);\n });\n}\n\n/**\n * 保存收藏\n * @param {Object} bookmark - { id, sessionId, messageId, content, sessionTitle, createdAt, pinned }\n * @returns {Promise<boolean>}\n */\nexport function putBookmark(bookmark) {\n return withStore('bookmarks', 'readwrite', (store, resolve) => {\n const request = store.put(bookmark);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 删除收藏\n * @param {string} id\n * @returns {Promise<boolean>}\n */\nexport function deleteBookmark(id) {\n return withStore('bookmarks', 'readwrite', (store, resolve) => {\n const request = store.delete(id);\n request.onsuccess = () => resolve(true);\n request.onerror = () => resolve(false);\n });\n}\n\n/**\n * 更新收藏的置顶状态\n * @param {string} id\n * @param {boolean} pinned\n * @returns {Promise<boolean>}\n */\nexport function updateBookmarkPin(id, pinned) {\n return withStore('bookmarks', 'readwrite', (store, resolve) => {\n const getReq = store.get(id);\n getReq.onsuccess = () => {\n const bm = getReq.result;\n if (!bm) { resolve(false); return; }\n bm.pinned = pinned;\n const putReq = store.put(bm);\n putReq.onsuccess = () => resolve(true);\n putReq.onerror = () => resolve(false);\n };\n getReq.onerror = () => resolve(false);\n });\n}\n","// storage/token-store.js - Token 使用统计的 IndexedDB CRUD\n\nimport { getDB, withStore } from './db.js';\n\nconst STORE_NAME = 'tokenStats';\n\n// 全局汇总存储 key\nconst OVERALL_KEY = 'overall';\n\n// 系统级会话 ID:不计入\"总会话数\",但其 Token 消耗仍计入总量\nconst SYSTEM_SESSION_IDS = new Set(['selection_toolbar']);\n\n/**\n * 记录单次 API 调用的 token 使用\n * @param {Object} record\n * @param {string} record.sessionId\n * @param {string} record.model\n * @param {number} record.promptTokens\n * @param {number} record.completionTokens\n * @param {number} record.totalTokens\n * @param {number} record.contextWindow\n * @param {string} record.callType - 'react_loop' | 'non_stream' | 'reflection' | 'tool_reflection' | 'subtask_reflection'\n */\nexport function recordTokenCall(record) {\n const entry = {\n id: `tcr_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,\n sessionId: record.sessionId,\n timestamp: new Date().toISOString(),\n model: record.model || 'unknown',\n promptTokens: record.promptTokens || 0,\n completionTokens: record.completionTokens || 0,\n totalTokens: record.totalTokens || 0,\n contextWindow: record.contextWindow || 128000,\n contextUsageRate: record.contextWindow > 0\n ? ((record.promptTokens || 0) / record.contextWindow)\n : 0,\n callType: record.callType || 'unknown'\n };\n\n return withStore(STORE_NAME, 'readwrite', (store, resolve) => {\n const req = store.put(entry);\n req.onsuccess = () => resolve(true);\n req.onerror = () => resolve(false);\n });\n}\n\n/**\n * 获取某个会话的 token 汇总\n * @param {string} sessionId\n * @returns {Promise<Object>}\n */\nexport function getSessionTokenSummary(sessionId) {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = (req.result || []).filter(r => \n r.sessionId === sessionId || \n (sessionId && r.sessionId?.startsWith(sessionId + '_'))\n );\n if (records.length === 0) {\n resolve({\n sessionId,\n totalPromptTokens: 0,\n totalCompletionTokens: 0,\n totalTokens: 0,\n apiCallCount: 0,\n avgContextUsageRate: 0,\n maxContextUsageRate: 0,\n minContextUsageRate: 0,\n records: []\n });\n return;\n }\n\n const usageRates = records.map(r => r.contextUsageRate || 0).filter(r => r > 0);\n resolve({\n sessionId,\n totalPromptTokens: records.reduce((s, r) => s + (r.promptTokens || 0), 0),\n totalCompletionTokens: records.reduce((s, r) => s + (r.completionTokens || 0), 0),\n totalTokens: records.reduce((s, r) => s + (r.totalTokens || 0), 0),\n apiCallCount: records.length,\n avgContextUsageRate: usageRates.length > 0\n ? usageRates.reduce((s, r) => s + r, 0) / usageRates.length\n : 0,\n maxContextUsageRate: usageRates.length > 0 ? Math.max(...usageRates) : 0,\n minContextUsageRate: usageRates.length > 0 ? Math.min(...usageRates) : 0,\n records: records.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)).slice(0, 20)\n });\n };\n req.onerror = () => resolve(null);\n });\n}\n\n/**\n * 获取全局 token 汇总\n * @returns {Promise<Object>}\n */\nexport function getOverallTokenSummary() {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n if (records.length === 0) {\n resolve({\n totalPromptTokens: 0,\n totalCompletionTokens: 0,\n totalTokens: 0,\n totalApiCalls: 0,\n totalSessions: 0,\n lastUpdated: null\n });\n return;\n }\n\n const sessionIds = new Set(\n records.map(r => r.sessionId).filter(id => id && !SYSTEM_SESSION_IDS.has(id))\n );\n resolve({\n totalPromptTokens: records.reduce((s, r) => s + (r.promptTokens || 0), 0),\n totalCompletionTokens: records.reduce((s, r) => s + (r.completionTokens || 0), 0),\n totalTokens: records.reduce((s, r) => s + (r.totalTokens || 0), 0),\n totalApiCalls: records.length,\n totalSessions: sessionIds.size,\n lastUpdated: records.reduce((latest, r) => {\n return r.timestamp > latest ? r.timestamp : latest;\n }, '')\n });\n };\n req.onerror = () => resolve(null);\n });\n}\n\n/**\n * 清除某个会话的 token 统计\n * @param {string} sessionId\n */\nexport function clearSessionTokenStats(sessionId) {\n return withStore(STORE_NAME, 'readwrite', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n const toDelete = records.filter(r => r.sessionId === sessionId);\n let deleted = 0;\n toDelete.forEach(r => {\n store.delete(r.id);\n deleted++;\n });\n resolve(deleted);\n };\n req.onerror = () => resolve(0);\n });\n}\n\n/**\n * 清除所有 token 统计\n */\nexport function clearAllTokenStats() {\n return withStore(STORE_NAME, 'readwrite', (store, resolve) => {\n const req = store.clear();\n req.onsuccess = () => resolve(true);\n req.onerror = () => resolve(false);\n });\n}\n\n/**\n * 获取最近 N 条 token 记录(全局)\n * @param {number} limit\n */\nexport function getRecentTokenRecords(limit = 20) {\n return withStore(STORE_NAME, 'readonly', (store, resolve) => {\n const req = store.getAll();\n req.onsuccess = () => {\n const records = req.result || [];\n records.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));\n resolve(records.slice(0, limit));\n };\n req.onerror = () => resolve([]);\n });\n}\n","// shared/agent-defaults.js - 内置 Agent 定义和模板\n\n/**\n * 内置默认 Agent(不可删除)\n */\nexport const BUILTIN_AGENTS = [\n {\n id: 'default',\n name: 'Default Assistant',\n description: 'All-purpose AI assistant with all tool capabilities',\n icon: '🤖',\n systemPrompt: null, // null = 使用全局 systemPrompt\n toolIds: null, // null = 使用全局 enabledTools\n skillIds: null, // null = 使用全部启用技能\n isBuiltin: true,\n allowSubDispatch: false,\n model: null,\n temperature: null,\n topP: null,\n }\n];\n\n/**\n * Agent 模板(供用户快速创建参考)\n */\nexport const AGENT_TEMPLATES = [\n {\n name: 'Code Review Expert',\n icon: '🔍',\n description: 'Focused on code review and quality assurance',\n systemPrompt: `You are a senior Code Review Expert. Your responsibilities:\n1. Review code for logical correctness, performance, and security\n2. Check code style, naming conventions, and best practices\n3. Identify potential bugs, memory leaks, and concurrency issues\n4. Provide actionable improvement suggestions with example code\n\nResponse principles:\n- Always indicate severity (critical/moderate/suggestion)\n- Provide specific code modification suggestions, avoid vague comments\n- Focus on maintainability, not just functional correctness`,\n systemPromptZh: `你是一位资深代码审查专家。你的职责:\n1. 审查代码的逻辑正确性、性能和安全性\n2. 检查代码风格、命名规范和最佳实践\n3. 识别潜在的 Bug、内存泄漏和并发问题\n4. 提供可操作的改进建议,并附带示例代码\n\n回复原则:\n- 始终标注严重程度(严重/中等/建议)\n- 提供具体的代码修改建议,避免模糊评论\n- 关注可维护性,而非仅关注功能正确性`,\n toolIds: ['page_content', 'search_browser_data', 'agent_file', 'agent_search', 'search_chats', 'fetch_url', 'plan_task'],\n allowSubDispatch: false,\n },\n {\n name: 'Web Automation Assistant',\n icon: '🌐',\n description: 'Focused on web interaction and automation',\n systemPrompt: `You are a web automation expert. Your strengths:\n1. Automate web page interactions based on user needs (clicking, form filling, scrolling, etc.)\n2. Extract and analyze web page content\n3. Handle multi-step web interaction flows\n\nOperating principles:\n- Understand page structure before acting; prefer query_elements first\n- Verify results after actions to ensure they take effect\n- Analyze errors and try alternative approaches\n- Never assume elements exist; fetch page info first when uncertain\n- For dialogs (alert/confirm/prompt), call handle_dialog BEFORE the action that triggers them, then use interact_element to proceed`,\n systemPromptZh: `你是一位网页自动化专家。你的优势:\n1. 根据用户需求自动化网页交互(点击、表单填写、滚动等)\n2. 提取和分析网页内容\n3. 处理多步骤的网页交互流程\n\n操作原则:\n- 行动前先理解页面结构,优先使用 query_elements\n- 操作后验证结果,确保生效\n- 分析错误并尝试替代方案\n- 不要假设元素存在,不确定时先获取页面信息\n- 涉及 alert/confirm/prompt 弹窗时,先在触发弹窗的操作前调用 handle_dialog,再通过 interact_element 继续`,\n toolIds: ['page_content', 'query_elements', 'interact_element', 'fill_form', 'scroll_to', 'wait_element', 'keyboard_input', 'select_dropdown', 'capture_page', 'extract_data', 'search_in_page', 'wait_navigation', 'scroll_collect', 'drag_drop', 'file_upload', 'iframe_content', 'manage_tab', 'handle_dialog'],\n allowSubDispatch: false,\n },\n {\n name: 'Data Analyst',\n icon: '📊',\n description: 'Focused on data extraction, analysis, and visualization',\n systemPrompt: `You are a data analyst. Your strengths:\n1. Extract structured data from web pages (tables, lists, JSON)\n2. Analyze and summarize data patterns\n3. Present analysis results in clear formats\n\nAnalysis principles:\n- Understand data structure before starting analysis\n- Use the most appropriate extraction method (tables → extract_data with dataType=table; links/images/forms → extract_data with dataType=links/images/forms; text/HTML → page_content with format=text/html)\n- Support analysis results with data; avoid subjective assumptions\n- Present conclusions using tables or charts`,\n systemPromptZh: `你是一位数据分析师。你的优势:\n1. 从网页中提取结构化数据(表格、列表、JSON)\n2. 分析和总结数据模式\n3. 以清晰格式呈现分析结果\n\n分析原则:\n- 开始分析前先理解数据结构\n- 使用最合适的提取方法(表格使用 extract_data 配合 dataType=table;链接/图片/表单等结构化数据使用 extract_data 配合 dataType=links/images/forms;纯文本/HTML 使用 page_content 配合 format=text/html)\n- 用数据支撑分析结果,避免主观臆断\n- 使用表格或图表呈现结论`,\n toolIds: ['page_content', 'extract_data', 'query_elements', 'search_in_page', 'scroll_collect', 'fetch_url', 'iframe_content', 'clipboard'],\n allowSubDispatch: false,\n },\n {\n name: 'Documentation Assistant',\n icon: '📝',\n description: 'Focused on technical documentation and content organization',\n systemPrompt: `You are a technical documentation expert. Your strengths:\n1. Write clear technical documentation (API docs, READMEs, user guides)\n2. Distill and summarize technical information\n3. Transform complex concepts into easy-to-understand documentation\n\nWriting principles:\n- Clear structure: Use headings, lists, and tables to organize content\n- Examples first: Key concepts must include code or configuration examples\n- Reader-oriented: Adjust technical depth based on target audience\n- Stay concise: Avoid redundancy; every paragraph should carry information`,\n systemPromptZh: `你是一位技术文档专家。你的优势:\n1. 编写清晰的技术文档(API 文档、README、用户指南)\n2. 提炼和总结技术信息\n3. 将复杂概念转化为易于理解的文档\n\n编写原则:\n- 结构清晰:使用标题、列表和表格组织内容\n- 示例先行:关键概念必须附带代码或配置示例\n- 面向读者:根据目标受众调整技术深度\n- 保持简洁:避免冗余,每段文字都应承载信息`,\n toolIds: ['page_content', 'clipboard', 'search_browser_data', 'search_chats', 'fetch_url', 'agent_file', 'agent_search', 'extract_data', 'capture_page', 'search_in_page'],\n allowSubDispatch: false,\n },\n];\n\n/**\n * 生成新的 Agent ID\n */\nexport function generateAgentId() {\n return 'agent_' + Date.now().toString(36) + '_' + Math.random().toString(36).substring(2, 6);\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-C0FnF6B9.js";import{a as t,c as n}from"./i18n-
|
|
2
|
-
//# sourceMappingURL=agent-store-
|
|
1
|
+
import{r as e}from"./rolldown-runtime-C0FnF6B9.js";import{a as t,c as n}from"./i18n-De3tbtBv.js";import{n as r,r as i}from"./agent-defaults-Ct1YxFzI.js";var a=e({createAgent:()=>m,deleteAgent:()=>g,getActiveAgentId:()=>_,getAgent:()=>p,getAllAgents:()=>d,getCustomAgents:()=>f,setActiveAgentId:()=>v,updateAgent:()=>h});t(`zh`,{agentStore:{unnamedAgent:`未命名Agent`}}),t(`en`,{agentStore:{unnamedAgent:`Unnamed Agent`}});var o=`customAgents`,s=`activeAssistantId`,c=`activeAgentId`,l=!1;async function u(){if(l)return;l=!0;let e=await chrome.storage.local.get([c,s]),t=e[c],n=e[s];t&&!n&&!t.startsWith(`pa_`)&&(await chrome.storage.local.set({[s]:t}),await chrome.storage.local.remove(c))}async function d(){let e=(await chrome.storage.local.get([o]))[o]||[];return[...r,...e]}async function f(){return(await chrome.storage.local.get([o]))[o]||[]}async function p(e){return e&&(await d()).find(t=>t.id===e)||null}async function m(e){let t=await f(),r={id:i(),name:e.name||n(`agentStore.unnamedAgent`),description:e.description||``,icon:e.icon||`🤖`,systemPrompt:e.systemPrompt||``,toolIds:e.toolIds||null,skillIds:e.skillIds||null,isBuiltin:!1,allowSubDispatch:e.allowSubDispatch!==void 0&&e.allowSubDispatch,model:e.model||null,temperature:e.temperature===void 0?null:e.temperature,topP:e.topP===void 0?null:e.topP,createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};return t.push(r),await chrome.storage.local.set({[o]:t}),r}async function h(e,t){let n=await f(),r=n.findIndex(t=>t.id===e);return r===-1?null:(n[r]={...n[r],...t,id:e,isBuiltin:!1,updatedAt:new Date().toISOString()},await chrome.storage.local.set({[o]:n}),n[r])}async function g(e){let t=await f(),n=t.findIndex(t=>t.id===e);return n!==-1&&(t.splice(n,1),await chrome.storage.local.set({[o]:t}),(await chrome.storage.local.get([s]))[s]===e&&await chrome.storage.local.remove(s),!0)}async function _(){return await u(),(await chrome.storage.local.get([s]))[s]||null}async function v(e){e?await chrome.storage.local.set({[s]:e}):await chrome.storage.local.remove(s)}export{p as a,h as c,_ as i,m as n,d as o,g as r,v as s,a as t};
|
|
2
|
+
//# sourceMappingURL=agent-store-D0Es5Ite.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-store-
|
|
1
|
+
{"version":3,"file":"agent-store-D0Es5Ite.js","names":[],"sources":["../../src/side_panel/agent-store.js"],"sourcesContent":["// side_panel/agent-store.js - Agent 数据持久化\nimport { BUILTIN_AGENTS, generateAgentId } from '../shared/agent-defaults.js';\nimport { t, registerTranslations } from '../shared/i18n.js';\n\nregisterTranslations('zh', {\n agentStore: {\n unnamedAgent: '未命名Agent',\n },\n});\nregisterTranslations('en', {\n agentStore: {\n unnamedAgent: 'Unnamed Agent',\n },\n});\n\nconst STORAGE_KEY = 'customAgents';\nconst ACTIVE_KEY = 'activeAssistantId';\nconst LEGACY_ACTIVE_KEY = 'activeAgentId';\n\nlet migratedActiveKey = false;\n\nasync function migrateLegacyActiveKey() {\n if (migratedActiveKey) return;\n migratedActiveKey = true;\n\n const result = await chrome.storage.local.get([LEGACY_ACTIVE_KEY, ACTIVE_KEY]);\n const legacyValue = result[LEGACY_ACTIVE_KEY];\n const newValue = result[ACTIVE_KEY];\n\n if (legacyValue && !newValue && !legacyValue.startsWith('pa_')) {\n await chrome.storage.local.set({ [ACTIVE_KEY]: legacyValue });\n await chrome.storage.local.remove(LEGACY_ACTIVE_KEY);\n }\n}\n\n/**\n * 获取所有 Agent(内置 + 用户自定义)\n * @returns {Promise<Array>}\n */\nexport async function getAllAgents() {\n const result = await chrome.storage.local.get([STORAGE_KEY]);\n const customAgents = result[STORAGE_KEY] || [];\n return [...BUILTIN_AGENTS, ...customAgents];\n}\n\n/**\n * 仅获取用户自定义的 Agent\n * @returns {Promise<Array>}\n */\nexport async function getCustomAgents() {\n const result = await chrome.storage.local.get([STORAGE_KEY]);\n return result[STORAGE_KEY] || [];\n}\n\n/**\n * 根据 ID 获取单个 Agent\n * @param {string} agentId\n * @returns {Promise<Object|null>}\n */\nexport async function getAgent(agentId) {\n if (!agentId) return null;\n const all = await getAllAgents();\n return all.find(a => a.id === agentId) || null;\n}\n\n/**\n * 创建新的自定义 Agent\n * @param {Object} agentData - 不含 id 的 Agent 数据\n * @returns {Promise<Object>}\n */\nexport async function createAgent(agentData) {\n const agents = await getCustomAgents();\n const newAgent = {\n id: generateAgentId(),\n name: agentData.name || t('agentStore.unnamedAgent'),\n description: agentData.description || '',\n icon: agentData.icon || '🤖',\n systemPrompt: agentData.systemPrompt || '',\n toolIds: agentData.toolIds || null, // null = 继承全局\n skillIds: agentData.skillIds || null, // null = 继承全部启用技能\n isBuiltin: false,\n allowSubDispatch: agentData.allowSubDispatch !== undefined ? agentData.allowSubDispatch : false,\n model: agentData.model || null,\n temperature: agentData.temperature !== undefined ? agentData.temperature : null,\n topP: agentData.topP !== undefined ? agentData.topP : null,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n agents.push(newAgent);\n await chrome.storage.local.set({ [STORAGE_KEY]: agents });\n return newAgent;\n}\n\n/**\n * 更新已有的自定义 Agent\n * @param {string} agentId\n * @param {Object} updates\n * @returns {Promise<Object|null>}\n */\nexport async function updateAgent(agentId, updates) {\n const agents = await getCustomAgents();\n const idx = agents.findIndex(a => a.id === agentId);\n if (idx === -1) return null;\n \n agents[idx] = {\n ...agents[idx],\n ...updates,\n id: agentId, // id 不可变\n isBuiltin: false, // 不可变\n updatedAt: new Date().toISOString(),\n };\n await chrome.storage.local.set({ [STORAGE_KEY]: agents });\n return agents[idx];\n}\n\n/**\n * 删除自定义 Agent\n * @param {string} agentId\n * @returns {Promise<boolean>}\n */\nexport async function deleteAgent(agentId) {\n const agents = await getCustomAgents();\n const idx = agents.findIndex(a => a.id === agentId);\n if (idx === -1) return false;\n \n agents.splice(idx, 1);\n await chrome.storage.local.set({ [STORAGE_KEY]: agents });\n\n // 如果删除的 Agent 是当前选中的,重置为默认\n const result = await chrome.storage.local.get([ACTIVE_KEY]);\n if (result[ACTIVE_KEY] === agentId) {\n await chrome.storage.local.remove(ACTIVE_KEY);\n }\n \n return true;\n}\n\n/**\n * 获取当前选中的 Agent ID\n * @returns {Promise<string|null>}\n */\nexport async function getActiveAgentId() {\n await migrateLegacyActiveKey();\n const result = await chrome.storage.local.get([ACTIVE_KEY]);\n return result[ACTIVE_KEY] || null;\n}\n\n/**\n * 设置当前选中的 Agent ID\n * @param {string|null} agentId\n */\nexport async function setActiveAgentId(agentId) {\n if (agentId) {\n await chrome.storage.local.set({ [ACTIVE_KEY]: agentId });\n } else {\n await chrome.storage.local.remove(ACTIVE_KEY);\n }\n}\n\n/**\n * 从模板创建 Agent\n * @param {Object} template\n * @returns {Promise<Object>}\n */\nexport async function createAgentFromTemplate(template) {\n return createAgent({\n name: template.name,\n icon: template.icon,\n description: template.description,\n systemPrompt: template.systemPrompt,\n toolIds: template.toolIds ? [...template.toolIds] : null,\n allowSubDispatch: template.allowSubDispatch || false,\n });\n}\n"],"mappings":"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"}
|