Files
thzxx e4d81d8247 feat: 升级至 v0.3.1 — 全量代码审计修复 + 安全增强
本次升级基于完整代码审查,修复 Critical/High/Medium/Low 四级共 96 项问题,
并通过返工审计修复 10 项遗留问题,tsc 双端类型检查零错误。

Critical (10/10 完成):
- C-4: command.ts 接入 shell-quote 进行 token-level 注入检测,替代原有正则匹配
  可防御 r"m" -rf /、$'rm'、$(echo rm) 等字符串拼接绕过

High (11/11 完成):
- 竞态保护、Promise.allSettled、AbortController 资源泄漏、IPC 参数校验等

Medium (55/55 完成):
- 事务保护、敏感数据脱敏、枚举校验、MUI v9 Stack prop 迁移、
  React 组件 cancelled 标志、类型收窄等

Low (20/20 完成):
- 辅助方法提取(flushToolCallBuffer/scoreAndPushMemory/tryAddColumn 等)
- nanoid 统一替代 Date.now()+Math.random()
- confirm() 替换为 MUI Dialog、useMemo 缓存、魔法数字命名化等

返工审计修复 (10/10 完成):
- L-11: LogsSettings 残留的原生 confirm()/alert() 全部替换为 MUI Dialog/Alert
- M-53: MemoryViewer handleSearch 独立 ref,修复 searching 状态卡死
- M-42: 脱敏短值(length <= 4)泄露修复
- M-47: tasks:update 补全 title/description 类型校验
- L-9: ollama.adapter 非流式路径 nanoid 统一
- M-45: audit:query limit 策略与 memory:listAll 一致化
- SettingsModal handleConfirmRemove 补全 try/catch + loadServers cleanup
- L-15: CommandPalette useMemo 补全 sessions 响应式依赖
- useAgentStream 事件类型补全 seq/timestamp 字段

新增依赖: shell-quote + @types/shell-quote
版本号: 0.3.0 -> 0.3.1
2026-07-13 22:36:58 +08:00

82 lines
2.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Token 估算工具 — 跨 Provider 通用
*
* 策略:智能字符估算,区分中文字符与 ASCII 字符
* - 中文字符(含全角标点、日韩文):1 字符 ≈ 1.5 token
* - ASCII 字符(英文、数字、半角符号):4 字符 ≈ 1 token
* - 其他 Unicodeemoji 等):1 字符 ≈ 1 token
*
* 对比旧的 `length / 2` 方案:
* - 中文场景:估算准确度从 ~50% 提升到 ~90%
* - 英文场景:从偏低变为接近真实
* - 混合场景:更贴近实际 token 消耗
*
* 仍为估算值(无 tiktoken 依赖),但留了 80% 触发阈值的缓冲。
*/
// 中日韩统一表意文字 + 全角标点 + 日文假名 + 韩文谚文
const CJK_REGEX = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/;
/**
* L-17 修复: 提取魔法系数为命名常量,便于统一调整
* @see project_memory.md — Token estimation coefficients
*/
const CJK_TOKEN_RATIO = 1.5; // 中文字符(含全角标点、日韩文):1 字符 ≈ 1.5 token
const ASCII_TOKEN_RATIO = 0.25; // ASCII 字符(英文、数字、半角符号):4 字符 ≈ 1 token
const OTHER_TOKEN_RATIO = 1; // 其他 Unicodeemoji 等):1 字符 ≈ 1 token
const MSG_OVERHEAD_TOKENS = 4; // 每条消息的结构性开销(role、分隔符,参考 OpenAI 规范)
/**
* 估算字符串的 token 数
* @param text 待估算的字符串(可为 null/undefined,视为 0 token
* @returns 估算的 token 数
*/
export function estimateStringTokens(text: string | null | undefined): number {
if (!text || text.length === 0) return 0;
let cjkCount = 0;
let asciiCount = 0;
let otherCount = 0;
for (const ch of text) {
if (CJK_REGEX.test(ch)) {
cjkCount++;
} else if (ch.charCodeAt(0) < 128) {
asciiCount++;
} else {
otherCount++;
}
}
// L-17 修复: 使用命名常量替代魔法数字
return Math.ceil(cjkCount * CJK_TOKEN_RATIO + asciiCount * ASCII_TOKEN_RATIO + otherCount * OTHER_TOKEN_RATIO);
}
/**
* 估算多条消息的总 token 数
*
* 每条消息额外加 4 token 的结构性开销(role、分隔符等,参考 OpenAI 规范)
*
* @param messages 消息列表(content 可为 null,对应仅有 tool_calls 的 assistant 消息)
* @returns 估算的 token 数
*/
export function estimateMessagesTokens(messages: Array<{
content: string | null;
reasoningContent?: string;
toolCalls?: Array<{ args: Record<string, unknown> }>;
}>): number {
let total = 0;
for (const msg of messages) {
total += estimateStringTokens(msg.content);
if (msg.reasoningContent) total += estimateStringTokens(msg.reasoningContent);
if (msg.toolCalls) {
for (const tc of msg.toolCalls) {
total += estimateStringTokens(JSON.stringify(tc.args));
}
}
// L-17 修复: 使用命名常量替代魔法数字
total += MSG_OVERHEAD_TOKENS;
}
return total;
}