CodexBar 模型定价元数据管道:models.dev 缓存、查询规则与自定义定价覆盖层
2026/9/13 17:32:56 网站建设 项目流程

CodexBar 模型定价元数据管道:models.dev 缓存、查询规则与自定义定价覆盖层

【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar

导读

本文深入剖析 CodexBar 的模型定价元数据管道:它如何以 models.dev 作为增量定价源,配合内置兜底价格表,为 OpenAI Codex 与 Claude Code 的本地会话成本估算提供统一、可离线、可覆盖的价格体系。读者将掌握定价缓存的存放位置与刷新机制、provider/model 双维度的精确查询规则、USD per 1M tokens 到 per-token 的单位换算逻辑,以及如何通过custom-pricing.json覆盖层精确修改某个模型在本地扫描中的计价,并理解价格指纹(fingerprint)为何能驱动下游缓存失效。

整体架构:models.dev 增量定价 + 内置兜底价格

CodexBar 的成本计算并不只依赖一份硬编码价格表。文档明确了它的核心设计:以 models.dev 作为增量定价来源(additive pricing source),与内置兜底费率(bundled fallback rates)并存。models.dev 覆盖不到的模型(例如刚刚发布、尚未收录的新模型)回落到仓库内置的价格表;一旦 models.dev 收录了该模型,后续刷新即优先使用在线数据。二者的分工体现在 CostUsagePricing.swift 中:内置表codexclaude两个字典以「每 token」为单位预置了一批常见模型的输入、输出、缓存读/写价格,而 models.dev 查询则作为更靠前的数据层。

在代码层面,模型的最终解析由CostUsagePricing.resolvedCodexPricing(model:...)完成,其返回结构CodexPricing同时携带阈值 token 数与超阈值价格带(thresholdTokens/inputCostPerTokenAboveThreshold等),说明价格解析不仅区分输入/输出,还支持长上下文切换价格带(见 CostUsagePricing.swift)。

数据源与本地缓存

数据源与缓存位置

定价元数据来自一个公开接口,无需任何 API Key:

  • 源 API:https://models.dev/api.json
  • 本地缓存:~/Library/Caches/CodexBar/model-pricing/models-dev-v1.json
  • TTL:24 小时

源码中这三个要素均有对应常量。ModelsDevClient默认 URL 即为该接口,请求使用 GET、超时 20 秒,并在收到非 2xx 状态码或 JSON 解析失败时抛出ModelsDevClient.Error(见 ModelsDevPricing.swift)。缓存文件的版本号与 TTL 定义在ModelsDevCache中:artifactVersion = 1ttlSeconds = 24 * 60 * 60,缓存文件路径由cacheFileURL拼装为Caches/CodexBar/model-pricing/models-dev-v<版本>.json(见 ModelsDevPricing.swift)。缓存内容是一个带版本号和抓取时间戳的归档(ModelsDevCacheArtifact),结构体还保留了fetchedAt供判断过期。

双入口:同步 lookup 与异步 refresh

管道对外暴露两个互补的入口(见 ModelsDevPricing.swift):

  • ModelsDevPricingPipeline.lookup(providerID:modelID:)同步读取最近一次有效的缓存归档并返回价格查询结果,供扫描器在遍历每一条 usage 记录时零延迟调用,不会触发网络请求。
  • ModelsDevPricingPipeline.refreshIfNeeded(now:cacheRoot:client:)异步检查缓存是否过期,过期才发起拉取,用于后台维护新鲜度。

此外还有一个面向「未知模型」的入口refreshForUnknownModelsIfNeeded(providerID:modelIDs:):当某条记录的模型 ID 在现有目录中查不到价格时,若距上次抓取已超过 15 分钟冷却期(modelsDevCatalogRetryInterval = 15 * 60),则触发一次刷新,并返回pricingAvailable/unavailable表示新价格是否因此可用。ModelsDevRefreshCoordinatoractor 会按缓存路径合并并发请求:同一路径上的并发刷新共享同一个 in-flight Task,避免 TTL 刷新与未知模型刷新重复下载;失败后 15 分钟内也不会重试(见 ModelsDevPricing.swift)。

原子写入与内存 memo 失效

