Skip to content

Commit 626c980

Browse files
鲁工鲁工
authored andcommitted
feat: v1.8.0 usability and hardening upgrade
Close the biggest day-to-day gaps while keeping the live-gateway advantage (in-session model switching): - ccmr doctor: provider-side failures (expired keys, unactivated models, wrong endpoints) were only discoverable mid-session; now one command sends a tiny real request per model and surfaces the upstream error verbatim - ccmr claude auto-starts the gateway: removes the two-terminal flow - Hot reload of models.yaml/.env + ~/.ccmr global fallback + ccmr use: no more restart-to-apply or cwd-dependent config mismatches - ccmr stats + GET /usage: per-model request/token accounting - fallback chains: variant-level failover on upstream 5xx/429/timeouts - init guards .env into .gitignore: a stray git add -A must not publish provider keys Hardening: drop ESM-only node-fetch (CJS dist crashed on Node <22.12, making engines >=18 a false promise) in favor of built-in fetch; stream idle watchdog so a hung upstream cannot pin a connection forever; timing-safe inbound auth comparison; friendly EADDRINUSE error. Engineering: vitest suite (69 tests) incl. a DEFAULT_CONFIG<->YAML template consistency test that already caught two real drifts; GitHub Actions CI on Node 18/20/24 with a stale-dist check.
1 parent 5d98c46 commit 626c980

63 files changed

Lines changed: 4789 additions & 366 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build-and-test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
# 18 is the engines floor; verify it stays honest (built-in fetch, no ESM-only deps)
15+
node-version: [18.x, 20.x, 24.x]
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: ${{ matrix.node-version }}
22+
cache: npm
23+
24+
- name: Install dependencies
25+
run: npm ci
26+
27+
- name: Build
28+
run: npm run build
29+
30+
- name: Test
31+
run: npm test
32+
33+
- name: Verify committed dist matches source
34+
run: |
35+
if ! git diff --exit-code --stat dist/; then
36+
echo "::error::dist/ is stale - run 'npm run build' and commit the result"
37+
exit 1
38+
fi
39+
40+
- name: Smoke test CLI
41+
run: |
42+
node dist/cli.js --version
43+
node dist/cli.js models | head -30

IMPLEMENTATION_PLAN.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# v1.8.0 实施计划(易用性 + 风险修复)
2+
3+
按 review 结论分 5 个阶段实施,测试先行(vitest 基线 → 新功能 red-green)。
4+
5+
| # | 阶段 | 内容 | 状态 |
6+
|---|------|------|------|
7+
| 0 | 测试基建 | vitest + ConfigManager/ModelRouter 基线测试 + DEFAULT_CONFIG↔YAML 模板一致性测试 | ✅ 完成 |
8+
| 1 | doctor + init 防护 | `ccmr doctor` 真实连通性自检;init 自动 .gitignore `.env` | ✅ 完成 |
9+
| 2 | 自动拉起 + 版本提示 | `ccmr claude` 探测 /health,不可达自动拉起网关;版本不一致警告 | ✅ 完成 |
10+
| 3 | CI | GitHub Actions: Node 18/20/24 build + test + dist 一致性 | ✅ 完成 |
11+
| 4 | 全局配置 + 热重载 + use | `~/.ccmr/` 兜底;watchFile 热重载;`ccmr use <model>` | ✅ 完成 |
12+
| 5 | 用量 + failover | UsageTracker + GET /usage + `ccmr stats`;fallback 降级链 | ✅ 完成 |
13+
| 6 | 杂项风险 | 流式空闲超时、timingSafeEqual、无条件日志、EADDRINUSE、移除 node-fetch/chalk | ✅ 完成 |
14+
| 7 | 自测 + 发布准备 | 69 测试全绿;init/use/热重载/doctor/自动拉起/stats 全部真实运行验证;README;v1.8.0 | ✅ 完成 |
15+
16+
## 自测记录(2026-07-07)
17+
18+
- `vitest run`:69/69 通过;一致性测试当场抓到模板两处真实漂移(`default_variant: 5.2` 未加引号、缺 `kimi-k2.6` 别名)并修复
19+
- `ccmr init`(scratch git 仓库):`.env` 自动写入 `.gitignore`
20+
- 热重载:`ccmr use kimi` → 网关日志出现 `[reload]``/health` 的 default_model 免重启切换 ✅
21+
- `ccmr doctor`(真实 key,ccmr-start):7 ok / 3 fail / 18 skip;确认 seed-2.1-pro 已开通可用,暴露 seed-2.1-turbo 未开通、mimo-token-cn Key 失效 ✅
22+
- 自动拉起 E2E:`ccmr claude -p ... --gateway-port 8096`(空端口)→ 网关自动拉起 → DeepSeek 真实请求返回 `SELFTEST-OK`
23+
- `ccmr stats`:正确显示 1 次请求 39148/41 tokens ✅

