ANUS 版本演进全解:免费模型路由、0.1.0 外观回归与提交署名机制(CHANGELOG 深度导读)
ANUS 版本演进全解:免费模型路由、0.1.0 外观回归与提交署名机制(CHANGELOG 深度导读)
本文以 CHANGELOG.md 为骨架,逐条拆解 ANUS(终端中的免费编码代理)从 0.2.0 到 0.2.2 及 Unreleased 的全部变更,并结合 lib/route.mjs、lib/coauthor.mjs、lib/look.mjs、extension/anus.mjs 等源码,讲清"每次请求自动路由到当下最聪明的免费模型"这一核心机制、提交署名(Co-Authored-By)的实现原理,以及界面外观回归背后的细节。读完本文,你将能解释 ANUS 的路由与退避(rest)策略、
anus setup的密钥存储方式、/free的状态分类,并理解窄终端崩溃修复和 git 版本兼容等工程细节。
一、为什么从 CHANGELOG 读懂 ANUS
ANUS 是一个"免费编码代理":它像付费编码代理一样读代码、改文件、跑命令,但每次请求都会路由到"今天正在应答的最聪明的免费模型"。CHANGELOG 是观察这个项目演进轨迹最直接的入口——0.2.0 奠定核心路由架构,0.2.1 修复稳定性并回归 0.1.0 的视觉外观,0.2.2 引入提交署名与 CLI 健壮性修复,Unreleased 则同步了各厂商免费额度政策的变化。
当前仓库版本为 0.2.2(见 package.json 的 version 字段),依赖 Node.js 22.19 及以上版本,核心引擎基于 @earendil-works/pi-coding-agent(MIT 协议)。
二、0.2.0:免费模型路由的诞生(架构基石)
0.2.0 是 ANUS 作为"免费编码代理"的正式起点,CHANGELOG 记录了四个核心能力,全部都有对应的源码实现。
2.1 安装即用:npm i -g @anus-dev/anus,然后 anus
项目以 npm 全局包形式分发(package.json 中 bin 指向 bin/anus.mjs)。启动入口在 lib/main.mjs:anus 直接在当前文件夹启动;首次运行时若无任何免费密钥,会自动进入密钥配置流程。
2.2 智能路由:每次请求都交给"当下最强的免费模型"
这是整个项目的灵魂。路由实现在 extension/anus.mjs 的 route() 函数中,配合 lib/route.mjs 的纯函数决策逻辑:
- 模型优先级列表:ANUS 维护一份排序的免费模型列表(lib/free-models.json),按
iq(Artificial Analysis 智力指数)排序,最佳模型排在最前; - 可用性过滤:只有你配置了密钥(
registry.hasConfiguredAuth)、且上下文窗口容得下当前对话(contextWindow < tokens * 1.15 + 4096会被跳过)的模型才会进入候选; - 失败即换:如果最优模型在回答前拒绝(限流、每日额度耗尽、过载),下一个模型立刻接管同一个请求,最多尝试 6 次(
MAX_TRIES = 6); - 纯函数可测试:lib/route.mjs 的
plan()、restFor()、orderModels()不依赖网络和引擎对象,因此路由规则在 test/route.test.mjs 中被独立验证。
2.3 anus setup:免费密钥的本地化管理
密钥配置在 lib/setup.mjs 中实现:
- 支持五家服务:OpenRouter、Google Gemini、Groq、Cerebras、Mistral(见 lib/providers.mjs);
- 密钥以引擎自身的格式写入
~/.anus/agent/auth.json,文件权限为0600(仅当前用户可读写),密钥只留在你的机器上; - 输入密钥时使用
askHidden()逐字符显示为*,避免密钥出现在终端回滚记录中; - 一个关键设计:
main()会默认删除 shell 环境中所有提供商的付费密钥变量(OPENROUTER_API_KEY等),确保"免费代理绝不会花你的钱";只有设置ANUS_ENV_KEYS=1时才允许使用环境变量密钥(lib/main.mjs)。
2.4 /free:查看模型的实时状态
在终端内输入 /free(注册于 extension/anus.mjs 的 registerCommand('free')),可以查看每个免费模型的状态,共分三类:
- awake(清醒):有密钥、未在休息,且最近通过了 ping 探测;
- resting(休息):因限流、额度或中断被暂时禁用,显示"还需休息多少分钟";
- no key(缺密钥):当前没有可用密钥的提供商。
ping 状态来自模型列表中的 ping: ok / flaky / down 字段(lib/free-models.json),表示该模型最近几小时是否回应过"hi"探测;orderModels() 会让 flaky 和 down 的模型沉到列表末尾。
2.5 模型列表每日刷新两次
模型列表从 anus.dev/free-models.json 拉取(默认地址,可用 ANUS_MODELS_URL 覆盖),缓存于 ~/.anus/agent/free-models.json,每 12 小时(一天两次)检查刷新(LIST_MAX_AGE_MS = 12 * 3_600_000)。刷新逻辑(refreshList())采用"先写临时文件、校验通过后 rename"的原子替换方式,并会回退到仓库内置的 lib/free-models.json 作为兜底,二者取"更新"的一份。
三、0.2.1:外观回归与稳定性修复
0.2.1 的标题是"ANUS 看起来又像 ANUS CLI 0.1.0 了",同时修复了四个实际问题。
3.1 0.1.0 视觉外观的完整回归
外观还原全部集中在 lib/look.mjs,并通过 test/look.test.mjs 保证每个宽度下不出错:
- 块状 Logo 与橙紫渐变:6 行 ASCII 方块字
ANUS,逐列按暖橙 → 品红的渐变上色(paintGradient/gradientAt),支持 24-bit truecolor 与 256 色终端降级; - 欢迎语与提示:保留 0.1.0 原文,如 "Welcome to ANUS - Autonomous Networked Utility System"、"Powered by the smartest free models • Born from AI, built by AI";
- 圆角输入框与
>提示符:BoxEditor与boxLines实现,输入框随文本长度自适应伸缩; - 回答前的
✦符号:starAnswer()给普通回答开头加✦,但遇到标题、列表、引用、表格或代码块时不加,保留其原有首行格式; - 加载短语回归:恢复 0.1.0 的加载提示池(lib/phrases.json,100+ 条),包括经典的 "Generating thrust...",工作状态行显示
✦ Generating thrust... (esc to cancel, 3s)并随时间更新; - 页脚回归:
/help命令恢复,页脚同时展示当前目录、沙箱状态和响应模型。
注意:这些视觉元素来自 ANUS CLI 0.1.0(Apache-2.0),版权声明见 NOTICE。
3.2 回答中途断流的模型:休息并让下个模型重试
这是一个重要的可靠性修复。此前,如果某个免费模型在回答中途断掉,回合会以错误结束;现在:
- 路由层识别"已开始回答后断流"(
spoke === true后收到 error 事件); - 将该模型标记为休息(
resting(keyOf(entry), ms)),休息时长由restFor()判定; - 通过
midStreamError()构造引擎能识别的错误文本,使引擎重试整个回合——重试时该模型正在休息,于是自然落到下一个免费模型(extension/anus.mjs)。
restFor() 对错误文本的分类策略(lib/route.mjs)是理解整个退避机制的关键:
| 错误特征 | 休息时长 | 作用域 |
|---|---|---|
| 401 / 密钥无效 / 未授权 | 6 小时 | provider(整个提供商的所有模型都休息) |
| 每日额度 / quota / billing / 403 | 直到次日 00:00 UTC | model |
| 上下文超长 / context_length_exceeded | 0(不休息) | model |
| 429 / rate limit / too many requests | 1 分钟 | model |
| 其他(过载等) | 5 分钟 | model |
测试用例 test/route.test.mjs 逐一验证了这些分类,包括"断流文本必须能让引擎重试"和"额度类文本不会阻塞重试"两条边界。
3.3 不再残留 free-models.json.tmp
模型列表刷新采用"临时文件 + 校验 + rename"的原子写入(refreshList()),校验失败会清理临时文件(rmSync(tmp, { force: true })),从根上杜绝 .tmp 残留。
3.4 页脚信息增强与窄终端自适应
页脚显示"哪个免费模型回答了本次请求 + 剩余上下文百分比"(footerLine())。空间不足时逐级降级:先去掉 "(see /help)",再缩短模型名(去掉 provider 前缀和 :free 后缀),最后只保留左侧路径。
3.5 窄于 84 列的终端不再崩溃
0.2.1 修复了一个真实事故:此前在宽度不足 84 列的终端启动会因头部行过长而崩溃。根因是引擎在"行宽超过终端宽度"时会崩溃,因此 lib/look.mjs 的每个渲染函数都调用 fit() 把行裁剪到终端宽度;宽度过窄时,块状 Logo 退化为单行 ANUS 文字。测试 test/look.test.mjs 用 1 到 200 的每一个宽度循环验证头部、工作行、页脚、输入框均不超宽。
四、0.2.2:提交署名、OpenRouter 排名与 CLI 修复
4.1 提交署名:Co-Authored-By: ANUS <noreply@anus.dev>
ANUS 代理执行的 git commit 会默认在末尾追加一行:
Co-Authored-By: ANUS <noreply@anus.dev>
这与 Aider、OpenCode 的做法一致。实现位于 lib/coauthor.mjs:
- 注入方式:不调用
git commit-tree等底层命令,而是用正则改写 agent 即将执行的 shell 命令,给每个git commit追加--trailer 'Co-Authored-By: ANUS <noreply@anus.dev>';支持git -C repo commit、git -c user.name='A B' commit等变体,以及&&、;串联的多条 commit; - 版本门槛:
git commit --trailer需要 git 2.32 及以上;coauthorEnabled()先检查ANUS_COAUTHOR环境变量,再运行git --version判断版本,旧版 git 完全不受影响(test/coauthor.test.mjs 覆盖了 2.25.1 返回 false、2.32.0 返回 true 等边界); - 关闭方式(二选一):
- 在
~/.anus/agent/settings.json中写入"anusCoauthor": false; - 运行环境变量
ANUS_COAUTHOR=0。 - 注意:设置文件中的开关会在启动时转成环境变量(lib/main.mjs),两条路径殊途同归。
- 在
4.2 OpenRouter 的 CLI 代理排名
ANUS 通过向 OpenRouter 请求附加识别头参与其 CLI 代理公开排名(extension/anus.mjs):
'HTTP-Referer': 'https://anus.dev',
'X-Title': 'ANUS',
'X-OpenRouter-Title': 'ANUS',
'X-OpenRouter-Categories': 'cli-agent'
X-OpenRouter-Categories: cli-agent 将 ANUS 归入 OpenRouter 的 cli-agent 排行榜;同时部分免费模型只服务具名编码代理,这些头也让模型提供方能正确识别请求来源。
4.3 anus --version / anus --help 的免写盘修复
issue #93 的场景:当主目录不可写(沙箱、只读挂载)时,anus --version 会崩溃,anus --help 也会在尝试写设置时崩溃。0.2.2 的修复:
--version直接读取 package.json 的版本号并打印,不触碰磁盘、不加载引擎(lib/main.mjs);--help等信息类命令在ensureSettings()失败时被容忍(捕获EACCES/EPERM/EROFS后继续),不会因写不了设置而退出;- 同理,主题目录不可写时跳过主题生成,前端目录回退到系统临时目录(
prepareFront的降级路径),保证只读环境也能运行。
五、Unreleased:免费额度政策变更与安全提示
CHANGELOG 的 Unreleased 部分同步了三家服务商免费政策的变化(对应 README.md 的密钥表格,检查日期为 2026-10-01):
- Cerebras 已不再免费:改为 $5 试用 30 天,且需要绑定银行卡——从"免费提供方"降级为可选来源(lib/providers.mjs 的注释同步更新为 "not free any more");
- Mistral:免费计划是每月 $10 的 API 额度、无需银行卡(取代旧的 Experiment 计划);
- Groq:免费计划为每分钟 8000 tokens(8K tokens a minute),因此大文件会很快触达限额。
anus setup 的交互提示和 README 均已更新,并明确警告:免费额度可能用你发送的内容来改进模型(Google 与 Mistral 的免费计划尤其如此,免费 OpenRouter 模型背后的提供商亦然),所以不要把机密放进运行 ANUS 的文件夹。
政策事实以仓库 lib/providers.mjs 与 README.md 中 2026-10-01 的记录为准;免费额度会变化,各服务页面永远是最新信息来源。
六、源码级验证:测试如何守住这些行为
三个测试文件从不同维度锁死了上述行为,是理解实现的最佳佐证:
- test/route.test.mjs:验证排序(flaky/down 沉底)、休息优先级(清醒在前、休息者按苏醒时间排序)、缺密钥不尝试、上下文过小被跳过、六类错误时长、断流重试文本,以及内置列表"仅含白名单提供商、无重复、OpenRouter 模型必须带
:free后缀"; - test/coauthor.test.mjs:验证 trailer 注入覆盖
git commit的各种变体与命令链、不误伤git commit-tree/git status/npm test、重复注入免疫、git 版本门槛; - test/look.test.mjs:验证 1–200 每个宽度下屏幕元素不超宽、0.1.0 启动文案完整、窄终端退化为单行 Logo、渐变端点颜色、
✦前缀规则、用户消息圆角框、页脚逐级降级。
运行方式:仓库根目录执行 npm test(node --test test/*.test.mjs,见 package.json 的 scripts.test)。
七、结语:从 CHANGELOG 看 ANUS 的工程取向
回看这三个版本(0.2.0 → 0.2.1 → 0.2.2 → Unreleased),ANUS 的演进呈现出清晰的工程取向:
- 免费优先且克制:路由只选"当下最聪明且醒着"的免费模型,失败立即换下一个;默认忽略 shell 中的付费密钥,把"免费代理绝不花钱"做成默认约束;
- 可测试的路由决策:路由与退避规则抽成纯函数,错误分类、休息时长、引擎重试兼容全部有测试覆盖;
- 健壮性细节:窄终端逐像素裁剪、只读主目录降级、临时文件原子替换、密钥 0600 权限与隐式输入——都是"在真实终端里不会翻车"级别的打磨;
- 透明与署名:提交署名可一键关闭,模型状态随时可查(
/free),免费额度的数据代价在 setup 界面直接告知。
如果你想继续深入,推荐按这个顺序阅读仓库:CHANGELOG.md(演进全貌)→ extension/anus.mjs(路由与 UI 整合)→ lib/route.mjs(路由决策核心)→ lib/coauthor.mjs(提交署名)→ lib/look.mjs(0.1.0 外观回归)→ test/route.test.mjs(行为锁定)。