文档强调两处实现细节,源码均有一一对应:

  1. 原子写入ModelsDevCache.save使用data.write(to: url, options: [.atomic])落盘(见 ModelsDevPricing.swift)。因此 macOS 与 Linux 上刷新已有缓存时,是「先完整写入新文件再替换」,不会先删除目标文件,中途断电或进程被杀也不会留下半截 JSON。
  2. 内存 memo 失效:解码 ~800KB 目录 JSON 代价很高,如果每条 usage 行都重新读取并解码会拖慢扫描。ModelsDevCacheMemo以「文件路径 + mtime + 文件大小」为键,缓存完整的加载结果(包括成功与失败两类结果,避免损坏缓存反复触发昂贵的解码);save成功后主动invalidate(path:),下一次load必然解码新文件(见 ModelsDevPricing.swift)。

刷新时的数据保全策略

拉取到的新目录并不会无条件替换旧缓存,ModelsDevPricingPipeline.performRefresh有三层保护(见 ModelsDevPricing.swift):

  • 合理性校验isPlausibleRefresh:要求新目录中anthropicopenai两个 provider 至少各存在一个有价格(isPriceable)的模型,直接拒绝空响应或残缺响应(见 ModelsDevPricing.swift)。
  • 兜底合并mergingFallbackPricing:models.dev 目录会随上游变动增删模型。刷新时若发现旧缓存中有价格、而新目录中已消失的模型,会以codexbar-fallback:前缀的键合并进新目录,保证历史模型价格不因上游删档而「失忆」(见 ModelsDevPricing.swift)。
  • 失败保底refreshStaleCache在刷新生效前先复查一次缓存是否已被其他并发刷新更新;刷新失败时返回false,旧的 last-valid 缓存依旧可读。

测试ModelsDevPricingTestsnetwork failure preserves last valid cacherefresh preserves cache when fetched catalog drops cached providerrefresh accepts model churn and preserves removed pricing as fallback等用例直接验证了上述行为(见 ModelsDevPricingTests.swift)。

查询规则:以 provider id + model id 双维度精确匹配

定价查询始终以provider id 与 model id 组成的二元组为作用域,防止两个 provider 下同名 model 或同名显示名互相串价。ModelsDevCatalog.pricing(providerID:modelID:)先把 provider id 归一化(去空白、转小写,见ModelsDevProvider.normalizeProviderID),再在对应 provider 的模型字典内查找;查找时会对模型 ID 生成候选序列(如去掉openai/前缀、把claude-xxx补成claude-xxx@default、剥离日期快照后缀-20251001等,见ModelsDevModelIDNormalizer.candidates),依次精确比对字典键或模型自身normalizedID,见 ModelsDevPricing.swift。

测试does not fall back across providers专门验证了隔离性:openai下查claude-sonnet-4-6anthropic下查gpt-4o-mini均返回nil(见 ModelsDevPricingTests.swift)。

Codex/OpenAI 侧的路由规则

对于本地 Codex 会话扫描,codexModelsDevPricingTargets(for:)负责把原始模型 ID 展开成候选(providerID, modelID)列表(见 CostUsagePricing.swift):

  • 裸的 Codex/OpenAI 模型 ID一律挂到 provider idopenai(常量codexModelsDevProviderID,见 CostUsagePricing.swift),并顺带尝试normalizeCodexModel后的规范化写法(例如gpt-5.6规范化为gpt-5.6-solgpt-reserve映射为 Luna,见 CostUsagePricing.swift)。
  • 带前缀的 provider 限定路由:只有当路由前缀落在codexModelsDevProviderIDs白名单(deepseekkimi-codingkimi-for-codingopenaiopencodeopencode-freeopencode-go,见 CostUsagePricing.swift)内才保留原路由,例如deepseek/deepseek-chat仍按deepseek计;kimi-coding会同时尝试kimi-for-codingopencode-free会同时尝试opencode
  • 未知前缀不计价:前缀不在白名单内的带路由 ID 返回空列表,保持 unpriced,绝不误并入 OpenAI 价格。

Claude 侧的一手厂商路由

Claude 会话日志里的模型 ID 走的是另一套「一手厂商」路由(claudeModelsDevPricingTargets/claudeModelsDevLookup,见 CostUsagePricing.swift):

  • 可辨识的裸 Claude 会话模型族按前缀归属一手厂商目录:claude-前缀归anthropicgpt-/o1/o3/o4等归openaigemini-/gemma-等归googlek3/k3[1m]kimi-for-codingkimi-/moonshot-moonshot(含kimi-for-coding),minimax-minimaxdeepseek-deepseek(见 CostUsagePricing.swift)。
  • 其他裸 ID 要求唯一命中:无法辨识归属的裸 Claude-session ID,会在全部一手厂商(anthropicopenaigooglemoonshotkimi-for-codingminimaxdeepseek,见 CostUsagePricing.swift)中查找,只有当恰好一个厂商命中时才计价;跨厂商歧义命中保持 unpriced(见 CostUsagePricing.swift)。
  • 显式路由不回落:带显式provider/model前缀的 Claude-session ID 只在该批准的显式路由上计价,绝不回落到其他厂商。