README.md

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,56 +16,75 @@
1616
npm install -g claude-code-model-router
1717

1818
# 然后使用 ccmr 命令
19-
ccmr init
20-
ccmr start
19+
ccmr init # 初始化配置(git 仓库内自动把 .env 加入 .gitignore)
20+
# 或 ccmr init --global 写入 ~/.ccmr 全目录共享
21+
ccmr doctor # (可选)连通性体检,确认各模型可用
2122

22-
# 启动 Claude Code
23+
# 启动 Claude Code(网关未运行时自动拉起,无需单独开终端)
2324
ccmr claude # 第三方模型(网关模式)
2425
claude # 官方订阅(默认模式)
26+
27+
# 日常切换默认模型
28+
ccmr use kimi
2529
```
2630

2731
### 方式二:使用 npx
2832

2933
```bash
30-
# 1. 初始化配置文件
34+
# 1. 初始化配置文件(git 仓库内自动把 .env 加入 .gitignore)
3135
npx claude-code-model-router init
3236

3337
# 2. 编辑 .env 文件,填入 API Keys
3438

35-
# 3. 启动网关
36-
npx claude-code-model-router start
39+
# 3. (可选)连通性体检,确认各模型可用
40+
npx claude-code-model-router doctor
3741

38-
# 4. 新开终端,启动 Claude Code
42+
# 4. 启动 Claude Code——网关未运行时会自动拉起,无需单独开终端
3943
# 第三方模型(网关模式):
4044
npx claude-code-model-router claude
4145

4246
# 官方订阅(默认模式):
4347
claude
4448
```
4549

50+
> 也可以手动 `ccmr start` 前台启动网关(日志直接可见);`ccmr claude` 自动拉起的网关日志在 `~/.ccmr/gateway.log`
4651
4752
## 命令说明
4853

4954
```bash
50-
# 初始化配置文件
55+
# 初始化配置文件(git 仓库内会自动把 .env 加入 .gitignore)
5156
npx claude-code-model-router init
57+
npx claude-code-model-router init --global # 写入 ~/.ccmr,全目录共享一份配置
5258

53-
# 启动网关
59+
# 启动网关(models.yaml / .env 修改后自动热重载,无需重启)
5460
npx claude-code-model-router start
5561
npx claude-code-model-router start --port 9000 # 指定端口
5662
npx claude-code-model-router start --host 0.0.0.0 # 监听所有网卡(见下方安全提示)
5763

5864
# 查看可用模型
5965
npx claude-code-model-router models
6066

