在 Claude Code 中接入第三方模型后,状态栏虽然能展示模型、上下文占用和运行时长,却未必能给出可信的费用数据。面对 DeepSeek 的峰谷分时价格,需要从会话 transcript 读取每条消息的 Token 用量和时间戳重新计算,同时处理重复记录、子代理文件与未知模型等容易被忽略的问题。
Claude Code 的状态栏默认能显示模型、目录、上下文占用和时长,但看不到花了多少钱。
我给它加上了这一段:
[deepseek-flash] 0911_测试评估软件 git:main ~5
######---- 42% 1m23s ¥1.11 谷
¥1.11 是这个会话的累计花费,谷 表示此刻处在 DeepSeek 的空闲(谷时)计费时段——换成高峰时段会变成红色的 峰。
看起来挺简单,但真动手会发现三个不看源码就一定会踩的坑。这篇把方案和坑都写出来,也欢迎大家在评论区晒晒自己的状态栏。
状态栏的 stdin JSON 里其实有个 cost.total_cost_usd。但它在我这个环境下不能用,原因有两层:
第一层,它并不总是按你以为的方式算价。我这边模型走的是 DeepSeek 的 Anthropic 兼容端点:
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-flash"
}
}
total_cost_usd 是 Claude Code 按 Anthropic 的价目表匹配 model id 得出的,而 deepseek-flash 显然不在那张表里。
第二层,也是更根本的:DeepSeek 现在是峰谷分时计价,同一个模型峰时和谷时单价差 2 倍。Claude Code 给的是一个聚合标量,结构上就表达不了"哪部分钱是在峰时花的"。就算它能查到价,也只会用单一单价一路算到底。
所以结论很直接:自己从 transcript 算。
DeepSeek 在 2026 年 9 月 10 日 12:00(北京时间)调整了 flash 系列定价,单位是元 / 百万 token:
| 模型 | 输入·缓存命中 | 输入·缓存未命中 | 输出 |
|---|---|---|---|
deepseek-flash | ¥0.02 | ¥1 | ¥4 |
deepseek-v4-pro | ¥0.15 | ¥4.5 | ¥13.5 |
上表是空闲时段价,高峰时段 = 空闲价 × 2。
时段划分(以官方文档为准):
01:00–04:00 与 06:00–10:00,即北京时间 09:00–12:00 与 14:00–18:00换算成代码就一个函数:
PEAK_MULTIPLIER = 2
PEAK_HOURS_UTC = ((1, 4), (6, 10)) # [起, 止)
def is_peak(when):
t = when.astimezone(datetime.timezone.utc)
if t.weekday() >= 5: # 周末全天谷时
return False
return any(start <= t.hour < end for start, end in PEAK_HOURS_UTC)
注意这里我用的是 UTC,不做本地时区转换——因为时间戳本身就是 UTC,直接按 UTC 的星期几和小时判断,不受机器时区影响,也不会因为夏令时出岔子。
状态栏的 stdin JSON 里带了 transcript_path,指向本次会话的 JSONL 文件。每条 assistant 消息都带一份 usage:
{
"type": "assistant",
"timestamp": "2026-09-13T04:11:53.500Z",
"message": {
"id": "msg_xxx",
"model": "deepseek-flash",
"usage": {
"input_tokens": 890,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 106368,
"output_tokens": 1710
}
}
}
对应关系很清楚:
cache_read_input_tokens → 缓存命中价input_tokens + cache_creation_input_tokens → 缓存未命中价output_tokens → 输出价关键设计:时段按每条消息自己的 timestamp 判断,而不是按"现在几点"。这样跨过峰谷边界的会话会自动拆开算——上午 11:50 那几轮按峰时计价,下午 15:00 的按谷时计价,不会一刀切。
这是我第一个撞上的。Claude Code 在流式输出过程中会反复重写同一条 assistant 条目,同一个 message.id 在文件里出现很多次。
我在自己的一个真实会话上数了一下:
usage 行总数 296 → 唯一 message.id 只有 99 个
其中 174 条是重复,有一个 id 重复了 13 次
如果老老实实逐行累加,花费会高估约 2.8 倍。
去重很简单,按 message.id 建字典,见过就跳过:
if usage and mid and mid not in seen:
seen[mid] = (usage, entry.get('timestamp'), msg.get('model'))
这个更隐蔽。我一开始只扫主 transcript,算出来的数字总比感觉少一点。后来翻目录才发现,子代理(subagent)的 usage 是单独成文件的:
~/.claude/projects/<项目>/<session_id>.jsonl ← 主会话
~/.claude/projects/<项目>/<session_id>/
└── subagents/agent-xxxxxxxx.jsonl ← 子代理,单独文件
这些条目都是 isSidechain: true,在主 transcript 里一条都没有。实测漏掉它们会少算约 12%——毕竟我平时没少让子代理去跑搜索。
所以要把子代理目录一起扫进来:
if session_id:
subagents = os.path.join(os.path.dirname(transcript_path), session_id, 'subagents')
paths += sorted(glob.glob(os.path.join(subagents, '*.jsonl')))
另外去重要跨文件做(所有文件共用一个 seen 字典),避免万一有重复计入两次。
<synthetic> 条目不是真实调用transcript 里还有一种 model 字段是 <synthetic> 的条目,是 Claude Code 本地生成的消息(比如"No response requested"这类),不是真实的 API 调用。
它们也带 usage,但价目表里查不到这个模型。如果代码里直接 PRICES[model] 就会 KeyError 把状态栏搞崩。正确做法是查不到就跳过:
price = PRICES.get(model)
if price is None:
return None # 未知模型不计费,而不是报错
顺带说一句,状态栏脚本最忌讳抛异常——崩一次你看到的不是报错,是整个状态栏消失,还很难联想到是算钱算崩的。所以我把整段计价逻辑包在 try/except 里,出了任何问题就静默不显示花费段,绝不连累状态栏本身。
状态栏刷新很频繁,而 transcript 会长到好几 MB。我实测了一个 5.7MB 的文件:
json.loads:103ms预筛的道理很简单:文件体积主要是被工具结果撑起来的(读一个大文件、抓一个网页),而那些行根本没有 usage。所以先做一次廉价判断,把绝大多数行直接跳过:
for line in handle:
if '"usage"' not in line:
continue # 工具结果等大行在这里就被丢掉了
entry = json.loads(line)
再叠一层 3 秒缓存(缓存文件放 TEMP 下、按 session_id 命名),稳态下渲染开销几乎为零。冷渲染 137ms、热缓存 12.5ms。
这里我特意没做增量 offset 解析——复杂度和收益不成正比。3 秒的陈旧度对一个看钱的数字来说完全无所谓。
算钱的代码最怕"看起来对"。我用两个办法互相印证:
一是边界单测。时段函数最容易在边界上写错,所以把边界逐个钉了一遍:
周一 00:59 → 谷 周一 01:00 → 峰 周一 03:59 → 峰 周一 04:00 → 谷
周一 05:59 → 谷 周一 06:00 → 峰 周一 09:59 → 峰 周一 10:00 → 谷
周五 10:00 → 谷 周六 02:00 → 谷 周日 06:00 → 谷
二是和一份独立实现逐条对账。我另写了一份逻辑独立、不共享任何代码的求和脚本,在同一时刻读同一个文件,两边结果必须逐位相等。最后差值是 0.00e+00。
这一步还纠正了我一个错误。我原本想用"某个会话必须等于 ¥1.0591"当数字,结果怎么都对不上——因为那就是我当时正在用的会话,我一边验证,它一边在长。用会变的东西当基准是没意义的,改成两套实现对账才真正测到了实现本身。
价目表我单独抽成了一个文件 statusline_pricing.py,只有一张表和两个函数。原因是 DeepSeek 在 8~9 月连着调了两次价——以后改价只需要动这一张表,不用去渲染逻辑里翻。
状态栏第 2 行从:
######---- 42% 1m23s
变成:
######---- 42% 1m23s ¥1.11 谷
金额的小数位是自适应的(<¥1 显示 4 位,<¥100 显示 2 位),不然新会话一直是 ¥0.00 看不出动静。末尾的 谷/峰 是此刻的时段标记,绿/红配色——写代码前扫一眼就知道现在单价贵不贵。
这次改动本身不大,但坑都不在"写代码"上,而在"搞清数据到底长什么样"上。三个坑里有两个(重复条目、子代理单独成文件)光看文档是发现不了的,只能老老实实去数文件里的行。
如果你也在用 DeepSeek 这类第三方端点跑 Claude Code,那自带的 cost 字段基本都指望不上,这套从 transcript 算的思路可以照搬——只要把 PRICES 那张表换成你实际用的模型和价格就行。
最后想收个集:你的状态栏长什么样?
我现在是两行:第一行模型 + 目录 + git 分支和改动数,第二行上下文条 + 时长 + 花费。还想过加的东西有:GitHub CI 状态、当前 todo 进度、机器负载……但状态栏宽度有限,加多了反而看不清。
如果你也改过状态栏,欢迎在评论区贴出来——尤其是你觉得"加了之后再也回不去"的那一两个字段,我挺想抄作业的。代码片段、截图、或者一句话描述都行。
文中 DeepSeek 价格与时段规则来自官方定价页(2026-09-10 生效的调价)。价格会变,照抄前建议先核一遍官网。
ubuntu20.04怎么使用蓝牙连接手机互传文件?
ubuntu系统怎么选择最佳服务器?
5Mware虚拟机安装Ubuntu 16.04.5 图文详解
用 Pi Agent 连接 Codex 或 Claude 官方账号有封号风险吗?
Pi Agent 使用 Codex、Claude 或 Gemini OAuth 会导致账号被封吗?
AI Agent 为什么更喜欢命令行?CLI 与 GUI 的真正分工是什么?