codeburn Open Design 提供方深度解析:事件流 JSONL 的会话发现、Token 归因与本地成本核算
2026/9/23 20:38:54 网站建设 项目流程

【免费下载链接】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

项目地址:https://gitcode.com/gh_mirrors/co/codeburn
点击查看免费下载

Open Design 是 codeburn 所追踪的 37 类 AI 编码工具/代理之一,它以"每次运行一个事件流 JSONL 日志"的方式落盘,token 用量附着在周期性的usage事件上而非每条会话轮次上。本文以 docs/providers/open-design.md 为骨架,结合 src/providers/open-design.ts 的实现与 tests/providers/open-design.test.ts 的测试夹具,完整讲解其日志目录发现、事件格式解析、缓存读取与推理 token 的计费归因、跨运行去重机制,以及排查"成本为 $0"类问题时的调试清单。

Open Design 在 codeburn 中的定位

在 codeburn 的提供方体系中,Open Design 属于核心提供方(core provider):它被静态导入并常驻内存,而非像 Antigravity、Warp、Vercel AI Gateway 等提供方那样按需懒加载。这一点在 src/providers/index.ts 与 src/providers/index.ts 的coreProviders数组中可以确认——openDesign与 Claude、Cline、Codex、Gemini 等一并列在 eager 列表内。

// src/providers/index.ts(节选) import { openDesign } from './open-design.js' // ... const coreProviders: Provider[] = [ claude, cline, clineCli, /* ... */ openDesign, pi, omp, /* ... */ ]

作为实现事实,tests/providers/open-design.test.ts 中有一条专门断言allProviderNames()包含'open-design',保证该提供方始终注册在--provider参数校验的白名单中。

其整合方式非常"轻薄":读取 Open Design 代理在本地落盘的运行日志,解析出每次调用的模型、token 与成本,喂给 codeburn 统一的分组、报表与 dashboard 管线,不涉及网络请求、配额接口或 provider 级缓存。

数据来源:默认目录、环境变量覆盖与多形态目录发现

各操作系统默认路径

Open Design 的运行日志按平台存放在应用数据目录下,src/providers/open-design.ts 的getOpenDesignDir()platform()分派:

OS默认路径
macOS~/Library/Application Support/Open Design
Windows%APPDATA%/Open Design
Linux~/.config/Open Design

Windows 分支有一个细节:当%APPDATA%环境变量缺失时,会回退到~/AppData/Roaming,避免join得到非法路径。

环境变量覆盖

设置$CODEBURN_OPEN_DESIGN_DIR可以覆盖上述所有默认路径,这在调试和测试中尤其有用(详见下文"调试清单")。源码中的常量ENV_DIR = 'CODEBURN_OPEN_DESIGN_DIR'(src/providers/open-design.ts)在getOpenDesignDir()第一行就被读取:只要该变量存在,就直接作为基目录返回。

多形态目录发现逻辑

Open Design 的安装形态并不统一,可能把日志根目录以namespaces根、data目录、runs目录或普通根目录四种形态之一暴露出来。为此 src/providers/open-design.ts 的discoverOpenDesignSessions()依据基目录的 basename做分支:

  • runs目录:直接对该目录做逐 run 扫描,namespace 由其父目录推导;
  • data目录:扫描其下的runs子目录;
  • namespaces:遍历每个 namespace 子目录,再进入<ns>/data/runs扫描;
  • 普通根目录:同时尝试<base>/data/runs<base>/runs两种形态,若存在namespaces子目录也一并遍历。

无论从哪种形态进入,最终都会被归一化到<namespace>/data/runs/<runId>/events.jsonl这一标准形态。逐 run 扫描由discoverRunsDir()(src/providers/open-design.ts)完成:它读取目录下的每个子目录,只把其中确实存在且为常规文件的events.jsonl记为SessionSource,目录不存在或为空时静默返回空列表,不会抛错中断整个扫描。

一个关键细节是按解析后路径去重dedupeSources()(src/providers/open-design.ts)用一个Set对 source.path 去重。由于普通根形态会同时探测data/runsruns两个位置,若某个安装的目录结构存在重叠(例如runs恰好就是data/runs的父目录引用关系),模糊根不会导致同一个 run 被重复计数。

存储格式:逐行 JSON 的事件流

Open Design 每次运行生成一个events.jsonl文件,每行一个 JSON 事件对象,字段结构为:

{ "id": "...", "event": "...", "data": { ... }, "timestamp": "..." }
  • id:事件唯一标识,用于去重(见下文"去重机制");
  • event:事件类型,目前代码只消费startagent两种;
  • data:事件负载,按事件类型承载不同字段;
  • timestamp:事件时间,既可以是 ISO 8601 字符串,也可以是数字 epoch 毫秒值

事件类型语义