61-
# 启动 Claude Code(网关模式,使用第三方模型)
67+
# 切换默认模型(持久化写回 models.yaml,运行中的网关自动生效)
68+
npx claude-code-model-router use kimi
69+
70+
# 连通性体检:对每个已配 Key 的模型发一条微型真实请求
71+
# 一次性暴露端点错误、Key 失效、账号未开通模型等问题
72+
npx claude-code-model-router doctor # 检查全部
73+
npx claude-code-model-router doctor seed kimi # 只检查指定模型
74+
75+
# 查看网关的按模型用量统计(请求数 / 错误数 / tokens)
76+
npx claude-code-model-router stats
77+
78+
# 启动 Claude Code(网关模式;网关未启动时会自动拉起)
6279
npx claude-code-model-router claude
6380
npx claude-code-model-router claude --gateway-port 9000 # 自定义网关端口
6481

6582
# 启动 Claude Code(官方订阅)
6683
claude
6784
```
6885

86+
> **配置发现顺序**`-c 指定路径` > `./models.yaml` > `./config/models.yaml` > `./.claude-router.yaml` > `~/.ccmr/models.yaml``.env` 同理:`./.env` 优先,`~/.ccmr/.env` 兜底(环境变量 `CCMR_HOME` 可改写全局目录位置)。
87+
6988
> **安全提示**:网关默认绑定到 `127.0.0.1`(仅本机可访问)。网关会用你本地配置的各厂商 API Key 代理上游请求,因此任何能访问该端口的人都能消耗你的额度。
7089
> 若确需通过 `--host 0.0.0.0` 暴露到局域网,请务必设置环境变量 `CCMR_REQUIRED_AUTH_TOKEN`,此时调用方必须在 `x-api-key``Authorization: Bearer <token>` 中携带该令牌。未设置时绑定非回环地址会打印警告。
7190
@@ -214,6 +233,22 @@ MiMo Token Plan 的 Base URL 与购买套餐所在集群绑定。默认 `mimo`
214233

215234
可以自定义供应商、模型变体、别名等。运行 `init` 命令会生成 `providers -> variants` 结构的模板;旧版平铺 `models` 配置仍然兼容。
216235

236+
网关运行中修改 `models.yaml``.env`**自动热重载**(轮询检测,约 1 秒生效),加模型、换 Key 都不用重启。注意:`gateway.host` / `gateway.port` 变更仍需重启才能重新绑定。
237+
238+
#### 故障降级(fallback)
239+
240+
任意 variant 可声明降级链,上游返回 5xx/429 或连接失败(含超时)时按序切换到备选模型;4xx 客户端错误不会触发降级,流式响应一旦开始输出也不再切换:
241+
242+
```yaml
243+
providers:
244+
deepseek:
245+
# ...
246+
variants:
247+
v4-pro:
248+
model_id: deepseek-v4-pro
249+
fallback: [kimi-k2.6, glm-5.2] # 依次降级
250+
```
251+
217252
## 使用场景
218253
219254
### 双模式使用(配置完全隔离)
@@ -340,7 +375,8 @@ npx claude-code-model-router claude
340375
|------|------|------|
341376
| `/v1/messages` | POST | Anthropic Messages API |
342377
| `/v1/models` | GET | 列出可用模型 |
343-
| `/health` | GET | 健康检查 |
378+
| `/usage` | GET | 按模型的用量统计(请求数 / 错误数 / tokens,网关重启后清零) |
379+
| `/health` | GET | 健康检查(含网关版本号) |
344380

345381
## 开发
346382

@@ -365,6 +401,14 @@ ccmr start
365401

366402
## 故障排除
367403

404+
### 第一步:先跑 doctor
405+
406+
```bash
407+
npx claude-code-model-router doctor
408+
```
409+
410+
对每个已配 Key 的模型发一条微型真实请求,直接给出结论:连通(含延迟)/ 上游报错原文(Key 失效、模型未开通、端点错误)/ 未配 Key。绝大多数"模型用不了"的问题一条命令即可定位。
411+
368412
### 端口被占用
369413

370414
```bash
@@ -374,9 +418,10 @@ npx claude-code-model-router start --port 9000
374418

375419
### API Key 错误
376420

