Day 17 / 共 20 天 · 第 4 周 平台化进阶 · 加深版

配置分层与模型/成本

昨天(Day 16)讲了记忆分层;今天讲另一套分层——settings.json 五层怎么合并、/model 怎么选、请求怎么到 Anthropic、退出时那句"花了多少钱"怎么算的。你会发现全项目的分层哲学是同一套。贴真实枚举和定价表。

📍 你在整门课的位置 · 第 4 周「平台化进阶」(本日主题:配置怎么合并、模型怎么选、钱怎么算)
D16 会话/记忆 D17 配置/模型/成本 D18 TUI 深入 D19 远程/workflow D20 收官
L01

settings 五层优先级(真实枚举)

🤔 痛点:个人习惯、团队约定、公司红线,怎么并存又不打架? 你想全局默认用某个模型,但某个项目团队约定用另一个,你在本机又想临时改一下,公司还有些设置死活不准动——这么多来源,谁说了算?
💡 本质:配置分层 = 公司报销制度 五层 settings 就像公司报销制度:公司有默认规则(user 打底)、部门能覆盖(project)、你个人有临时特批(local/flag),但财务红线(policy 企业策略)谁都改不了、最高优先。规则一层层叠、越具体越优先,最后合并成"此刻实际生效"的一份。

Claude Code 的配置来自五个来源,后者覆盖前者真实代码src/utils/settings/constants.ts:7):

export const SETTING_SOURCES = [
  'userSettings',     // 用户全局:~/.claude/settings.json
  'projectSettings',  // 项目共享:.claude/settings.json(进 git)
  'localSettings',    // 本机项目:.claude/settings.local.json(gitignore)
  'flagSettings',     // 命令行 --settings 旗标传的
  'policySettings',   // 企业策略:managed-settings.json 或远程 API(最高)
] as const
1userSettings你的全局
2projectSettings项目,进 git
3localSettings本机项目,不进 git
4flagSettings命令行旗标
5policySettings企业策略(最高,覆盖一切)
为什么后者覆盖前者? 越具体/越权威的越靠后、优先级越高。你的全局偏好(user)打底,项目约定(project)覆盖它,你在本机对这个项目的临时调整(local)再覆盖,命令行现场指定(flag)更高,企业强制策略(policy)最高——组织能强制某些设置,个人改不了。这套"分层 + 后覆盖前"跟 Day 09 权限、Day 12 MCP、Day 16 CLAUDE.md 完全一致——整个项目的配置哲学是统一的,学会一处处处通。
L02

深合并与 policy 的特殊规则

合并逻辑(src/utils/settings/settings.ts:657):先以插件 settings 作最低基座,再用 lodash mergeWith 按启用顺序深合并。但 policySettings(企业策略)走不同规则——"first-source-wins"(首个来源胜出):remote → MDM(Windows 注册表/macOS plist)→ managed-settings.json,按此顺序取第一个有值的。

为什么 policy 用"首个胜出"而非"深合并"? 因为企业策略是"强制"性质——不能让下层来源"部分覆盖"上层策略(那样就能绕过管控了)。用"首个来源胜出"保证一旦某个高优先级策略源定了值,就是它说了算,下面的完全不参与合并。深合并 = 递归合并嵌套对象(你改一个字段,其它字段还保留)。MDM(移动设备管理)是企业统一下发配置的机制。远程策略源是本 fork 的企业向扩展,官方没有。
L03

请求怎么抵达 Anthropic

Day 03/06 讲了循环怎么调 callModel。真正发 HTTP 到 Anthropic 的是 src/services/api/claude.ts

  • 客户端工厂 getAnthropicClientclient.ts:84):构造 new Anthropic({ apiKey })
  • 发流式请求:anthropic.beta.messages.create({ ...params, stream: true })claude.ts:1917)。
  • 中枢生成器 queryModel:1048)处理流式响应(Day 03 讲过的 switch)。
为什么用官方 Anthropic SDK? 因为 Day 01 说过——它是逆向重写,devDependency 里有官方 @anthropic-ai/sdk。它不重造 HTTP 层,直接用官方 SDK 发请求、拿流式响应,自己重写的是上面的一切(循环、工具、UI)。这很务实:底层协议对接用官方 SDK(稳定),业务逻辑自己实现(可控)。beta.messages 用 beta 端点是为了拿较新的能力(prompt caching 等)。
L04

/model 与默认模型

/model 命令(type:'local-jsx',Day 11)弹一个模型选择面板。模型选择优先级(src/utils/model/model.ts:111getMainLoopModel):

  1. 用户明确指定的(ANTHROPIC_MODEL 环境变量 或 settings.model)。
  2. 否则按订阅档位给默认(:260):Max/Team Premium → Opus,PAYG/Pro/Enterprise → Sonnet。
为什么按订阅档位定默认模型? Opus 最强但最贵,Sonnet 性价比高。付费档位高的用户(Max)默认给最强的 Opus;普通档位默认给 Sonnet(够用又省)。这是"根据你付了多少钱给你配相应模型"的合理默认,你随时能用 /model 改。选完通过 onDone 把"已切换"消息插回对话(Day 11 讲的 local-jsx onDone)。
L05

主模型 vs 小快模型

Claude Code 其实同时用两个模型

模型用途怎么取
主模型(Opus/Sonnet)主循环,干真活getMainLoopModel
小快模型(Haiku)摘要、侧查询等杂活getSmallFastModelmodel.ts:42

