【免费下载链接】codeburn
Free, local tool to track AI coding token usage and cost across 37 tools and agents (Claude Code, Cursor, Codex, Gemini and more), by model, project, and task. npx codeburn
CodeBurn 是一款本地运行、无需账号的 AI 编码 Token 用量与成本追踪工具,它直接读取 Claude Code、Cursor、Codex、Gemini 等工具在你磁盘上留下的会话文件,并按工具、模型、项目与任务维度产出账单级明细。本文聚焦其中 Qwen(Qwen Code CLI)这一集成:从数据读取路径、JSONL 存储格式、解析与成本计算流水线,到去重键、项目命名规则、工具调用提取等关键 Quirks,以及为它补充测试与修复 Bug 的完整路径。读完本文,你将能定位 Qwen 集成涉及的每一段代码,理解它的行为边界,并知道如何排查“工具缺失”“重复计数”“成本为 0”等典型问题。
集成概览:eager 加载的 31 个核心 Provider 之一
Qwen 是 CodeBurn 中最早、最基础的 Provider 集成之一,对应源码文件为 src/providers/qwen.ts,文档索引见 docs/providers/README.md 的 Eager(始终加载)表格。
在 src/providers/index.ts 的coreProviders数组中,qwen与claude、cline、codex、copilot、gemini等 31 个 Provider 一起被静态 import 并立即注册(src/providers/index.ts),这意味着:
- 每次扫描会话时 Qwen 解析器必然参与发现与解析,不存在懒加载失败被跳过的分支;
qwen出现在allProviderNames()返回的合法--provider取值集合中(src/providers/index.ts),你可以用codeburn --provider qwen或codeburn doctor --provider qwen单独限定到该集成;- 作为对照,
antigravity、forge、cursor、warp等才走懒加载路径(src/providers/index.ts)。
Provider 的通用契约定义在 src/providers/types.ts:每个 Provider 需要实现discoverSessions()(返回SessionSource[])、createSessionParser()(返回逐条产出ParsedProviderCall的SessionParser),以及可选的probeRoots()(供codeburn doctor展示并校验探测路径)。Qwen 集成的整体结构即围绕这三个方法展开。
数据来源:QWEN_DATA_DIR 与 ~/.qwen/projects 目录布局
Qwen 的会话数据读取路径由 src/providers/qwen.ts 的getQwenProjectsDir()决定:
function getQwenProjectsDir(): string { return process.env['QWEN_DATA_DIR'] ?? join(homedir(), '.qwen', 'projects') }优先级为:
| 优先级 | 取值 | 说明 |
|---|---|---|
| 1 | $QWEN_DATA_DIR | 环境变量,适合自定义安装目录或 CI 环境 |
| 2 | ~/.qwen/projects | 默认路径,即join(homedir(), '.qwen', 'projects') |
目录结构为两级嵌套:<projectsDir>/<projectDir>/chats/*.jsonl。discoverSessions()(src/providers/qwen.ts)的扫描逻辑是:
readdir(projectsDir)枚举所有项目目录,失败(如目录不存在)时直接返回空数组;- 对每个项目目录进入其
chats子目录,读取全部*.jsonl文件; - 每个文件经
stat确认是常规文件后,包装成SessionSource(含path、project、provider: 'qwen')加入结果。
值得注意的健壮性设计:若某项目没有chats子目录(readdir抛错),该目录被continue跳过而不是让整个发现失败;这与 src/providers/index.ts 的discoverOne隔离机制一脉相承——任何一个 Provider 的发现异常都不会拖垮整轮扫描,异常会以codeburn: skipped qwen discovery after an error: ...的形式告警一次后跳过。probeRoots()返回[{ path: projectsDir, label: 'projects' }],因此在codeburn doctor中可以看到实际探测的是哪个目录,用于区分“Qwen 未安装”与“QWEN_DATA_DIR 配错”。
存储格式:逐行 JSON 的 JSONL 会话文件
Qwen 会话文件是JSONL(JSON Lines)格式:每行一个独立 JSON 对象,代表一条会话事件。解析器在 src/providers/qwen.ts 用raw.split('\n')按行切分、过滤空行后逐行JSON.parse;解析失败的行走continue静默跳过(src/providers/qwen.ts),保证单行损坏不阻塞整条会话。
文件读取复用 src/fs-utils.ts 的readSessionFile(),该函数对超过MAX_SESSION_FILE_BYTES(128 MB)的文件会跳过并告警,避免超大文件拖垮内存。
每条记录的类型QwenEntry(src/providers/qwen.ts)包含的关键字段:
| 字段 | 类型 | 用途 |
|---|---|---|
uuid | string | 单轮(turn)唯一标识,参与去重键 |
sessionId | string | 会话标识,参与去重键 |
timestamp | string | 事件时间,参与周期归集 |
type | 'user'/'assistant'等 | 区分用户消息与助手回合 |
model | string | 模型名,缺失时回退'qwen-auto' |
message.parts | QwenPart[] | 文本、thought、函数调用等分段 |
usageMetadata | object | token 计数来源,见下文 |
QwenPart(src/providers/qwen.ts)是message.parts的单元结构:text(文本)、thought(思考标记)、functionCall(工具调用信封)与functionResponse(工具结果)。
解析流水线:从 JSONL 到 ParsedProviderCall
解析核心是createParser()返回的异步生成器(src/providers/qwen.ts),它对每一行执行如下分支:
1. 用户消息(type === 'user'):收集parts中非 thought 的文本,拼接后截取前 500 字符存入pendingUserMessage(src/providers/qwen.ts),作为下一条助手调用的userMessage附带到产出中,随后continue不产出调用。
2. 助手消息(type === 'assistant')且必须带usageMetadata:缺少usageMetadata的记录直接跳过(src/providers/qwen.ts)。
3. 零消耗过滤:当promptTokenCount与candidatesTokenCount同时为 0 时跳过(src/providers/qwen.ts),避免把无 token 消耗的占位回合计入报表。
4. 去重:以qwen:${sessionId}:${uuid}为键查重(见下文专节)。
5. 成本核算:将usageMetadata的四项计数映射到ParsedProviderCall的标准字段:
| usageMetadata 字段 | 映射到 ParsedProviderCall | 说明 |
|---|---|---|
promptTokenCount | inputTokens | 输入 token |
candidatesTokenCount | outputTokens | 常规输出 token |
thoughtsTokenCount | reasoningTokens | 思考 token,单独计数并计入成本 |
cachedContentTokenCount | cachedInputTokens/cacheReadInputTokens | 缓存命中读取的 token |
6. 产出调用:yield一个完整的ParsedProviderCall(契约见 src/providers/types.ts),字段包括provider: 'qwen'、model、四类 token 计数、costUSD、tools、bashCommands、timestamp、deduplicationKey、userMessage、sessionId等。
时间戳兜底:回退到文件 mtime
isoTimestamp()(src/providers/qwen.ts)会校验记录自带的timestamp:能解析为合法日期则转 ISO 格式返回;缺失或不可解析时回退到会话文件的 mtime(fileMtime)。这样即使个别行时间戳损坏,调用也能落在真实日期上,而不是落入空字符串被 src/parser.ts 的周期过滤器排除。
成本计算:thoughts 计入输出、缓存 token 单独计价
成本统一走 src/models.ts 的calculateCost(),Qwen 的调用方式是:
const costUSD = calculateCost(model, inputTokens, outputTokens + reasoningTokens, 0, cachedTokens, 0)值得展开的三个细节:
- 思考 token 计入输出:
thoughtsTokenCount被加入outputTokens一起按输出单价计费。Qwen 官方将 thought 与候选输出分开上报,但思考过程同样消耗模型输出配额,因此 CodeBurn 把它并入输出成本,同时保留reasoningTokens字段供报表展示思考量; - 缓存 token 以
cacheRead身份计费:cachedContentTokenCount作为第 5 个参数(cacheReadTokens)传入,按缓存读取单价计价,同时写入cachedInputTokens供模型效率分析使用;cacheCreationInputTokens固定为 0(Qwen 不单独上报缓存写入); - 未知模型返回 $0:若模型不在定价库中,
calculateCost返回 0 并向 stderr 输出提示(--verbose下可见),此时报表中该调用成本为 0——排查“Qwen 成本全为 0”问题时,先确认模型名是否已收录。
去重机制:qwen:<sessionId>:<uuid>与跨 Provider 的 seenKeys
Qwen 的去重键由三部分组成(src/providers/qwen.ts):
const dedupKey = `qwen:${entry.sessionId}:${entry.uuid}` if (seenKeys.has(dedupKey)) continue seenKeys.add(dedupKey)seenKeys是一个跨 Provider、跨文件共享的Set,在 src/parser.ts 等处被统一维护:解析器在处理每个调用前查询、处理后写入。这套共享去重机制的意义在于:
- 同一会话文件在增量解析中被重复读到(例如文件追加、缓存重建)时,已见键的调用不会二次计数;
- 不同 Provider 的数据若指向同一底层会话(极端场景),也能靠键前缀(
qwen:、claude:、gemini:等)天然区隔; - 该键最终落在
deduplicationKey字段,随会话缓存持久化(见 src/parser.ts 等缓存写入点),冷启动重放时同样生效。
原文档特别提醒的坑:部分 Qwen 构建在续接(resumed)会话时会重复生成 UUID。如果你的复现场景中“同一轮被计数两次”,先验证<sessionId>:<uuid>在你的数据里是否真的唯一——若 UUID 重复,单纯依赖该键无法去重,需要在修复时补充更稳妥的判据。
工具调用提取:固定信封结构与工具名映射
工具(function call)从message.parts的固定信封形状functionCall: { name, args }中提取,逻辑集中在extractTools()(src/providers/qwen.ts):
for (const part of parts) { if (part.functionCall?.name) { const mapped = toolNameMap[part.functionCall.name] ?? part.functionCall.name tools.push(mapped) if (mapped === 'Bash' && part.functionCall.args && typeof part.functionCall.args['command'] === 'string') { bashCommands.push(...extractBashCommands(part.functionCall.args['command'] as string)) } } }工具名映射表(src/providers/qwen.ts):
| Qwen 原始工具名 | CodeBurn 展示名 |
|---|---|
read_file | Read |
write_to_file | Write |
edit_file | Edit |
execute_command | Bash |
search_files | Grep |
list_files/list_directory | LS |
browser_action | WebFetch |
web_search | WebSearch |
ask_followup_question | AskUser |
attempt_completion | Complete |
未命中映射表的工具名原样保留(toolNameMap[raw] ?? raw),同时toolDisplayName()(src/providers/qwen.ts)在渲染层复用同一张表,保证报表展示一致。
两个派生行为值得注意:
- 当映射结果为
Bash且args.command是字符串时,会调用 src/bash-utils.ts 的extractBashCommands()进一步拆出实际执行的 shell 命令——该函数会剥离 ANSI 转义、剔除引号包裹的字符串内容,并按&&、;、|分隔符把复合命令切成片段(见 src/bash-utils.ts),从而支撑“按任务/命令统计”的报表维度; - 固定信封是脆弱点:
functionCall是当前 Qwen 版本的上报形状,原文档明确警告——如果未来 Qwen 重构工具调用格式,这里(extractTools的循环)会是第一个坏掉的地方。出现“工具全部缺失”类 Bug 时,应优先排查该循环对parts结构的假设是否仍然成立。
项目命名 Quirks:来自目录名的最后一段
discoverSessions()通过projectNameFromDirName()(src/providers/qwen.ts)从项目目录名推导项目名:
function projectNameFromDirName(dirName: string): string { const parts = dirName.replace(/^-/, '').split('-') return parts[parts.length - 1] || dirName }规则:去掉前导-后按-切分,取最后一段作为项目名。这意味着项目名完全来自文件系统路径,而不来自会话文件内的任何字段。由此产生的两个可推断后果:
- 同一项目放在两个不同路径下会被识别为两个项目(目录名不同 → 项目名可能不同),这是原文档明确记录的 Quirk;
- 带
-前缀或含多个连字符的目录名,只会取最后一段,因此目录命名本身会影响报表中的项目聚合粒度。
测试现状与修复指南
截至当前仓库,tests/providers/目录下已有claude.test.ts、cline.test.ts、codebuff.test.ts等 Provider 测试,但没有qwen.test.ts(docs/providers/README.md 索引表中 Qwen 一行的 Test 列为 none)。原文档将其标注为“已知的 good first issue”:为 Qwen 添加 fixture 数据与 fixture 驱动测试,让回归对所有人可见。
如果你要修复 Qwen 集成中的 Bug,按以下清单推进:
- 先加 fixture 与测试,再改逻辑。缺少
tests/providers/qwen.test.ts意味着任何行为变更都没有回归保护,回归不可见; - 症状是“工具缺失”:检查
extractTools()的函数调用提取循环(src/providers/qwen.ts),确认functionCall.name信封结构是否仍与当前 Qwen 版本匹配,以及toolNameMap是否需要补充新工具名; - 症状是“重复计数”:先确认
<sessionId>:<uuid>在你的复现数据中确实唯一——已知部分 Qwen 构建在续接会话时重复 UUID,此时该去重键失效,需要调整去重策略; - 症状是“成本为 0”:分两层排查——先确认
usageMetadata四项计数是否正常落入解析结果(可用codeburn doctor --provider qwen检查发现与解析健康度),再确认模型名是否在 src/models.ts 的定价库中;未收录的模型成本必然为 0 并伴随 stderr 提示。
在 CodeBurn 中验证 Qwen 集成
验证链路分为发现与解析两层:
codeburn doctor --provider qwen:只诊断 Qwen,展示probeRoots解析出的实际探测路径($QWEN_DATA_DIR或~/.qwen/projects)、发现的会话数、解析健康度;--json可输出机器可读结果(参见 docs/cli.md);codeburn overview --provider qwen:只看 Qwen 的用量与成本汇总;- 直接运行
npx codeburn(无账号、无注册,见 README.md)会在总览中看到 Qwen 按模型、按项目的明细行,与 Claude、Codex 等其余 30+ 个集成并列聚合。
由于 Qwen 集成是 eager 加载,只要本机存在~/.qwen/projects/*/chats/*.jsonl(或QWEN_DATA_DIR指向的等价目录),无需任何额外配置即可被扫描;若路径被改过,codeburn doctor --provider qwen能立刻区分“Qwen 未安装/无数据”与“环境变量配错”两种情况。
【免费下载链接】codeburn
Free, local tool to track AI coding token usage and cost across 37 tools and agents (Claude Code, Cursor, Codex, Gemini and more), by model, project, and task. npx codeburn
相关推荐
CodeBurn 集成 OpenClaude:Claude Code 分支的 JSONL 会话解析与成本核算全解析
CodeBurn 集成 OpenClaude:Claude Code 分支的 JSONL 会话解析与成本核算全解析 本文聚焦 CodeBurn 对 OpenCl
CodeBurn 解析 Droid(Factory CLI)会话:JSONL 数据源、会话级 Token 均摊与成本核算实现详解
CodeBurn 解析 Droid(Factory CLI)会话:JSONL 数据源、会话级 Token 均摊与成本核算实现详解 Droid 是 Factory
CodeBurn Forge 提供器集成解析:SQLite 会话扫描、成本核算与去重机制
CodeBurn Forge 提供器集成解析:SQLite 会话扫描、成本核算与去重机制 CodeBurn 是一款免费、本地运行、用于追踪 37 种 AI 编码
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考