Kimi 的 k3[1m] 上下文别名

Claude 会话中常见的k3[1m]是 Kimi Code 文档化的「1M 上下文」别名。CodexBar 在kimi-for-coding路由下完成精确行查找之后,额外把k3[1m]追加解析为kimi-for-coding/k3(见 CostUsagePricing.swift)。注意细节:

  • 记录中的模型名(k3[1m])保持不变,不会被改写,只是价格解析落到k3行;
  • 其他上下文变体与付费 Moonshot 路由不会被推断;
  • 目录中k3的零费率只是「已知的估计值」,并不代表订阅或额外用量免费——这是文档特意强调的语义边界。

Vertex AI 上的 Claude 日志

当 Claude 会话来自 Google Vertex AI 时,对应的 models.dev provider id 为google-vertex-anthropic。测试supports provider scoped model normalization验证了google-vertex-anthropic/claude-sonnet-4-6anthropic/claude-sonnet-4-6能各自命中正确的价格(见 ModelsDevPricingTests.swift)。

计价单位:从「每百万 token」换算到「每 token」

models.dev 对外发布的价格单位是USD per 1M tokens,而 CodexBar 内部成本数学使用USD per token,换算在元数据层完成:

perToken = modelsDevCost / 1_000_000

源码中ModelsDevModel.pricing(providerID:providerName:)即执行该换算:input / unitoutput / unit,其中unit = 1_000_000.0;缓存读(cacheReadcacheReadInputCostPerToken)与缓存写(cacheWritecacheCreationInputCostPerToken)同样按此规则换算(见 ModelsDevPricing.swift)。

超 200K 上下文价格带:当 models.dev 包含cost.context_over_200k字段时,CodexBar 将其解析为「超过 200K token 之后」的价格带,并同样按 per-1M 规则换算。换算后的结构中thresholdTokens被置为200_000,并填充inputCostPerTokenAboveThresholdoutputCostPerTokenAboveThresholdcacheReadInputCostPerTokenAboveThresholdcacheCreationInputCostPerTokenAboveThreshold四个超阈值字段(见 ModelsDevPricing.swift)。

单位换算有测试覆盖:converts models dev per million token prices to per token prices断言 3/1M、15/1M、0.3/1M、3.75/1M 等原始值换算后的 per-token 结果,并验证thresholdTokens == 200_000及超阈值字段(见 ModelsDevPricingTests.swift)。

在成本计算阶段,超阈值价格带会被真正使用:codexCostUSD依据thresholdTokens判断整次请求是否进入长上下文计费,claudeCostUSD则以input + cacheRead + cacheCreationTotal是否超过阈值来切换价格带(见 CostUsagePricing.swift)。值得注意的是 Codex 侧还有一个codexPriorityInputTokenLimit = 272_000的优先级输入上限(见 CostUsagePricing.swift),与 models.dev 的 200K 阈值是两套独立机制。

自定义定价覆盖层(custom-pricing.json)

文件位置与平台差异

精确匹配的「标价覆盖」存放在平台 Application Support 目录:

macOS: ~/Library/Application Support/CodexBar/custom-pricing.json Linux: ${XDG_DATA_HOME:-~/.local/share}/CodexBar/custom-pricing.json

Linux CLI 走的是FileManager的 Application Support 目录(即 XDG data home),而不是~/.config。只把文件放到 XDG config 下会被忽略。源码中CostUsageCustomPricing.defaultFileURL正是通过AppGroupSupport.localFallbackDirectory定位该目录,并拼接固定文件名custom-pricing.json(见 CostUsageCustomPricing.swift)。