小快模型的调用入口 queryHaikuclaude.ts:3396thinking: disabled)。Day 10 的 autocompact 摘要、各种后台判断都用它。

为什么要两个模型? 主对话要强模型(复杂推理);但很多"杂活"(把长历史压成摘要、判断个小问题)用便宜快的 Haiku 就够,用 Opus 是浪费。把贵模型只用在真正需要的地方,杂活派给便宜模型——这跟 gov-agents 教程里的"省钱三档"(Sonnet/Haiku/规则)是完全一样的成本纪律。一个成熟的 AI 应用一定会做这种"模型分级"。
L06

成本怎么算(真实定价表)

退出时那句"本次花了 $0.xx"是这么来的。真实的定价档位常量(src/utils/modelCost.ts:36):

export const COST_TIER_3_15  = { ... }   // Sonnet:输入 $3 / 输出 $15(每百万 token)
export const COST_TIER_15_75 = { ... }   // Opus 4/4.1:$15 / $75
export const COST_TIER_5_25  = { ... }   // Opus 4.5:$5 / $25
export const COST_TIER_30_150= { ... }   // fast mode:$30 / $150
// MODEL_COSTS:每个模型 → 对应档位(含 cache 读写单价)
export const MODEL_COSTS = { 'claude-sonnet-...': { COST_TIER_3_15, ... }, ... }
流式返回 usage各类 token 数 查定价表MODEL_COSTS tokensToUSDCosttoken÷1e6×单价 累加addToTotalSessionCost

实时计费发生在流式增量处(claude.ts:2357):calculateUSDCost(model, usage)addToTotalSessionCost。退出打印 hook useCostSummarysrc/costHook.ts:6)在 process.on('exit') 打印 + 保存。

流式 usage输入/缓存读/缓存写/输出 查 MODEL_COSTS该模型对应档位 token÷1e6×单价各类分别算 累加进本次session 总花费 边流边算,中断也不丢账;退出时把累计值打印出来
图注:一次响应的花费怎么从 token 数算出来并累加。
📝 举个例子:算一笔 Sonnet 的账 一轮请求 usage = 输入 20000 token、输出 800 token,模型是 Sonnet(档位 $3/$15 每百万)→ 输入费 = 20000÷1e6×$3 = $0.06;输出费 = 800÷1e6×$15 = $0.012 → 这轮约 $0.072,累加进 session 总额。退出时打印"本次花了 $0.07"。
为什么缓存 token 单独计价? 因为 Anthropic 的 prompt caching(Day 07/12 提过)——命中缓存的 token 便宜很多(约 1 折),写入缓存的略贵。所以 usage 里区分"普通输入/缓存写/缓存读",各按各的单价算,才准确。Day 06 讲的"边流边累加 usage",就是为这里的实时计费服务的——中断也不丢账。成本状态还会随 --continue 恢复(Day 16),跨会话累计。每百万 token 计价:$3/百万 意思是每处理 100 万个输入 token 收 3 美元。
L07

多 Provider(本 fork 扩展)

官方 Claude Code 只走 Anthropic(含官方 Bedrock/Vertex)。本 fork 扩展了一大堆供应商——作为"扩展"了解即可:

  • client.ts 里有 Bedrock / Azure Foundry / Vertex 分支。
  • 独立的非 Anthropic 供应商目录 src/services/api/{openai,gemini,grok}/
  • OpenAI 兼容多供应商注册表 src/services/providerRegistry//provider use,支持 cerebras/groq/deepseek)。
  • 甚至有 ChatGPT/Codex 模式(chatgptModels.ts)。
为什么加这么多供应商? 官方 Claude Code 绑定 Anthropic 模型。这个 fork 想让用户能用任意模型(OpenAI、Gemini、本地开源…)驱动同一套 Agent CLI。判断"是不是官方功能"的窍门(Day 01/19):看是不是 feature('...') 门控、包名 @claude-code-best/*、成片 stub。多 Provider 就属于这类扩展。核心的 Anthropic 调用、/model、成本追踪是官方真实机制。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • settings 五层优先级 + 为什么后者覆盖前者?
  • policy 为什么用"首个胜出"而非深合并?
  • 请求怎么到 Anthropic?为什么用官方 SDK?
  • /model 优先级 + 为什么按订阅档位定默认?
  • 主模型 vs 小快模型的分工(省钱纪律)?
  • 成本怎么算?缓存 token 为什么单独计价?

✋ 动手:对着真实代码读一遍

# 1. settings 五层枚举(L01)
sed -n '7,22p' src/utils/settings/constants.ts

# 2. 请求发送与客户端(L03)
grep -n "beta.messages.create\|getAnthropicClient" src/services/api/*.ts | head

# 3. 模型选择(L04)
sed -n '111,130p' src/utils/model/model.ts

# 4. 成本定价表(L06)
sed -n '36,120p' src/utils/modelCost.ts

# 5. 实操:/model 换模型,退出看成本
bun run dev   # 里面输入 /model,退出看花费
明天预告 · Day 18:Day 04 讲了 TUI 骨架,Day 18 深入细节——输入框多行编辑/粘贴/自动补全、键位系统、vim 模式、对话框浮层的"优先级仲裁"。前端交互的精华都在这。

← Day 16 会话记忆 Day 18 · TUI 深入 →