377-
1. 检查 .env 文件中的 Key 是否正确
378-
2. 确认账户有余额
379-
3. 运行 `npx claude-code-model-router models` 查看状态
421+
1. 运行 `npx claude-code-model-router doctor` 查看上游的具体报错
422+
2. 检查 .env 文件中的 Key 是否正确(修改后网关自动热重载,无需重启)
423+
3. 确认账户有余额
424+
4. 运行 `npx claude-code-model-router models` 查看 Key 配置状态
380425

381426
### 网关模式提示地区不支持
382427

@@ -388,6 +433,29 @@ DeepSeek Anthropic 兼容接口会忽略 `metadata` 字段,但某些 Claude Co
388433

389434
## 更新日志
390435

436+
### v1.8.0
437+
438+
**易用性**
439+
- 新增 `ccmr doctor [models...]`:对每个已配 Key 的模型发一条微型真实请求做连通性体检,一次性暴露端点错误、Key 失效、账号未开通模型等问题
440+
- 新增 `ccmr use <model>`:持久化切换默认模型(文本级改写 `default_model`,保留注释),运行中的网关自动生效
441+
- 新增 `ccmr stats``GET /usage`:按模型统计请求数 / 错误数 / 输入输出 tokens
442+
- `ccmr claude` 检测网关未启动时**自动拉起**(detached,日志在 `~/.ccmr/gateway.log`);检测到网关与 CLI 版本不一致时给出警告
443+
- 配置热重载:网关运行中修改 `models.yaml` / `.env` 约 1 秒内自动生效,无需重启
444+
- 全局配置目录:`~/.ccmr/models.yaml``~/.ccmr/.env` 作为兜底(cwd 优先),`ccmr init --global` 一键生成;`CCMR_HOME` 可改写位置
445+
- 新增模型降级链:variant 级 `fallback: [...]`,上游 5xx/429/连接失败时按序切换备选模型
446+
447+
**风险修复**
448+
- `ccmr init` 在 git 仓库内自动把 `.env` 加入 `.gitignore`,防止 API Key 被误提交
449+
- 移除 ESM-only 的 node-fetch(CJS 产物在 Node <22.12 会 `ERR_REQUIRE_ESM` 崩溃),改用 Node 18+ 内置 fetch,`engines: >=18` 恢复真实;同时移除未使用的 chalk 依赖
450+
- 流式转发增加空闲超时看门狗,上游挂死不再永久悬挂连接
451+
- 入站鉴权改为常量时间比较(`crypto.timingSafeEqual`
452+
- 端口被占用时给出友好错误提示(原为裸堆栈崩溃)
453+
- 配置热重载遇到损坏的 YAML 时保留旧配置继续运行,不再静默退化为默认配置
454+
455+
**工程化**
456+
- 引入 vitest 测试体系(69 个测试):路由解析 / URL 构建 / SSE 转发 / 鉴权 / 热重载 / failover / 用量统计,以及 DEFAULT_CONFIG 与 YAML 模板的一致性测试(当场修复了模板中 `default_variant: 5.2` 未加引号、缺失 `kimi-k2.6` 别名两处漂移)
457+
- 新增 GitHub Actions CI:Node 18/20/24 矩阵构建 + 测试 + dist 一致性校验
458+
391459
### v1.7.1
392460
- 修正 Doubao Seed 接入点:按量付费应走 `https://ark.cn-beijing.volces.com/api/compatible`(而非旧 Coding Plan 的 `/api/coding`),模型 ID 带版本后缀 `doubao-seed-2-1-pro-260628` / `doubao-seed-2-1-turbo-260628`
393461
- 新增 Agent Plan 订阅入口 `seed-plan``https://ark.cn-beijing.volces.com/api/plan`,需 `ARK_PLAN_API_KEY`),与按量付费 `seed` 区分,模式同 `step` / `step-plan`

0 commit comments

Comments
 (0)