用户要管账号、订阅、模型配置
这是一个云端产品域需求,不是本地 Agent runtime 自己就能解决的问题。
先判断这件事属于账号与商业化能力,读者的注意力应该先落到 console 业务域,而不是本地 CLI 或部署细节。
如果一开始就跳去看 infra 或容器,读者只会记住“怎么部署”,却记不住“为什么要有这一层产品职责”。
理解部署与基础设施方案,掌握云端部署策略。
对应路径:
sst.config.ts、infra/、packages/function/、packages/console/、packages/containers/前置阅读:第一篇 Agent 基础架构、第八篇 HTTP API 服务器、第十篇 多端 UI 开发 学习目标:理解 OpenCode 不只是一个本地 CLI,而是一套同时覆盖本地运行时、云端 API、控制台与基础设施编排的多层系统
这一章最容易写偏的地方,是把 OpenCode 误写成“一个部署到 Cloudflare 的 AI 工具”。
真实情况更接近下面这张图:
本地运行时
- packages/opencode -> CLI / TUI / 本地 server
- packages/app -> Web 客户端
- packages/desktop -> 桌面端
云端产品侧
- packages/function -> 分享、同步、外部集成 API
- packages/console -> 账号、计费、订阅、模型控制台
基础设施层
- sst.config.ts
- infra/app.ts
- infra/console.ts
- infra/enterprise.ts
交付层
- packages/containers
- GitHub Actions / 发布流程所以这一章更适合回答四个问题:
function 和 console这一章要回答的是:
packages/function、packages/console、infra/ 在整体架构里分别扮演什么角色建议先看这条线:
本地 opencode / app / desktop
-> 云端 packages/function 提供分享、同步、外部集成
-> packages/console 承担账号、订阅、模型控制台
-> sst.config.ts + infra/*.ts 编排云端资源
-> containers / CI-CD 负责构建和交付先看懂“系统被分成了几层”,再去分别研究 Cloudflare、SST、容器和控制台细节。
sst.config.ts 和 infra/app.ts,建立基础设施总图。packages/function/src/api.ts 和 packages/console/core/src/*,区分云端公共 API 与控制台产品域。infra/console.ts、infra/enterprise.ts 以及发布相关目录,理解交付层是怎么接上的。packages/opencode/src/server 和 packages/function/src/api.ts 不是同类服务端。如果只看产品表层,很容易把 OpenCode 理解成“一个 CLI,再加一点云端能力”。但从仓库结构看,本地部分本身就已经拆成了多入口:
packages/opencode:本地 CLI / TUI / serverpackages/app:本地 Web 客户端packages/desktop:桌面端根目录脚本也直接说明了这一点:
bun run dev -> 进入 packages/opencodebun run dev:web -> 进入 packages/appbun run dev:desktop -> 进入 packages/desktop所以本地开发更准确的理解是:围绕同一套核心语义,按不同终端选择不同入口。
这会直接影响你对“Agent 到底运行在哪里”的判断。
在当前仓库里:
packages/opencode这也解释了为什么前面几篇里你会看到:
换句话说,本地运行时本身就是架构层的一部分;云端不是来替代它,而是承接分享、同步、账号和控制台等产品侧能力。
这个仓库的一个核心特点,是把前端入口拆开,但尽量让后端语义一致:
| 入口 | 主要位置 | 角色 |
|---|---|---|
| CLI / TUI | packages/opencode | Agent 核心、本地服务、终端交互 |
| Web | packages/app | 浏览器端 UI |
| Desktop | packages/desktop | 桌面壳层,复用 UI |
对读者来说,理解这一层之后,再看云端部分就不会困惑:
云端不是拿来“替代本地 Agent”,而是给本地 Agent 之外的产品能力提供稳定落点。
sst.config.ts 是基础设施入口,不是业务入口 当前仓库的基础设施入口很清晰,就在 sst.config.ts:
export default $config({
app(input) {
return {
name: "opencode",
removal: input?.stage === "production" ? "retain" : "remove",
protect: ["production"].includes(input?.stage),
home: "cloudflare",
providers: {
stripe: { apiKey: process.env.STRIPE_SECRET_KEY! },
planetscale: "0.4.1",
},
}
},
async run() {
await import("./infra/app.js")
await import("./infra/console.js")
await import("./infra/enterprise.js")
},
})这段配置能看出三件事:
app / console / enterprise 三条线拆模块这个配置里非常值得初学者学习的一点,是 stage 设计:
retainprotect这体现的是一个非常典型的基础设施思路:
环境隔离不是靠约定,而是靠 IaC 默认值保证。
infra/app.ts 管的是公开产品面 infra/app.ts 主要负责三类资源:
api.<domain> 对应的 Cloudflare Workerdocs.<domain> 对应的文档站点app.<domain> 对应的 Web 客户端关键代码很直观:
export const api = new sst.cloudflare.Worker("Api", {
domain: `api.${domain}`,
handler: "packages/function/src/api.ts",
url: true,
})
new sst.cloudflare.x.Astro("Web", {
domain: "docs." + domain,
path: "packages/web",
})
new sst.cloudflare.StaticSite("WebApp", {
domain: "app." + domain,
path: "packages/app",
})这也说明 OpenCode 的“产品前台”并不是一个单体站点,而是拆成:
infra/app.ts 里最值得点出来的是 SYNC_SERVER 绑定:
args.bindings = $resolve(args.bindings).apply((bindings) => [
...bindings,
{
name: "SYNC_SERVER",
type: "durable_object_namespace",
className: "SyncServer",
},
])这说明 Cloudflare Durable Object 在这个系统里不是装饰性功能,而是承担了会话同步 / 分享相关能力的状态核心。
也就是说,云端 API 这部分并不只是“普通 REST 接口”,它还承载了实时状态同步。
packages/function 与 packages/console 的云端架构 先不要把这些目录都看成“云端后端”,先用一条账号 / 订阅 / 模型配置链路,分清每一层到底负责什么。
不要先把这些目录都看成“云端后端”。先顺着同一条账号、订阅、模型配置链路, 看清 function、console、infra 和 containers / CI 分别负责什么。
这是一个云端产品域需求,不是本地 Agent runtime 自己就能解决的问题。
先判断这件事属于账号与商业化能力,读者的注意力应该先落到 console 业务域,而不是本地 CLI 或部署细节。
如果一开始就跳去看 infra 或容器,读者只会记住“怎么部署”,却记不住“为什么要有这一层产品职责”。
识别这是账号与商业化相关需求,明确后续应该沿着云端产品层去找答案。
不直接决定怎么部署,也不等于本地 Agent 的运行时逻辑。
先分清这是不是控制台业务域问题,再决定往哪一层继续读。
packages/function 更像产品级公共 API packages/function/src/api.ts 这部分非常值得单独讲,因为它和 packages/opencode/src/server/ 不是一回事。
本地 server/ 更偏向本地运行时和客户端协作;
而 packages/function/src/api.ts 则是云端产品面向外部的能力,例如:
这说明仓库里存在两套“服务端”语义:
如果电子书不把这点讲清楚,初学者会很容易把 packages/opencode/src/server 和 packages/function/src/api.ts 混为一谈。
packages/console 是另一条完全不同的产品线 packages/console 这一组目录也很重要:
packages/console/app:控制台前端(SolidStart)packages/console/core:数据库与业务核心packages/console/function:认证与日志处理 Workerpackages/console/mail:邮件模板(JSX Email)packages/console/resource:资源定义它说明 OpenCode 不只是“本地编码 Agent”,同时还在建设一个云端控制台产品。
packages/console/core 是控制台的业务核心,采用经典的分层架构:
1. Schema 层(src/schema/):
account.sql.ts - 账户表结构user.sql.ts - 用户表结构workspace.sql.ts - 工作区表结构billing.sql.ts - 计费表结构model.sql.ts - 模型配置表provider.sql.ts - 提供商配置表key.sql.ts - API Key 管理表auth.sql.ts - 认证信息表benchmark.sql.ts - 性能基准表ip.sql.ts - IP 管理表2. 业务逻辑层(src/):
account.ts - 账户管理user.ts - 用户管理workspace.ts - 工作区管理billing.ts - 计费逻辑subscription.ts - 订阅管理model.ts - 模型配置provider.ts - 提供商管理key.ts - API Key 管理black.ts - 黑名单管理3. 基础设施层(src/):
drizzle/ - ORM 配置与类型aws.ts - AWS 服务集成context.ts - 请求上下文actor.ts - 操作者身份identifier.ts - ID 生成(ULID)lite.ts - 轻量级客户端4. 工具层(src/util/):
date.ts - 日期处理price.ts - 价格计算log.ts - 日志工具memo.ts - 缓存工具fn.ts - 通用函数env.cloudflare.ts - Cloudflare 环境变量为什么用 PlanetScale/PostgreSQL 而不是 SQLite:
| 维度 | 本地 Agent (SQLite) | Console (PlanetScale/PostgreSQL) |
|---|---|---|
| 使用场景 | 单用户、本地优先 | 多用户、云端协作 |
| 并发需求 | 低(单进程) | 高(多租户) |
| 数据规模 | 小(个人项目) | 大(所有用户) |
| 备份恢复 | 文件复制 | 自动备份、PITR |
| 扩展性 | 垂直扩展 | 水平扩展 |
| 运维成本 | 零配置 | 托管服务 |
这不是技术栈不统一,而是不同运行场景选不同存储。
1. 账户与用户体系:
Account (账户)
├── User (用户)
│ ├── email, name, avatar
│ └── createdAt, updatedAt
├── Workspace (工作区)
│ ├── name, slug
│ └── members, settings
└── Subscription (订阅)
├── plan: 'free' | 'pro' | 'enterprise'
├── status: 'active' | 'canceled' | 'past_due'
└── stripeSubscriptionId2. 计费与配额:
Billing (计费)
├── Usage (使用量)
│ ├── tokens, requests
│ └── date, modelId
├── Limit (限制)
│ ├── maxTokens, maxRequests
│ └── period: 'daily' | 'monthly'
└── Payment (支付记录)
├── amount, currency
└── stripePaymentId3. 模型与提供商:
Model (模型配置)
├── name, provider
├── enabled, pricing
└── capabilities
Provider (提供商)
├── name, type
├── apiEndpoint
└── authConfig4. API Key 管理:
Key (API Key)
├── accountId, workspaceId
├── key (加密存储)
├── permissions
└── expiresAtConsole Core 有 60+ 数据库迁移文件,说明这是一个持续演进的系统:
migrations/
├── 20250902065410_fluffy_raza/ # 初始 Schema
├── 20250903035359_serious_whistler/ # 添加用户表
├── ...
└── 20260224043338_nifty_starjammers/ # 最新迁移迁移管理命令:
bun db # 进入 Drizzle Kit shell
bun db-dev # Dev 环境
bun db-prod # 生产环境
# 在 shell 中执行
drizzle-kit generate # 生成迁移
drizzle-kit migrate # 执行迁移
drizzle-kit push # 推送 SchemaConsole 提供了一套完整的模型配置管理流程:
# 更新模型配置
bun update-models
# 推送到不同环境
bun promote-models-to-dev
bun promote-models-to-prod
# 从环境拉取配置
bun pull-models-from-dev
bun pull-models-from-prod
# 限制配置同理
bun update-limits
bun promote-limits-to-dev
bun promote-limits-to-prod这套流程说明 Console 把“模型配置”当成了一种需要版本管理和环境隔离的资源。
这条产品线承担的核心职责:
infra/console.ts 体现的是“产品后台基础设施” infra/console.ts 里最明显的三类资源是:
比如:
const cluster = planetscale.getDatabaseOutput(...)
export const auth = new sst.cloudflare.Worker("AuthApi", {
handler: "packages/console/function/src/auth.ts",
})
new sst.cloudflare.x.SolidStart("Console", {
path: "packages/console/app",
})这一层和本地 Agent 没有直接等价关系,它是产品商业化和运营化所需的基础设施。
Console 基础设施的完整架构:
┌─────────────────────────────────────────────────────────┐
│ Console 前端 │
│ (SolidStart + Cloudflare Pages) │
│ - 用户界面 │
│ - 工作区管理 │
│ - 订阅与计费 │
└─────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Auth Worker │ │ Log Processor│ │ Stripe │
│ (认证授权) │ │ (日志处理) │ │ Webhook │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
└────────────────────┴────────────────────┘
│
▼
┌──────────────────┐
│ PlanetScale DB │
│ (PostgreSQL) │
│ - account │
│ - user │
│ - workspace │
│ - billing │
│ - model │
│ - provider │
└──────────────────┘关键设计决策:
仓库里有专门的 packages/containers/:
base/Dockerfilebun-node/Dockerfilerust/Dockerfiletauri-linux/Dockerfilepublish/Dockerfile这说明容器在当前项目里的定位更偏向:
而不是“所有开发都必须 Docker 化”。
从目录名就能看出来,每个镜像职责不同:
base:通用基础环境bun-node:兼顾 Bun 和 Node 的构建场景rust:为桌面或原生相关构建准备tauri-linux:桌面端 Linux 打包publish:发布链路这和前面讲的多端结构是对应的。
项目支持 CLI、Web、Desktop,自然也会需要多种交付环境。
对这个仓库来说,CI/CD 不是简单地跑测试后发布。
它至少承担三种职责:
所以你在写这一章时,重点不该只是“GitHub Actions 怎么写”,而是:
为什么多端、多包、多基础设施的项目必须把发布流程工程化。
从 infra/app.ts 和 infra/console.ts 可以直接看到几种生产特征:
logpush这些都说明这个仓库已经不是“个人工具仓库”阶段,而是产品化基础设施形态。
这一层如果只写“负载均衡、缓存、水平扩展”会比较空。
更贴近当前仓库的扩展性,其实体现在:
api、docs、app、auth、console这类拆分能力,决定了项目能不能持续演进,而不是某一个组件单点性能有多强。
如果你的目标读者是 Agent 开发初学者,我建议这一章最终强调的是:
packages/function 与 packages/opencode/src/server 不是同一类服务端| 模块 | 位置 | 建议重点 |
|---|---|---|
| 基础设施入口 | sst.config.ts | stage、provider、模块入口 |
| 产品前台资源 | infra/app.ts | API Worker、Docs、WebApp、Durable Object |
| 控制台资源 | infra/console.ts | PlanetScale、Auth、Console、Stripe |
| 云端公共 API | packages/function/src/api.ts | share/sync、DO、外部集成 |
| 控制台核心 | packages/console/core | 云端数据库与业务域模型 |
| Console Schema | packages/console/core/src/schema/ | 数据库表结构定义 |
| Console 业务逻辑 | packages/console/core/src/ | account、user、workspace、billing 等 |
| Console 迁移 | packages/console/core/migrations/ | 60+ 数据库迁移文件 |
| 容器 | packages/containers | 构建与发布环境拆分 |
sst.config.ts 和 infra/ 下的主入口,建立本地运行时与云端资源的总图。packages/console/core/CLAUDE.md,理解 Console 的整体架构和职责。packages/console/core/src/schema/,了解数据库表结构设计。packages/console/core/src/ 下的业务逻辑文件(account、user、workspace、billing 等)。packages/function/ 和 packages/console/core/,区分“云端公共 API”和“控制台业务域”。packages/containers/ 或发布相关脚本,理解构建环境为什么也被单独成包。判断 OpenCode 的部署与基础设施为什么不能被简化成“把一个应用部署到 Cloudflare”,而必须拆成本地运行时、云端 API、Console 和 IaC 资源编排几层。
sst.config.ts 与 infra/app.ts,整理基础设施总入口怎样把 app / console / enterprise 三条线拆开。packages/function/src/api.ts 与 packages/console/core/src/,分别写出“云端公共 API”和“控制台业务域”各自解决什么问题。完成后你应该能说明:
packages/opencode/src/server、packages/function/src/api.ts、packages/console/core 不能混成一个统一后端。packages/opencode/src/server、packages/function/src/api.ts、packages/console/core 这三层不能被混成一个”统一后端”?理解了运行时和基础设施之后,再看测试体系会更清楚:
这就是第15章要解决的问题。
错误理解:SST(Serverless Stack Toolkit)是 OpenCode 服务端的核心框架,负责处理 HTTP 请求、路由、中间件等运行时逻辑。
实际情况:SST 是基础设施编排工具,不是运行时框架。它负责声明式地描述和部署 Cloudflare Workers、KV 存储、R2 对象存储等云资源,类似 Terraform 但用 TypeScript 编写。OpenCode 的 HTTP 服务器是 Hono,业务逻辑在 packages/opencode,SST 管理的是这些代码运行所需的云资源,两者职责完全不同。
packages/console 是 OpenCode CLI 的控制台界面,就是 TUI 的另一个名字 错误理解:packages/console 包含的是 TUI(终端用户界面)的代码,和 packages/app 是同类东西。
实际情况:packages/console 是一个独立的 Web 产品——OpenCode 的云端控制平面(dashboard)。它有自己的前端(console/app)、后端 API(console/core、console/function)和邮件服务(console/mail),管理用户账号、订阅、工作区和使用量统计。它和本地 CLI 的 TUI 是完全不同的产品,只是共享同一个仓库。
错误理解:把 OpenCode 部署到云端后,功能和本地运行完全相同,只是换了个运行位置。
实际情况:本地模式和云端模式有功能差异。云端部署(opencode serve)主要用于团队共享——多个用户可以连接到同一个 Agent 实例,但本地文件系统访问、执行本地命令等功能受限于服务器环境。console 层增加了用户认证、计费、工作区隔离等本地模式没有的能力。
错误理解:OpenCode 的核心服务端(packages/opencode)运行在 Cloudflare Workers 上,利用边缘计算提升响应速度。
实际情况:packages/opencode 运行在标准 Bun/Node.js 进程里,需要访问本地文件系统(读写文件、执行命令),这和 Cloudflare Workers 的无状态、无文件系统的边缘运行时完全不兼容。Cloudflare 在这里用于部署 packages/console 的 Web 界面和一些云端 API 函数,不是核心 Agent 逻辑的运行环境。
OPENCODE_SERVER_PASSWORD,远程访问就是安全的 错误理解:给 OpenCode 服务器设置密码后,就可以安全地将其暴露在公网上,供远程访问。
实际情况:OPENCODE_SERVER_PASSWORD 只是基础的 HTTP Bearer Token 认证,防止未授权访问。但 OpenCode 服务端能执行任意 shell 命令(通过 bash 工具)、读写本地文件,如果暴露在公网,攻击面极大。生产级的远程访问应该通过 VPN、SSH 隧道或零信任网络访问(ZTNA)等更强的安全机制保护,不能只依赖密码。
Source baseline
这一章先抓 OpenCode 不是单机 CLI 这件事:本地运行时、云端 API、控制台和 SST 基础设施是怎样被拆层并一起交付的。
devf8475649da1c如果本章对你有帮助
给本书仓库点一个 Star,是对作者最直接的支持。