解析顺序与作用范围

  • 文件内的值一律是USD per 1M tokens
  • 对原生 Codex 会话扫描,解析顺序为overlay(覆盖层)> models.dev > builtin(内置表)。测试codex cost prefers overlay over bundled list pricesaggregate fallback consults the overlay before bundled rates直接验证了覆盖层优先于内置表(见 CostUsageCustomPricingTests.swift)。
  • 改文件即失效:文件内容以 SHA-256 生成fingerprint(见 CostUsageCustomPricing.swift),该指纹被拼入CostUsagePricingKey.codex(...)的定价键(见 CostUsagePricingKey.swift)。因此任何一次编辑保存都会使 Codex 定价指纹失效,下一次原生 Codex 扫描会重新加载费率。测试overlay fingerprint invalidates the Codex pricing key验证了这一点(见 CostUsageCustomPricingTests.swift)。

作用范围限制(重要):覆盖层目前只作用于原生 Codex/OpenAI 兼容会话的计价。Claude 的本地扫描器、Cursor 以及生产环境的 OpenCodex 快照加载都不读取该文件(OpenCodex 侧始终持有空覆盖层)。因此写入anthropic/claude-…这样的键不会改变任何 Claude 标价。

键的规范与完整 JSON 示例

  • 大小写不敏感(统一 trim + 小写归一化,见CostUsageCustomPricing.normalizeKey)。
  • 键可以是裸模型 ID(gpt-5.4),也可以是provider/modelopenai/gpt-5.4)。
  • 只有精确归一化后的键能匹配,不存在前缀或家族通配。
  • 同一模型两种写法并存时,裸键优先,provider 限定行被忽略。除非你就是想让裸键覆盖生效,否则不要同时定义两行。
{ "gpt-5.4": { "input": 1.25, "output": 10, "cacheRead": 0.125, "cacheWrite": 1.25 }, "openai/gpt-5.4-mini": { "input": 0, "output": 0 } }

查询时先查裸键、再查provider/model复合键的顺序在rates(providerID:model:)中实现(见 CostUsageCustomPricing.swift)。

字段规则

  • 0表示该 token 类别免费(不是未知)。测试overlay exact match uses per-million rates and treats zero as free验证了输入 0 费率参与求和时按 0 计算(见 CostUsageCustomPricingTests.swift)。
  • 缺字段保持未知:CodexBar 不会用 models.dev 或内置表去填补缺失字段。因此一个只写了input的局部覆盖行,整体是「未定价」而不是「覆盖层与目录混合价」。测试missing overlay fields stay unknown instead of falling throughmatching partial overlay stays unknown instead of using bundled list prices双双验证(见 CostUsageCustomPricingTests.swift)。
  • 负数与非有限数被忽略rate(_:)只接受有限且>= 0的数字,0是合法免费值(见 CostUsageCustomPricing.swift)。
  • 缓存字段接受替代拼写:cache_readcache_writecacheCreationcache_creation(见 CostUsageCustomPricing.swift)。
  • 测试隔离:测试进程(通过XCTestConfigurationFilePath.xctest后缀等环境/进程特征识别)永远不读开发者 Application Support 目录中的真实覆盖文件,load直接返回空覆盖层,测试只使用 fixtures 或空覆盖层(见 CostUsageCustomPricing.swift)。

测试保障:行为可验证

定价管道的行为在仓库中有系统性测试覆盖,是排查问题时的第一手参照:

  • ModelsDevPricingTests.swift:覆盖 models.dev 子集解析、按 provider/model 查询、跨 provider 不回落、per-1M 到 per-token 换算、过期缓存仍可读、网络失败保底、部分目录不覆盖、未知模型刷新与冷却、TTL 与未知模型刷新合并单次下载等。
  • CostUsageCustomPricingTests.swift:覆盖零费率、缺字段保持未知、覆盖层优先于内置表、指纹失效定价键、聚合路径同样优先覆盖层等。

这两份测试文件完整刻画了本文所述每一条规则的预期行为,无论是自行接入该管道还是排查「为什么这个模型没有价格」,都可以从中找到对应断言。

结语:三层价格体系的协作方式

至此可以完整概括 CodexBar 的定价元数据体系:custom-pricing 覆盖层(用户精确覆盖)→ models.dev 目录(在线增量、24h TTL 缓存、失败保底、合并兜底)→ 内置价格表(离线最后防线)。三层之间通过 provider id + model id 精确作用域隔离,通过 SHA-256 指纹串联缓存失效,通过单元测试锁定每一条规则。理解这套管道后,无论是调试「某模型价格不更新」、排查「为什么某条记录未定价」,还是为自己的私有模型添加本地标价,都能快速定位到对应的源码位置与测试用例。

【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar

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

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

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

立即咨询