ANUS 版本演进全解:免费模型路由、0.1.0 外观回归与提交署名机制(CHANGELOG 深度导读)

原创2026-10-14 11:17:40527 阅读
文章标签:AI AgentCLI开发工具AI 应用

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 版本兼容等工程细节。

ANUS 终端界面截图:块状 Logo、渐变配色、圆角输入框与加载短语


一、为什么从 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 的演进呈现出清晰的工程取向:

  1. 免费优先且克制:路由只选"当下最聪明且醒着"的免费模型,失败立即换下一个;默认忽略 shell 中的付费密钥,把"免费代理绝不花钱"做成默认约束;
  2. 可测试的路由决策:路由与退避规则抽成纯函数,错误分类、休息时长、引擎重试兼容全部有测试覆盖;
  3. 健壮性细节:窄终端逐像素裁剪、只读主目录降级、临时文件原子替换、密钥 0600 权限与隐式输入——都是"在真实终端里不会翻车"级别的打磨;
  4. 透明与署名:提交署名可一键关闭,模型状态随时可查(/free),免费额度的数据代价在 setup 界面直接告知。

如果你想继续深入,推荐按这个顺序阅读仓库:CHANGELOG.md(演进全貌)→ extension/anus.mjs(路由与 UI 整合)→ lib/route.mjs(路由决策核心)→ lib/coauthor.mjs(提交署名)→ lib/look.mjs(0.1.0 外观回归)→ test/route.test.mjs(行为锁定)。

登录后查看全文
ANUS