OmniRoute 免费用量汇总 API 的 Catalog 数据源一致性修复:free-tier summary 路由如何接入 Radar 实时目录
2026/9/8 21:55:11 网站建设 项目流程

OmniRoute 免费用量汇总 API 的 Catalog 数据源一致性修复:free-tier summary 路由如何接入 Radar 实时目录

【免费下载链接】OmniRouteNever stop coding. Free MIT AI gateway: one endpoint, 352 providers (150+ free), 1200+ models Kimi, Claude, GPT, Gemini, GLM, DeepSeek, MiniMax. Works with Claude Code, Codex, Cursor, OpenCode, Cline & Copilot. Quota-aware auto-fallback, RTK+Caveman compression saves 15-95% tokens, MCP/A2A, Desktop/PWA. Built by 550+ contributors项目地址: https://gitcode.com/GitHub_Trending/om/OmniRoute

导读

本文围绕仓库中changelog.d/fixes/11550-free-tier-summary-catalog-source.md记录的一项修复展开:在 OmniRoute 网关中,GET /api/free-tier/summary此前无论 Radar 实时 Feed 是否生效,都只返回随版本冻结的 baseline 目录数据,导致"API 报告的免费用量"与"仪表盘展示的免费用量"不一致。修复后该路由与仪表盘复用同一套目录解析逻辑(getRadarCatalog),在响应中显式声明本次应答由哪个 Catalog 来源(baselineradar-overlay)提供、以及该来源真实的构建/整理日期,同时将支持者密钥(supporter-key)专属的 live Feed 数据严格限制给本实例已认证调用方。读完本文,你将掌握该 API 的完整响应契约、数据源裁决规则与鉴权边界,并能直接通过源码与单测验证这些行为。


一、问题背景:一个免费额度问题,两个互相矛盾的答案

OmniRoute 的免费额度体系有一套以"目录(catalog)"为唯一事实源的展示机制:

  • 随版本冻结的 baseline 目录:手工维护、随 release 发布的FREE_MODEL_BUDGETS,整理日期记录为FREE_CATALOG_CURATED_AT(见 open-sse/config/freeModelCatalog.data.ts)。
  • Radar 实时覆盖层(overlay):当RADAR_ENABLED开关打开且本地已同步 feed 缓存时,会通过getRadarCatalog()将 feed 中的新模型/新额度叠加在 baseline 之上,仪表盘各页面(如 Free-Tier Budget 卡片、free-tiers 页面)都读取这份"解析后的目录"。

修复前的缺陷正如该 changelog 所述:summary 路由始终回答 release-frozen 的 baseline 数字,却带着过期的整理日期;而 Radar overlay 又在后台刷新同一份目录给仪表盘看。结果是——两个入口、一个问题的两个不同答案,且"声称自己有日期"的那个入口恰恰是数据最旧的那个。相关分析也完整记录在单测文件头注释中,见 tests/unit/free-tier-summary-radar-overlay.test.ts。

这一点还叠加了一个语义陷阱:standalone 构建在部署时会把所有文件时间戳重写,如果实现用文件 mtime 冒充"目录更新时间",就会把一份过期目录宣传成刚更新过。因此修复的核心原则是只报真实应答来源自己的日期


二、修复方案:与仪表盘共用同一套 Catalog 解析

修复的实现集中在 src/app/api/free-tier/summary/route.ts,其核心逻辑为:

// Same catalog resolution as the dashboard screens: one source of // truth, with meta === null meaning "the baseline answered" (flag off, no // cache, or corrupt cache). const { entries, meta } = getRadarCatalog(); const serveOverlay = meta !== null && (meta.tier !== "live" || (await isAuthenticated(req))); const totals = serveOverlay ? computeFreeModelTotals({ excludeTosAvoid, entries: entries.map(toBudgetEntry) }) : computeFreeModelTotals({ excludeTosAvoid });

2.1 解析函数getRadarCatalog的裁决规则

getRadarCatalog定义在 src/lib/radar/index.ts,返回{ entries, meta },其中meta === null表示 baseline 应答。源码注释明确列出 baseline 生效的三种情形:

  • RADAR_ENABLED开关关闭;
  • 本地没有任何 feed 缓存;
  • 缓存存在但已损坏(payload 无法解析/签名校验失败)。

当 overlay 应答时,meta携带versiontiercommunity/live)与generatedAt(feed 自身的构建日期),其中generatedAt允许为null,代表"迁移前写入的旧缓存行没有记录构建日期"这一事实状态。

