axios 仓库贡献规范深读:AGENTS.md 如何统一定义人与 AI Agent 的工程实践
axios 是一个面向浏览器与 Node.js 的 Promise 风格 HTTP 客户端。它的根目录维护了一份名为 AGENTS.md 的"规范级贡献者指南",同时约束人类维护者与 AI 编码代理:从安装安全、构建命令、包结构、架构边界,到错误码约定、拦截器执行顺序、请求生命周期和安全敏感代码的回归红线,全部以可直接执行的规则形式固化在仓库中。读完本篇,你将掌握在 axios 仓库中安全安装依赖、正确运行各类测试套件、按架构边界修改代码,以及理解其原型污染防护等安全机制的完整方法。
AGENTS.md 的定位:唯一权威贡献者指南
axios 自我定位是一个 Promise based HTTP client,用于浏览器和 Node.js。默认实例由 index.js 从 lib/axios.js 导出;浏览器构建使用 XHR 或 Fetch 适配器,Node 端使用 HTTP/HTTPS 适配器,而平台选择逻辑集中在 lib/platform/ 下。
AGENTS.md 明确声明自己是"该仓库中人类与 AI 代理共同的唯一权威(canonical)贡献者指南"。配套地,.github/copilot-instructions.md 只是一个"薄垫片(thin stub)",其内容指向 AGENTS.md,并同步了其中"承重(load-bearing)"的安全规则子集;一旦两者漂移,以 AGENTS.md 为准。这种"单一权威 + 工具入口垫片"的组织方式,使得 Copilot、Claude Code 等 AI 工具与人类维护者遵循同一套规则。此外,CLAUDE.md 仅包含一行 @AGENTS.md 引用,说明该规范已事实上成为整个仓库工程约定的单一事实来源。
安装与供应链安全:npm ci 与 ignore-scripts
AGENTS.md 的"Setup And Safety"一节把安装流程当作安全边界来对待,核心规则如下:
- 统一使用
npm ci安装;仓库 .npmrc 中写死了ignore-scripts=true,CI 同样使用npm ci --ignore-scripts。已在仓库中确认 .npmrc 内容即为单行ignore-scripts=true。 - 禁止删除
ignore-scripts=true。如果新装后确实需要 git hooks,只需一次性执行npm rebuild husky && npx husky恢复 husky 钩子,而不是放开安装脚本执行。 - 依赖的增改属于安全敏感操作:
package-lock.json会经过lockfile-lint校验 npm HTTPS 主机来源与 integrity 哈希。 - 涉及 package、lockfile、GitHub Actions 的更新 PR 仅限维护者/机器人;外部协作者应直接关闭这类 PR。Dependabot 保留 7 天延迟,除非出现严重漏洞需要维护者主导的手动更新。
- 即使设置了
ignore-scripts,build/test/lint 工具在运行阶段仍会执行依赖代码;因此能用"聚焦检查"证明改动时,就不要跑完整构建。 - 未经讨论不得新增运行时依赖——axios 的依赖面被刻意维持得极小。
从 package.json 可以印证这一"极小依赖面"策略:运行时依赖仅有 4 个(follow-redirects、form-data、https-proxy-agent、proxy-from-env),其余全部是 devDependencies 中的构建与测试工具链。
命令体系:构建、Lint 与分层测试
AGENTS.md 的"Commands"一节定义了仓库内的标准命令,均可在 package.json 的 scripts 字段中得到逐条对应:
| 用途 | 命令 | 说明 |
|---|---|---|
| 构建发布产物 | npm run build |
对应 gulp clear && cross-env NODE_ENV=production rollup -c:先 gulp clear 删除 dist/,再由 Rollup 产出浏览器 ESM/UMD/CJS 与 Node CJS 各 bundle |
| 仅 Lint 源码 | npm run lint |
对应 eslint lib/**/*.js;聚焦检查可写 npx eslint lib/path/to/file.js |
| 单元测试 | npm run test:vitest:unit |
对应 vitest run --project unit;聚焦单文件:npm run test:vitest:unit -- tests/unit/path.test.js |
| 浏览器测试 | 先 npx playwright install(CI 用 npx playwright install --with-deps),再 npm run test:vitest:browser:headless |
与 CI 行为对齐 |
| Smoke/模块兼容套件 | 先 npm run build、npm pack,把 tarball 安装进对应的 tests/smoke/* 或 tests/module/* 包,再运行该套件自己的 npm 脚本 |
关键区别:这些套件测的是打包后的产物,不是源码树 |
vitest.config.js 进一步揭示了测试项目的划分:unit(Node 环境,匹配 tests/unit/**/*.test.js)、browser(Playwright 驱动 Chromium)与 browser-headless(Chromium/Firefox/WebKit 三浏览器 headless,并加载 tests/setup/browser.setup.js)。
文档同时固化了 CI 的执行顺序:install → build → Playwright 安装 → unit → browser headless → pack → CJS/ESM 模块与 smoke 测试 → Bun/Deno smoke 测试。这一顺序解释了为什么 smoke 套件必须先 build 再 pack——它们消费的是与用户实际安装一致的 tarball。
包形态(Package Shape):ESM 源码、按环境切分的导出
"Package Shape"一节规定了 axios 的包结构与发布契约:
- 源码是 ESM(
"type": "module");公开 ESM 入口为 index.js,它把 lib/axios.js 的默认实例解包成命名导出——实际可以看到index.js将create、Axios、AxiosError、CanceledError、CancelToken、AxiosHeaders、HttpStatusCode、getAdapter、mergeConfig、toFormData等 17 个成员具名导出,同时保留default,从而在 ESM 与 CJS 消费侧保持静态属性一致。 - 禁止手工编辑
dist/:它是被忽略的、由 Rollup 从lib/生成的产物。 - 运行时导出按环境切分。package.json 的
exports字段展示了这一设计:bun与default环境分别映射./dist/node/axios.cjs(require)与./index.js(import);react-native与browser环境则映射浏览器产物。同时browser与react-native字段把./lib/adapters/http.js、./lib/platform/node/index.js、./lib/platform/node/classes/Buffer.js、./lib/platform/node/classes/FormData.js重定向到lib/helpers/null.js或浏览器平台实现,完成"Node 文件 → 浏览器/null 替换"。 - 公共运行时导出、index.d.ts(ESM 类型)与 index.d.cts(CJS 的
export = axios类型)必须在 API 变更时保持同步。 lib/env/data.js由gulp version在版本号变更时生成(对应preversion: gulp version脚本),日常功能开发不应直接编辑它。
预发布记录规范:CHANGELOG 与 PRE_RELEASE 双轨制
axios 把"未发布变更"与"已发布记录"严格分轨,这在很多项目里是容易混乱的角落,AGENTS.md 给出了明确规则:
- 用户可见的未发布变更写入 PRE_RELEASE_CHANGELOG.md,而不是 CHANGELOG.md;后者是"发布所有"的文件,只在准备真正发布时更新。
- 推迟的 README、文档站、examples、迁移指南(MIGRATION_GUIDE.md)以及多语言文档更新,统一记录在 PRE_RELEASE_DOCS.md 中;要求提供足够的上下文以便发布时套用,禁止存"脆弱的 diff 或只有行号的笔记"。
- 在功能/修复工作中,除非任务明确是发布准备,否则不要更新 README 或文档站;正确做法是把"文档将来应该写什么"记入 PRE_RELEASE_DOCS.md,留待发布阶段统一落地。
架构边界:core / adapters / platform / helpers 的分工
AGENTS.md 的"Architecture Boundaries"一节是理解 axios 源码结构的核心地图,每条边界都可在源码中得到验证:
lib/core/——领域逻辑层。 负责请求分发、配置合并、拦截器、请求头与错误。关键类包括:
Axios(lib/core/Axios.js):请求分发与拦截器链的编排;AxiosError(lib/core/AxiosError.js):标准化错误码体系;AxiosHeaders(lib/core/AxiosHeaders.js):大小写不敏感的请求头归一化;InterceptorManager(lib/core/InterceptorManager.js):同步/异步拦截器注册。
lib/adapters/——I/O 层。 执行真正的网络请求。默认适配器偏好顺序为 ['xhr', 'http', 'fetch'],能力选择发生在 lib/adapters/adapters.js。打开该文件可以看到 getAdapter(adapters, config) 的完整算法:把输入归一化为数组后逐项检查——若项是已解析句柄(函数、null 或 false)则直接使用;否则按名称查 knownAdapters 表,未知名称直接抛 AxiosError('Unknown adapter ...');对 fetch 这类惰性适配器还会调用其 get(config) 做能力探测。全部落选时,函数汇总每个适配器的拒绝原因(区分"is not supported by the environment"与"is not available in the build"),抛出带 ERR_NOT_SUPPORT 码的 AxiosError。文档特别强调:按能力选择适配器,而不是按环境名称判断。
lib/platform/——平台选择层。 lib/platform/index.js 默认聚合 Node 平台实现(./node/index.js 与 ./common/utils.js);浏览器构建则依靠打包期的 browser 字段与 Rollup alias 替换到 lib/platform/browser。
lib/helpers/——通用工具层。 约束是"应保持通用、可脱离 axios 复用",不得把 axios 特有的请求生命周期逻辑放进这里。
新文件风格要求:lib/**/*.js 必须使用显式 .js 扩展名的 ESM 导入、在既有文件使用 'use strict'; 的位置保持一致、axios 源自的失败一律抛 AxiosError。
命名约定与错误处理规范
命名约定(可直接作为代码评审检查单):
- 类用 PascalCase:
Axios、AxiosError、InterceptorManager; - 函数用 camelCase:
buildURL、mergeConfig、dispatchRequest; - 错误码用 UPPER_SNAKE_CASE 常量挂到
AxiosError上:ERR_NETWORK、ETIMEDOUT等; - 内部类槽位使用
Symbol键(例如 lib/core/AxiosHeaders.js 中的const $internals = Symbol('internals')),而非下划线前缀属性。
错误处理规则与 lib/core/AxiosError.js 的实现一一对应:
- axios 源自的失败必须抛
AxiosError而不是裸Error,构造参数为(message, code, config, request, response)五元组,让消费方可以做结构化内省。源码中构造函数会把config、request、response挂到错误实例上,并从response.status派生status字段。 - 第三方错误用静态方法
AxiosError.from(error, code, config, request, response)包装。从源码看,from还处理了 Node 双栈连接失败产生的AggregateError(message 为空时聚合errors[]),并把原错误挂到不可枚举的cause上——注释明确说明这是为了避免包装的 socket/request 循环引用破坏 pino/winston 等结构化日志。 - 权威错误码清单定义在 lib/core/AxiosError.js 文件末尾,共 14 个:
ERR_BAD_OPTION_VALUE、ERR_BAD_OPTION、ECONNABORTED、ETIMEDOUT、ECONNREFUSED、ERR_NETWORK、ERR_FR_TOO_MANY_REDIRECTS、ERR_DEPRECATED、ERR_BAD_RESPONSE、ERR_BAD_REQUEST、ERR_CANCELED、ERR_NOT_SUPPORT、ERR_INVALID_URL、ERR_FORM_DATA_DEPTH_EXCEEDED。 - 配置选项校验统一走
validatorhelper(lib/helpers/validator.js),不得自造临时校验路径。
另一个值得注意的实现细节:AxiosError.toJSON() 支持通过请求配置中的 redact 数组合做敏感键脱敏——匹配键(不区分大小写、任意深度)在序列化快照中被替换为 [REDACTED ****] 常量。这意味着把 AxiosError 直接 JSON.stringify 进日志时,可以防止 auth 等字段意外落盘,这是错误处理规范之外的又一安全设计。
拦截器执行顺序与请求生命周期
拦截器顺序在 axios 中既影响行为也影响测试,AGENTS.md 把它写成硬性规则:
- 请求拦截器:后注册先执行(LIFO);
- 响应拦截器:先注册先执行(FIFO);
- 两者都支持
synchronous: true(当链中无异步 handler 时避免 Promise 包装开销)与runWhen: (config) => boolean条件执行; - 新增内建拦截器时必须把顺序写入文档。
由此形成的完整请求生命周期共 9 步:
- 用户调用
axios()或方法别名(get/post等); - 通过
mergeConfig(lib/core/mergeConfig.js)合并实例默认值与请求配置; - 执行请求拦截器(LIFO);
- 经 lib/adapters/adapters.js 的能力检查选定适配器;
- 依次应用
transformRequest函数; - 适配器执行 HTTP 请求;
- 依次应用
transformResponse函数; - 执行响应拦截器(FIFO);
- 以
AxiosResponse兑现 Promise,或以AxiosError拒绝。
取消机制的不变量
取消(Cancellation)一节给出三条不变量,适用于任何生命周期阶段(包括响应体读取中途):
CancelToken(旧式,见 lib/cancel/CancelToken.js)与AbortSignal(现代 API)双通道并存,不得破坏任何一条路径;- 取消必须在请求的任意阶段生效,含 in-flight 的 body 读取;
- 结算或取消时必须移除 signal 监听器,防止内存泄漏。
常见陷阱清单:四条可执行的"禁止事项"
AGENTS.md 的"Common Pitfalls"是把历次修复经验固化的负空间清单:
- 禁止原地变更 config 对象——merge/transform 一律返回新对象;
- 禁止假设浏览器或 Node 特有全局变量存在——先做能力检查;
- 禁止直接使用
Function.prototype.bind——必须用 lib/helpers/bind.js。查看该文件可见它只有几行:fn.apply(thisArg, arguments)包装,通过apply转发原始arguments,这是库内其他代码依赖的行为; - 库代码中禁止抛裸
Error——一律AxiosError并附合适错误码。
测试体系:运行时优先的分层布局
测试规则("Tests"一节)与 tests/ 目录结构完全对应:
- 布局按运行时优先组织:单元测试
tests/unit/**/*.test.js、浏览器测试tests/browser/**/*.browser.test.js(对应 vitest 的browser/browser-headless项目)、smoke 套件tests/smoke/esm/**/*.smoke.test.js与tests/smoke/cjs/**/*.smoke.test.cjs; - 本地 HTTP 服务统一用 tests/setup/server.js 创建,并在
try/finally中清理——文档特别警告"泄漏的服务会导致 Vitest 挂死"; - 行为涉及打包/导入时,CJS 与 ESM 的 smoke 覆盖必须保持对齐;
- 类型兼容由两个模块套件分别守护:
tests/module/cjs用 TypeScript 4.9,tests/module/esm用 TypeScript 5.x;改动声明文件(index.d.ts/index.d.cts)时须运行对应套件; - 浏览器测试会替换 XHR 等全局对象,因此必须在清理钩子中恢复全局、重置 spy。
安全敏感代码:原型污染防护与威胁模型联动
AGENTS.md 的最后一节定义了"不可回归"的安全红线,并且每一条都能在源码中找到落点:
- 配置读取禁止原型遍历。对于影响行为的配置读取,不得使用
in、解构或直接config.foo访问不可信配置,必须用自有属性检查。这正对应utils.hasOwnProp与本地own()helper 的使用模式——在 lib/core/mergeConfig.js 中可以看到大量utils.hasOwnProp(config2, prop)形式的读取,合并入口甚至为hasOwnProperty自身恢复了不可枚举的自有槽位。 - 合并与对象物化必须持续过滤
__proto__、constructor、prototype,这里的回归就是安全 bug。源码印证:mergeConfig.js的深合并循环开头即有if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') return;的守卫。 - 触及 URL 构建、重定向、代理/环境变量处理、XSRF、socket 路径、解压限制或适配器的改动,应先查阅 THREATMODEL.md 并补充聚焦的回归测试。
withXSRFToken的跨域行为保持显式:只有true才强制跨域附加 XSRF 头,不得扩大该触发条件。- 不得弱化
beforeRedirect、代理、socketPath的防护,除非有覆盖凭据泄漏与 SSRF 类场景的测试兜底。
小结:一份可被机器执行的工程契约
AGENTS.md 的价值在于它把"隐性工程共识"翻译成了对人和 AI 同样生效的显式契约:安装安全(npm ci + ignore-scripts)、命令语义(build/lint/unit/browser/smoke 各自的适用对象与前置条件)、包形态(ESM 源码 + 按环境切分的 exports + 双类型声明文件同步)、架构边界(core/adapters/platform/helpers 四分层与"按能力选适配器")、命名与错误码约定、拦截器 LIFO/FIFO 顺序、取消双通道不变量、分层测试布局,以及原型污染过滤与安全敏感改动的强制威胁模型评审。对照 lib/adapters/adapters.js、lib/core/AxiosError.js、lib/core/mergeConfig.js 等源码即可逐条验证这些规则不是空泛口号,而是与实现深度咬合的工程红线。对于希望深入 axios 内部机制的开发者,这份文档是最快的"仓库地图"入口。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00