从 src/providers/open-design.ts 的解析循环可以归纳出三类有效事件:

  1. start事件:运行起始,data.model携带当前模型标识,用于为后续 usage 事件"播种"模型;
  2. agent+data.type === 'status':状态切换事件,data.model同样会更新当前模型;
  3. agent+data.type === 'usage':token 用量事件,data.usage中携带四个计数字段:
{ "event": "agent", "data": { "type": "usage", "usage": { "input_tokens": 1000, "output_tokens": 200, "cached_read_tokens": 50, "thought_tokens": 25 } } }

usage各字段含义如下:

字段含义
input_tokens本次调用的输入 token 总数,已包含缓存读取部分
output_tokens输出 token 数
cached_read_tokens其中命中缓存、按缓存读取费率计价的 token 数
thought_tokens推理(reasoning)token 数,按输出费率计价

解析的健壮性守卫

解析器对"脏数据"相当宽容,这体现在 src/providers/open-design.ts 的一组小工具函数上:

  • parseEvent()(L53-L63):对每一行先 trim,空行跳过,JSON.parse失败或解析结果不是普通对象时返回null并跳过——单行损坏不会拖垮整个 run;
  • stringValue()(L36-L38):只接受非空字符串;
  • tokenValue()(L40-L42):只接受有限正数,负数、NaNInfinity、非数字一律归一为 0,防止损坏日志产生负 token 进而污染聚合总额;
  • timestampValue()(L44-L51):字符串原样保留,数字按new Date(value)转为 ISO 字符串,无法解释的数字返回空串。

其中数字时间戳的兼容处理有专门测试覆盖:run-mixed夹具中第二条 usage 事件的时间戳是数字1782122405000,tests/providers/open-design.test.ts 验证经parseAllSessions与日期范围过滤后,其timestamp被正确归一化为2026-06-22T10:00:05.000Z,且在跨日聚合中不会被误排除。

Token 归因与本地成本核算

先减缓存、后计费

Open Design 的input_tokens字段是含缓存读取的总额。如果直接把它当作新鲜输入去计价,缓存命中的部分就会被按全价输入费率重复计费。因此解析器在调用计费函数前先做减法(src/providers/open-design.ts):

const uncachedInputTokens = Math.max(0, usage.inputTokens - usage.cacheReadTokens)

Math.max(0, …)同样是为了防御损坏数据:若缓存读取数异常大于输入总数,不会产生负的"新鲜输入"。

推理 token 折入输出

thought_tokens属于推理链 token,按业界惯例以输出费率计价。解析器将其直接并入输出参数再传给calculateCost(src/providers/open-design.ts):

const costUSD = calculateCost( currentModel, uncachedInputTokens, usage.outputTokens + usage.reasoningTokens, 0, // cacheCreationTokens usage.cacheReadTokens, 0, // webSearchRequests )

对应 src/models.ts 的calculateCost(model, inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, webSearchRequests, speed):缓存读取 token 走cacheReadCostPerToken档位,缓存写入档位在此传 0,推理 token 因并入outputTokens而按outputCostPerToken计价。测试中也用同一函数反算验证:run-mixed夹具中 codex 调用期望costUSD ≈ calculateCost(model, 950, 225, 0, 50, 0)(tests/providers/open-design.test.ts),即输入 950 = 1000 − 50 缓存、输出 225 = 200 + 25 推理。

由此产出的ParsedProviderCall记录把各分量拆开保留(src/providers/open-design.ts):inputTokens存减缓存后的新鲜输入,cacheReadInputTokenscachedInputTokens都记缓存读取数,reasoningTokens独立成字段,供报表分别展示。

未知模型与 $0 成本

成本计算完全在本地进行。若模型名不在定价表中,calculateCost返回 0,codeburn 会给出"no pricing data"提示(默认仅在CODEBURN_VERBOSE=1时输出明细),而不会虚构价格。因此"成本为 $0"有两种常见成因:一是模型本身无定价数据,二是模型从未被正确播种(见下节怪癖)。

Provider 级无缓存

文档明确:Open Design 提供方没有 provider 级缓存。每次扫描都重新读取磁盘上的events.jsonl;跨扫描的一致性不依赖缓存层,而是依赖下述去重键机制。

去重机制:open-design:<sessionId>:<eventId>

与其他提供方一样,codeburn 用去重键防止同一份日志被重复统计(例如多次运行codeburn扫描同一目录、或解析中断后重扫)。Open Design 的去重键构造规则(src/providers/open-design.ts):

open-design:<sessionId>:<eventId>
  • sessionId取运行目录名(即events.jsonl所在目录的 basename);
  • eventId取事件自身的id字段;
  • 若某事件没有id字段,则回退到行号计数器line-0line-1……(fallbackEventCounter)。

去重通过解析器共享的seenKeys: Set<string>实现:同一个Set实例被传入不同解析器乃至不同扫描轮次,usage事件若发现自己的键已存在则直接跳过。

测试对这一点做了直接验证:tests/providers/open-design.test.ts 用同一个seenKeysrun-mixed夹具连续解析两次,第一次产出 2 条调用,第二次产出 0 条——证明去重键确实跨解析轮次生效。另外夹具run-mixed中特意安排了两个id相同的 usage 事件evt-glm-usage),第二次解析被去重键拦截,说明同一 run 内重复 id 的脏数据也不会被重复统计。