2.2 目录的合并与形状保持

当 overlay 生效时,路由先将 merge 后的条目经toBudgetEntry()投影回 baseline 的FreeModelBudget形状再送入汇总,这样 overlay 内部的额外字段(origin、provenance、capabilities 等)不会泄漏到对外契约中——无论哪个来源应答,公开的 JSON 结构完全一致

投影过程还有一个关键细节:hardStopGuaranteed是 baseline 手工维护的、feed 不携带的事实,因此路由预先建立provider:modelId→ 布尔值的映射(HARD_STOP_BY_KEY),在 overlay 应答时从 baseline 回读补上,避免降级丢失。见 src/app/api/free-tier/summary/route.ts。

汇总函数本身computeFreeModelTotals位于 open-sse/config/freeModelCatalog.ts,负责 per-model 池去重(每个共享池只计一次)、excludeTosAvoid过滤(排除tos === "avoid"enabled === false的条目)、计算steadyRecurringTokens/firstMonthRealisticTokens/boostMonthlyTokens/uncappedProviders等输出字段。


三、诚实标注:catalogSourcecatalogUpdatedAt

修复引入两个新字段用于回答"这是谁的数据、数据有多新":

字段取值含义
catalogSource"baseline"release-frozen 目录应答(开关关、无缓存、缓存损坏,或匿名访问 live feed 被拒)
catalogSource"radar-overlay"Radar feed 目录应答(解析后的实时目录)
catalogUpdatedAtbaseline 路径取手工维护常量FREE_CATALOG_CURATED_AT,绝不取文件 mtime
catalogUpdatedAtoverlay 路径meta.generatedAt,即feed 自己的构建日期;若为迁移前的旧缓存行则为null("未知就保持未知",绝不用下载时刻fetchedAt冒充)

源码实现见 src/app/api/free-tier/summary/route.ts。单测 tests/unit/free-tier-summary-route.test.ts 专门断言:catalogUpdatedAt必须等于FREE_CATALOG_CURATED_AT、必须是可解析的日期且不得晚于当前时间——因为它只允许报告真实的整理日期,而不能是构建时间戳。

由此,调用方(例如仪表盘预算卡片)可以直接区分:显示的额度是"跟随 release 冻结的基线"还是"已随 Radar feed 实时刷新"的版本。


四、鉴权边界:supporter-key 的 live Feed 不做公开转发

Radar feed 服务器在下载时刻就决定了内容等级:community feed 是免费公开目录;live feed 是支持者密钥(supporter-key)专属内容。本实例可以合法地重新供给自己持有的内容,但当实例暴露在公网时,绝不能变成付费内容的免费中继。因此修复后的 entitlement 规则是:

  • community tier→ 供给所有调用方(匿名与已认证均可见),因为这是公共免费 feed;
  • live tier→ 仅供给本实例已认证的调用方(经isAuthenticated(req)判定);匿名调用方回退到 release-frozen 的 baseline;
  • 开关关闭 / 无缓存 / 缓存损坏→ 行为与修复前完全一致(baseline)。

对应实现即serveOverlay = meta !== null && (meta.tier !== "live" || (await isAuthenticated(req))),认证工具来自 src/shared/utils/apiAuth.ts。这一边界被 tests/unit/free-tier-summary-radar-overlay.test.ts 全面覆盖,包括以下场景:

  • 开关关闭 → baseline 应答,诚实打上catalogSource: "baseline"
  • community + 匿名 / 已认证 → 均返回 overlay,catalogUpdatedAt等于 feed 构建日期GEN_AT,且不等于下载时刻FETCHED_AT
  • feed 中enabled: false的条目不进入 totals 与perModel
  • 迁移前缓存行(无generatedAt)→ 返回 overlay 但catalogUpdatedAt: null
  • live + 匿名 → baseline;live + 已认证 → overlay(本实例合法持有的额度);
  • 损坏缓存 → baseline 兜底,契约不变。

五、完整的响应契约

