OpenCode Stats 统计服务解构:app / core / server 三层架构与本地运行实操
Stats 是 opencode 仓库中一个独立于 console 的统计站点,用于沉淀和展示推理用量、模型/供应商排名等统计数据。本文以 packages/stats/README.md 为骨架,结合 core、server、app 三个子包的源码与部署配置,说明 Stats 的分层结构、数据库设计、数据同步链路,以及如何在本地启动和类型检查整套服务。读完后你可以独立完成 Stats 站点的本地开发、理解其 Effect + Drizzle + SolidStart 的技术组合,并定位各层职责对应的具体源码。
Stats 是什么:独立于 console 的统计站点
packages/stats/README.md 给出的第一句定位是:Stats 是一个独立于 console 的站点(Stats is a separate site from the console)。运行时、数据库和域名服务放在 core,SolidStart 站点放在 app,可部署的服务入口放在独立子包中。
README 声明的子包职责如下:
app:SolidStart 前端站点;core:Effect 服务、应用配置、Drizzle schema/migrations 以及统计领域服务;function:调用core服务的 Lambda 处理器。
对照当前仓库的实际目录结构(packages/stats/ 下为 app、core、server 三个子包),从源码结构看,README 中提到的可部署入口包现在对应的是 server 子包:它不再是 Lambda,而是一个基于 Effect NodeHttpServer 的 Bun/Node HTTP 服务(入口文件),承担数据摄入与同步职责。README 的核心分层思想(站点 / 领域核心 / 可部署服务)在现状中仍然完整保留。
本地运行与类型检查:从仓库根目录出发的完整命令
启动站点
packages/stats/AGENTS.md 与 README 一致地指出,从仓库根目录执行 bun run dev:stats 即可启动 SolidStart 站点。根 package.json 中该脚本的真实定义是:
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev"
也就是说,它并不是直接起一个 Vite 进程,而是先进入 sst shell --stage=production 环境(加载 SST 生成的环境变量与资源信息),再在 packages/stats/app 目录下执行 vite dev。这解释了为什么 Stats 的本地开发必须“从仓库根目录”执行——子包自身无法获取 SST 注入的环境上下文。
app 子包的 package.json 中脚本为:
dev:vite dev --host 0.0.0.0build:vite buildstart:vite starttypecheck:tsgo --noEmit
类型检查
README 列出的三条 typecheck 命令:
bun run --cwd packages/stats/app typecheck
bun run --cwd packages/stats/core typecheck
bun run --cwd packages/stats/function typecheck
app 与 core 两个子包当前均可直接使用(两者 typecheck 脚本均为 tsgo --noEmit)。由于可部署入口包已由 function 演进为 server,实际执行时对应命令为:
bun run --cwd packages/stats/server typecheck
三个子包的依赖都声明了 engines.node >= 22,使用 Node/Bun 22+ 是运行前提。
core 子包:Effect 服务 + Drizzle 数据库的核心层
core 是整个 Stats 的领域核心,core/package.json 的 exports 划分了清晰的对外模块面:
.:总入口,聚合导出Athena、AppConfig、Database、GeoStat、StatsHome、Inference、ModelStat、ProviderStat、RetentionStat、Stat、Runtime、StatSync(见 src/index.ts);./athena、./config、./database、./r2-sql、./runtime、./stat-sync:各基础设施模块;./domain/*:领域服务按文件独立导出。
Effect 运行时装配
runtime.ts 展示了典型的 Effect 依赖注入装配方式:
const repoLayer = Layer.mergeAll(
ModelStatRepo.layer,
ProviderStatRepo.layer,
GeoStatRepo.layer,
RetentionStatRepo.layer,
).pipe(Layer.provide(databaseLayer))
export const layer = Layer.mergeAll(AppConfig.layer, databaseLayer, repoLayer)
export const runtime = ManagedRuntime.make(layer)
即:合并四个统计仓储层(Model / Provider / Geo / Retention),统一由数据库层提供依赖,再与 AppConfig 合并为 ManagedRuntime。app(SolidStart 服务端)与 server(摄入服务)都通过这一入口消费同样的服务集,保证两端行为一致。
应用配置:Effect Config + SST 资源
config.ts 定义了 AppConfigValue(stage + publicUrl):
const config = Config.all({
stage: Config.succeed(Resource.App.stage),
publicUrl: Config.string("PUBLIC_URL").pipe(Config.withDefault("http://localhost:3000")),
}).pipe(Config.map(decodeAppConfigValue))
stage 来自 SST 注入的 Resource.App.stage,publicUrl 读环境变量 PUBLIC_URL 且默认 http://localhost:3000。这说明本地开发时无需额外配置,站点默认按 localhost:3000 处理公开地址。
数据库:PlanetScale(MySQL)+ Drizzle
core 的依赖里同时出现 @planetscale/database 与 drizzle-orm,schema 使用 mysqlTable 定义。database/schema.ts 中两张核心表:
model_stat:以provider+model+provider_model为维度,包含周期列(grain/period_key/dataset/tier/client/source 等)、指标列与rank_by_tokens/rank_by_requests/rank_by_cost排名列;provider_stat:以provider为维度,额外带市场份额列与rank_by_sessions等排名列。
两者均建立以 (grain, period_key, dataset, tier, client, source, provider[, model]) 为键的唯一索引(uniq_model_period、uniq_provider_period),并配有面向排行榜查询的二级索引(如 idx_leaderboard_tokens 按 total_tokens 排序)。这与 app 端“模型/供应商排行榜”页面是一一对应的。
migrations 目录 core/migrations 目前包含 8 个迁移,其中 20260620000000_unique_users(去重独立用户统计)与 20260826000000_model_retention(模型留存)两个迁移只包含 migration.sql,说明存在手工 SQL 迁移的历史痕迹;配合 drizzle.config.ts 使用 drizzle-kit 管理。
core 提供的数据库运维脚本(见 core/package.json):
bun run --cwd packages/stats/core db:generate # drizzle-kit generate 生成迁移
bun run --cwd packages/stats/core db:migrate # 执行 src/migrate.ts
bun run --cwd packages/stats/core db:push # drizzle-kit push 直推 schema
bun run --cwd packages/stats/core db:studio # drizzle-kit studio 交互查看
bun run --cwd packages/stats/core db:check-unique-users # 校验唯一用户约束
bun run --cwd packages/stats/core db:ensure-unique-users # 落地唯一用户约束
bun run --cwd packages/stats/core honeycomb:backfill # 回灌 Honeycomb 数据
领域服务与可观测性
core/src/domain/ 下的服务划分:home(首页统计,含 home.test.ts)、inference(推理用量,含 inference.test.ts)、model、provider、geo(地域)、retention(留存)、stat(基础统计),另有 model-normalization.ts 处理模型名称归一化。core/src 下还有若干与数据管道直接相关的一等脚本:athena.ts(@aws-sdk/client-athena 查询)、r2-sql.ts(Cloudflare R2 SQL 查询)、stat-sync.ts(统计同步)、honeycomb-backfill.ts 与 ensure-unique-users.ts。
server 子包:数据摄入与同步服务
server/src/server.ts 使用 Effect 的 @effect/platform-node 起一个 Node HTTP 服务:
const ServerLive = NodeHttpServer.layerConfig(
() => createServer(),
Config.all({
port: Config.number("PORT").pipe(Config.withDefault(3000)),
host: Config.string("HOST").pipe(Config.withDefault("0.0.0.0")),
}),
)
路由层 Routes 建立在 Ingest.layer 之上(ingest.ts,依赖 @aws-sdk/client-firehose,用于消费/转发 Firehose 事件),并注册了优雅退出信号处理(shutdown.ts)。服务入口脚本为 bun src/server.ts,构建镜像使用 server/Dockerfile。
从部署配置 infra/stats.ts 可以确认整条链路的组装方式:
- 数据库:PlanetScale 数据库
opencode-stats(含开发分支StatsDatabaseBranch与独立密码StatsDatabasePassword),并通过sst.Linkable暴露给 app/core 链接使用; - 站点:
sst.cloudflare.x.SolidStart部署到 Cloudflare,路径为packages/stats/app,域名为stats.${domain}——即 Stats 是独立域名,与 console 完全分离; - 同步服务:
sst.aws.Service(StatsSyncService)使用packages/stats/server/Dockerfile打包,链接数据库、inference 事件、R2 SQL 及其认证配置。infra 中的注释提到该服务以约 5 分钟一次的周期拉取统计查询,说明 Stats 数据是近实时增量同步而非整库重算; - 本地辅助:
sst.x.DevCommand(StatsStudio)在packages/stats/core目录下启动数据库 Studio,方便本地连库。
app 子包:SolidStart 统计站点
app/package.json 显示站点技术栈为 solid-js + @solidjs/start + nitro,并直接依赖 @opencode-ai/stats-core(workspace 引用)与 @opencode-ai/ui,图表缩放使用 d3-scale,国家/地区文案使用 i18n-iso-countries。
路由结构(app/src/routes):
/(index.tsx):站点首页,配合 stats-runtime.ts 与 stats-cache.ts 在服务端装配核心服务并缓存查询结果;/compare/...:多套对比页面(family 对比、lab+model 跨供应商对比),包含雷达图(compare-radar.tsx)与对比卡片等组件;/[lab]/[model]:单模型详情(lab 为实验室/供应商标识);/api/health、/api/newsletter:健康检查与订阅接口。
国际化方面,app/src/i18n 内置 16 个语言文件(ar、br、da、de、es、fr、it、ja、ko、no、pl、ru、th、tr、uk、zh、zht 中的 16 种),并通过 context/i18n.tsx 与 lib/language.ts 在客户端/服务端协同工作。SEO 相关由 sitemap.xml.ts 生成站点地图,构建配置见 app.config.ts 与 vite.config.ts。
小结
- Stats 与 console 完全解耦:独立 SolidStart 站点(
app)、独立域名(stats.${domain})、独立 PlanetScale 数据库(opencode-stats); - 本地开发只需一条命令:
bun run dev:stats(本质是sst shell+vite dev);三个子包均可用bun run --cwd <子包> typecheck做类型检查; core以 Effect Layer 装配领域仓储,以 Drizzle(MySQL)管理model_stat/provider_stat等统计表,数据库脚本集中在core的 package.json 中;server负责 Firehose 数据摄入与周期性 stat-sync,是 Stats 数据链路的服务端入口;- 相关源码入口:core 运行时、数据库 schema、server 入口、部署配置。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00