已知边界与怪癖

先有模型,后有 usage

解析器维护一个currentModel状态:只有start事件或agent+status事件携带了模型之后,后续usage事件才会被计入;在模型未知前到达的 usage 事件会被直接丢弃,而不会归因到unknown模型。从源码看,这是if (!usage || !currentModel) continue(src/providers/open-design.ts)这一守卫的直接结果。设计意图很明确:宁可丢一条无法归属的用量,也不把成本挂到错误模型上造成报表污染。

这一行为在夹具中有正反两个例子:

  • run-start-seededstart事件先播种glm-5.2,随后到达的 usage 事件被正确计入(模型glm-5.2、输入 770、输出 33、缓存读取 7、推理 3);
  • run-no-usage:整个 run 只有startstatus和一条message事件、没有任何 usage 事件,解析结果为空——tests/providers/open-design.test.ts 断言calls长度为 0。

不追踪工具与 bash 命令

Open Design 的事件流目前不暴露逐调用(per-call)的工具名,因此每次产出的调用记录中toolsbashCommands恒为空数组。这意味着按工具维度(如 Skills & Agents 细分)的报表中,Open Design 的数据不会出现在工具维度统计里。

仅两个模型的显示名覆盖

modelDisplayName()维护了一个很小的映射表(src/providers/open-design.ts):

原始模型串显示名
openai-codex:gpt-5.5GPT-5.5
glm-5.2GLM-5.2
GLM-5.2GLM-5.2

除此之外的任何模型串原样展示。也就是说,模型重命名只作用于这两个特例,新模型接入 Open Design 时无需改动代码即可显示。

测试覆盖:三组夹具与七条用例

测试夹具位于 tests/fixtures/open-design/,采用真实的namespaces目录形态(namespaces/release-stable/data/runs/<runId>/events.jsonl),每条用例都基于真实文件而非内存字符串:

夹具 run场景
run-mixed混合模型运行:start播种 codex,usage 用数字时间戳,status切到 glm,第二次 usage 携带重复 id验证去重
run-start-seededstart先播种模型,验证无 status 前置也能归属
run-no-usage只有 start/status/message、无 usage,验证零产出

tests/providers/open-design.test.ts 共 7 条用例,覆盖:环境变量覆盖下的目录发现(3 个 run 全部发现、project 均为release-stable)、混合模型拆分为逐模型调用(含 token/时间戳/成本断言)、无 usage 运行零产出、start 播种模型、数字 epoch 时间戳在日期聚合中的归一化、跨解析器轮次去重,以及核心提供方注册断言。

测试通过process.env['CODEBURN_OPEN_DESIGN_DIR']指向夹具的data目录来驱动发现逻辑,并在afterEach中还原环境变量与临时缓存目录(tests/providers/open-design.test.ts)——这也从侧面演示了调试该提供方时的标准手法。

在 codeburn 中调试 Open Design 的清单

基于文档"When fixing a bug here"一节,结合源码与测试可整理出如下排查顺序:

  1. 确认安装实际使用的目录形态。先用codeburn doctor查看 probe 到的路径(probeRoots()返回的就是overrideDir ?? getOpenDesignDir(),src/providers/open-design.ts)。然后按需设置CODEBURN_OPEN_DESIGN_DIR指向具体的datarunsnamespaces根——这是把发现逻辑指向指定目录最快的方式,测试正是这么做的。
  2. 若成本为 $0,先查模型播种。检查该 run 的events.jsonl里,在第一个usage事件之前是否出现过携带data.modelstartstatus事件;若没有,usage 事件会被静默丢弃。其次再排查模型是否在定价表内(未知模型会输出 no pricing data 提示,可用CODEBURN_VERBOSE=1查看)。
  3. 新增测试夹具时,事件文件放入tests/fixtures/open-design/(保持namespaces/<ns>/data/runs/<runId>/events.jsonl形态),用例追加到tests/providers/open-design.test.ts,并在测试的beforeEach中把CODEBURN_OPEN_DESIGN_DIR指向新夹具目录。

这条清单同样适用于向 Open Design 事件流新增字段或新事件类型的场景:先确认目录形态与模型播种顺序,再以夹具驱动解析,就能在改动src/providers/open-design.ts后快速验证不回归。

【免费下载链接】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

项目地址:https://gitcode.com/gh_mirrors/co/codeburn
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询