GET /api/free-tier/summary返回200JSON,测试见 tests/unit/free-tier-summary-route.test.ts。核心字段包括:

  • steadyRecurringTokens:文档化、按池去重后的月度经常性 token 总额(测试断言 ≥ 10 亿),是可对外宣传的"稳"数字;
  • firstMonthRealisticTokens:steady + 一次性注册赠额(仅首月),恒不小于steadyRecurringTokens
  • usedThisMonth:本月已用 token(来自sumUsageTokensThisMonth(),见 src/lib/db/usageSummary.ts);
  • remainingmax(0, steadyRecurringTokens - usedThisMonth)
  • boostMonthlyTokens:deposit-unlock 提升(如 OpenRouter $10 充值把免费池从 50 提至 1000 req/day 的约 2400 万/月),单独报告、绝不并入 steady(测试断言boostMonthlyTokens < steadyRecurringTokens,详见 open-sse/config/freeModelCatalog.ts 的FREE_TIER_BOOSTS);
  • uncappedProviders:永久免费但无公开 token 上限的供应商列表,列出但不求和;
  • perModel:per-model 条目数组(≥ 400 项,覆盖上千模型),形状同FreeModelBudget且携带enabled
  • modelCountheadline(含 "free tokens/month")等展示字段;
  • noCredentialProviders在服务端算好再下发——若在客户端推导会把整个 provider REGISTRY 打进浏览器 bundle,见 src/shared/utils/providerCredentialRequirement.ts;
  • catalogSource/catalogUpdatedAt:本修复新增的来源与日期标注;
  • 响应体中不包含任何"at /"形式的堆栈痕迹,防止异常路径泄露(两处测试均有断言)。

查询参数excludeTosAvoid=1会过滤tos === "avoid"的模型;测试确认该参数在 baseline 路径与 overlay 路径上都生效,但它只影响 summary 视图,不影响全局路由决策。

CORS 头允许*源、仅放行GET, OPTIONS,并有对应的OPTIONS预检处理器(见 src/app/api/free-tier/summary/route.ts)。


六、数据流全貌与仪表盘消费方

修复后的数据流可归纳为一条主线:dashboard 各页面(FreeBudgetCard 等)与/api/free-tier/summary走同一条getRadarCatalog()computeFreeModelTotals()链路,对外只通过catalogSource区分底座。仪表盘侧的消费组件见 src/app/(dashboard)/dashboard/usage/components/FreeBudgetCard.tsx/dashboard/usage/components/FreeBudgetCard.tsx),汇总页面见 src/app/(dashboard)/dashboard/free-tiers/page.tsx/dashboard/free-tiers/page.tsx)。

值得注意的设计分界:"统计/展示"与"决策"刻意读不同的源。展示使用"baseline 叠加 Radar feed 的解析目录",决策(模型导入、auto/*路由、GET /v1/models、浏览器预览)则只使用随包发布的FREE_MODEL_BUDGETS加上本地启发式规则(:free后缀、零定价、grantsFreeAccess),见 docs/reference/FREE_TIERS.md 的 "Two regimes" 章节与 src/shared/utils/freeModels.ts。展示可以随 feed 实时变好,决策必须离线可复现、且浏览器与服务端答案一致——本次修复不改变这一分界,只消除展示链路内部的"两个入口两个答案"。


七、结论与验证清单

11550这一修复把 free-tier summary 从"永远报告冻结数字 + 过期日期"改为"随 Radar feed 刷新 + 诚实标注来源与构建日期",同时守住了一条关键红线:支持者专属的 live Feed 只服务已认证调用方,匿名与公开实例绝不转发付费内容。

如果你想在本地复现或回归验证,可直接运行仓库中的两个单测文件:

  • tests/unit/free-tier-summary-route.test.ts —— 响应契约、去重总量、excludeTosAvoid、日期诚实性、无堆栈泄露;
  • tests/unit/free-tier-summary-radar-overlay.test.ts —— overlay/baseline 裁决矩阵、community/live 鉴权矩阵、迁移旧缓存与损坏缓存兜底。

结合 src/app/api/free-tier/summary/route.ts、src/lib/radar/index.ts 与 open-sse/config/freeModelCatalog.ts 三处源码阅读,即可完整掌握这套"单一事实源 + 诚实溯源"的实现。

【免费下载链接】OmniRouteNever stop coding. Free MIT AI gateway: one endpoint, 352 providers (150+ free), 1200+ models Kimi, Claude, GPT, Gemini, GLM, DeepSeek, MiniMax. Works with Claude Code, Codex, Cursor, OpenCode, Cline & Copilot. Quota-aware auto-fallback, RTK+Caveman compression saves 15-95% tokens, MCP/A2A, Desktop/PWA. Built by 550+ contributors项目地址: https://gitcode.com/GitHub_Trending/om/OmniRoute

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

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

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

立即咨询