docs: streamline documentation and skill workflows

Keep documentation focused on current behavior and clear ownership.
Load skill references by task and clarify official source priority and reply tone.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-09-28 15:54:49 +08:00
co-authored by Codex
parent 9a1e90dd3d
commit a1fc368474
29 changed files with 1498 additions and 1327 deletions
+31 -51
View File
@@ -1,69 +1,49 @@
---
name: cpr-dev-guide
description: Codex Proxy RS 仓库开发与问题审查入口。用户要求“开发/实现这个功能”“直接改”“修复一下”“看下报错”“审查这个问题/代码”“自审当前改动”“调整页面”“更新文档”,或继续修改本仓库的 Rust、Vue、协议、存储、部署和 CI 时主动使用,无需点名技能。负责定位、方案、自审与验证;提交或审查 PR 另配合 cpr-github-pr。Use for implementation, debugging, code review and documentation in this repository, not generic advice for unrelated projects.
description: Codex Proxy RS 仓库的开发、修复、排障、代码审查和文档修改入口;PR、Issue、插件创作与发版按任务另用对应技能
---
# 开发指南
先读根目录的 [AGENTS.md](../../../AGENTS.md) 和 [CONTRIBUTING.md](../../../CONTRIBUTING.md)。
本技能只组织工作入口,架构、协议和视觉规则从下列权威文档读取,不维护第二份副本。
## 加载边界
普通问题审查、代码审查与排查由本技能进入贡献文档的审查标准;有 PR 链接、编号或提交 PR 的意图时再加载 `cpr-github-pr`。
审查请求先报告结论,不因加载开发技能而擅自修复;用户已要求修改时完成实现、自审和验证。
- 遵守已提供的 [AGENTS.md](../../../AGENTS.md),上下文已有的规则不重复读取
- 下表是条件导航,不是必读清单,只加载当前任务命中的技能与章节,进入新阶段再补读
- 文档链接带 `#章节` 不代表读取工具会自动截取内容,先用 `rg -n '^#{1,4} ' 文件` 找边界,再按范围读取;常规定位不整份加载 CONTRIBUTING、架构、API、主题或全部 references,确需全文审查时再展开
- 每次补读先明确要解决的未知项,已有资料足够就继续工作;文档提及 Rust、Vue、插件或发布,不等于要加载对应开发流程
## 按任务读取
| 任务 | 技能与文档入口 |
| 当前任务 | 必读的相关部分 |
| --- | --- |
| 用户能力、快速开始 | [README](../../../README.md) |
| 模块职责、调用链、状态归属 | [系统架构](../../../docs/architecture.md) |
| Rust 开发 | 加载并使用 `$rust-best-practices`,结合 [系统架构](../../../docs/architecture.md) 与 [项目约定](../../../CONTRIBUTING.md#项目约定) |
| HTTP 路由、请求与响应、导入导出 | [API 文档](../../../docs/api.md) |
| 插件 SDK、Runtime 或宿主能力扩展 | 先执行下方「插件架构边界」,结合 [系统架构](../../../docs/architecture.md) 定位原生能力归属 |
| 开发独立网关插件 | 先执行下方「插件架构边界」,再使用 [cpr-plugin-dev](../cpr-plugin-dev/SKILL.md):公开 SDK、能力选择、示例、打包与验证 |
| 前端开发、管理端组件、主题 Token、视觉验证 | 按下方「前端任务」加载设计约束,结合 [管理端主题](../../../docs/theme.md) 与 [界面验证](../../../CONTRIBUTING.md#界面验证) |
| 常规文档更新 | [文档职责与更新条件](../../../CONTRIBUTING.md#文档职责与更新条件):维护当前说明,在原章节就地修订,不记录变更历史 |
| 部署、客户端配置、备份与恢复 | [部署说明](../../../deploy/README.md) |
| 数据迁移、冻结清单与本地测试库 | [迁移说明](../../../backend/migrations/README.md) |
| 验证命令与提交规范 | [贡献与审查](../../../CONTRIBUTING.md) |
| 创建或审查 PR | [cpr-github-pr](../cpr-github-pr/SKILL.md) |
| 整理或提交 Issue | [cpr-github-issue](../cpr-github-issue/SKILL.md) |
| 发布说明、版本规划与发版 | [cpr-release](../cpr-release/SKILL.md) |
| 开发、排障、代码审查 | [问题与方案依据](../../../CONTRIBUTING.md#问题与方案依据),需要定位代码时按 AGENTS 的 CodeGraph 约定执行 |
| 模块或调用链变更 | [Workspace 边界](../../../docs/architecture.md#3-workspace-边界)及所属模块章节,不通读整份架构 |
| Rust 开发或审查 | `$rust-best-practices`、所属模块、[后端自审](../../../docs/architecture.md#后端自审),验证时读[命令](../../../docs/architecture.md#验证命令) |
| 页面开发 | `$frontend-design`、[文案与信息层级](../../../docs/theme.md#界面文案与信息层级)、[界面验证](../../../CONTRIBUTING.md#界面验证);修改已有页面前留存截图,主题算法仅在涉及时读取 |
| 接口、部署或迁移 | 分别定位 [API](../../../docs/api.md)、[部署](../../../deploy/README.md)、[迁移](../../../backend/migrations/README.md)的对应章节 |
| SDK、Runtime、宿主扩展或独立插件 | [插件职责边界](references/plugin-boundaries.md);只有创作、排查独立网关插件时使用 [cpr-plugin-dev](../cpr-plugin-dev/SKILL.md) |
| 修改或审查文档 | [文档检查](references/documentation.md)及目标章节,不默认读取代码开发、页面验收或发布流程 |
| 普通代码审查 | [审查标准](../../../CONTRIBUTING.md#审查标准);审查默认只读 |
| PR / Issue / 发版 | 按当前意图选 [PR](../cpr-github-pr/SKILL.md)、[Issue](../cpr-github-issue/SKILL.md)或[发版](../cpr-release/SKILL.md),普通提交不创建 PR |
## 前端任务
## 参考仓库的优先级
- 加载并使用 `$frontend-design`,先读 [界面文案与信息层级](../../../docs/theme.md#界面文案与信息层级),将本项目的管理工具风格、现有字体与主题 Token 作为设计约束
- 开发前对照原页面及该章节的同类参考,确认可复用布局、组件与操作位置;修改已有页面前先留存截图,不为局部需求重做页面
- 页面保留操作所需的简短信息,重复文案删除,详细帮助按主题约定收进 Popover;关键限制和错误仍就近提示
- 交付前按该章节复核文案、层级与密度,按 [界面验证](../../../CONTRIBUTING.md#界面验证) 实际操作并保存前后截图;页面 PR 的截图必须可供审查者访问,预览步骤与构建不能替代
- 涉及 Codex 行为、协议或兼容性时,以官方 [openai/codex](https://github.com/openai/codex) 对应版本的源码与调用链为第一手资料,先查官方实现
- 优先使用用户提供或本机已有的官方仓库,核对来源、版本或提交;资料不足时再查官方对应版本的源码、测试与文档
- 三方仓库只能提供排查线索或实现参考,不能代替官方行为依据;与官方不一致时回到官方源码核实,不直接照搬三方结论
- 结论注明适用版本或提交,区分官方事实、三方参考与本项目推断;官方证据不足时保留不确定性
## 后端任务
## 执行与收尾
加载并使用 `$rust-best-practices`,先按架构文档定位事实归属与既有调用链,实现后逐项执行 [后端自审](../../../docs/architecture.md#后端自审) 中与本次改动相关的检查。
交付说明实际复用位置、重要边界的处理与验证缺口,不用“符合最佳实践”或“CI 已通过”代替自审结论。
1. 确认工作区、任务范围与已有改动,先定位事实归属、同类实现和调用链,再修改;编码前只读[项目约定](../../../CONTRIBUTING.md#项目约定)的对应条目
2. 自审完整差异,处理本次引入的职责越界、重复规则、冗余状态与无依据分支;仅审查时报告,不自行修复
3. 按变更范围读取[验证](../../../CONTRIBUTING.md#验证),区分通过、失败、跳过与未执行;提交时补读[提交约定](../../../CONTRIBUTING.md#项目约定)
4. 交付说明实际结果与验证缺口;页面证据和后端生命周期检查按上表执行,不把构建通过当作行为验收
## 插件架构边界
## 文档硬约束
**底线:宿主可以暴露底层能力,但不能替插件完成业务。** 开发或审查插件 SDK、Runtime、宿主接口及独立网关插件时,必须先明确宿主与插件各自的职责;具体架构依据见 [系统架构](../../../docs/architecture.md)。
**常规文档只描述当前状态,不写变更历史**,这项检查适用于每次开发收尾,不限于专门的文档任务
- 宿主提供独立、可组合的受控原生能力,负责授权、资源隔离、事务和审计,复用已有服务与状态归属。
插件自身的触发条件、跨资源业务关联、策略和流程编排由插件实现;不能将某个 Issue 的使用场景直接做成宿主的组合流程,或为其增加专属业务状态与补偿逻辑。
- 开发独立网关插件时,在插件内通过公开 SDK 组合用户要求的业务。发现能力缺口时,先界定可独立使用的基础接口,不能把整个插件业务搬进宿主。
- 基础接口必须说明输入输出、权限、事务、失败及重复调用语义;这些合同不应夹带对插件业务工作流或私有数据模型的要求。
- 自审完整调用链、协议、测试与文档,检查是否混入插件业务判断、自动串联或专属状态;仅更换通用名称不算消除业务耦合。
## 执行顺序
1. 从仓库根目录确认工作区、当前任务范围和相关运行实例;保留已有改动。
2. 按 `AGENTS.md` 的 CodeGraph 约定定位入口、调用链和职责归属,再阅读相关实现。
3. 按 [问题与方案依据](../../../CONTRIBUTING.md#问题与方案依据) 核实触发条件、问题发生版本与目标分支现状,
优先依据适用的官方源码确定或评估方案。结合上表读取所需文档,先找可复用的同类实现,再按授权修改或审查所属模块。
4. 文档和运行结果不一致时,追查当前源码、配置与版本,区分实现缺陷和文档过期;
按 [文档职责与更新条件](../../../CONTRIBUTING.md#文档职责与更新条件) 判断是否修订,不为每次改动追加文档或发布说明。
5. 复核相对目标基线的完整差异及上下游:开发时清理本次引入的职责越界、重复逻辑、冗余状态和无依据的分支;仅审查时报告问题与依据。页面与后端分别按上述任务入口执行,插件相关变更还须逐项核对「插件架构边界」,不等待 PR 审查代做开发自审。
6. 交付前按文档职责逐段复核最终文档 diff:新增内容是否回答当前读者的使用或维护问题,是否属于该文档,
是否与其他位置重复,脱离本次变更背景能否独立理解。仅解释实现经过、前后对比或验证结果的内容移到
任务报告或 PR;仍影响升级、恢复的说明保留适用条件与操作。没有文档差异时,只核对行为变化是否使现有说明失真。
7. 按 `CONTRIBUTING.md` 验证并报告结果。后端 Cargo 命令从 `backend/` 执行,或显式指定该目录的 manifest。用户要求提交或更新 PR 时,进入 `cpr-github-pr` 流程整理最终证据与正文。
发布任务按 `cpr-release` 技能读取当前版本、发布入口和工作流,发布说明语言遵循 `CONTRIBUTING.md`。
- 动笔前确定读者、所属章节与必须说明的当前事实,没有必要就不改文档
- 有文档差异时执行[文档检查](references/documentation.md),逐段决定保留、改写、删除或移入任务报告,未完成内容审查不能称文档已检查
- 没有文档差异时只确认现有说明是否失真;普通开发不自动修改 `release/notes.md`
@@ -0,0 +1,38 @@
# 文档检查
仅在准备修改文档或最终差异含文档时读取,职责与行文依据是 [文档职责与更新条件](../../../../CONTRIBUTING.md#文档职责与更新条件),只读该节
## 写之前
先明确这段内容服务谁、解决哪个当前使用或维护问题、属于哪个现有章节,再决定详略
接口保留调用合同,架构保留职责与不变量,部署保留操作与排障;同一事实只在所属位置完整说明,其他位置链接
常规文档在原章节修订,不追加本次变更小节,不为了“同步文档”补写修复总结
内部重构、实现经过、前后对比、验证结果应留在 PR、Issue 或任务报告
## 逐段验收
检查工作区和暂存区中本任务的最终文档差异,按内容用途处理,不只搜索关键词:
| 段落回答的问题 | 处理 |
| --- | --- |
| 现在如何使用、配置或理解结果 | 保留当前事实与必要边界,放回所属章节 |
| 谁负责状态、事务、资源和失败处理 | 放入所属架构章节,避免在 API 或部署文档复述实现 |
| 这次做了什么、以前怎样、为什么这样修、跑过哪些测试 | 移入 PR、Issue 或任务报告,不以换成现在时的方式留在常规文档 |
| 特定配置或数据状态下如何升级、恢复 | 写明适用条件、操作及限制,删除演变过程 |
| 别处已完整说明的事实 | 合并或改为必要摘要与链接 |
脱离提交日期、Issue 和本次任务背景后,读者仍应能独立理解正文
业务中的请求历史、审计记录、旧数据兼容与回滚操作是当前功能,不能因含“历史”“旧”等词而删除
## 交付检查
- 检查新段落的必要性、归属、重复与当前状态表达,发现变更叙述先改完再交付
- 标题、短列表、表格和独立短段通常不加句号,长段按主题拆分,命令与字段保持准确
- 检查相对链接、章节锚点、表格与命令示例,`git diff --check` 或构建通过不能代替内容审查
- 报告实际覆盖和缺口即可,不把检查记录写进常规文档
只有明确的发布说明任务才进入 `cpr-release`,`release/notes.md` 用于该次发行,不向 README、API、架构或部署文档复制版本变化列表
@@ -0,0 +1,11 @@
# 插件职责边界
仅在开发或审查 SDK、Runtime、宿主扩展接口及独立网关插件时读取
先看[插件扩展](../../../../docs/architecture.md#31-插件扩展)中的职责与执行边界,不通读整份架构
**宿主可以暴露底层能力,不能替插件完成业务**
- 宿主提供可独立组合的受控原生能力,负责授权、资源隔离、事务与审计,复用已有服务与状态归属
- 插件自身的触发条件、跨资源关联、策略和流程编排由插件实现,不能把某个使用场景做成宿主专属流程、状态或补偿逻辑
- 发现能力缺口时,界定可独立使用的基础接口,说明输入输出、权限、事务、失败及重复调用语义,不夹带插件私有数据模型或工作流
- 自审调用链、合同、测试与文档中的业务判断、自动串联和专属状态,仅换成通用名称不算解除耦合
+44 -43
View File
@@ -1,61 +1,62 @@
---
name: cpr-github-issue
description: 整理、创建和补充 GitHub Issue。用户说“提/开/提交一个 Issue”“报个 bug 到仓库”“反馈给上游”“把问题或功能建议整理成 Issue”“补充这个 Issue”时主动使用;已有 Issue 反馈上下文中的“提交吧”“补上复现”也适用。处理缺陷、功能与协作规则反馈,区分草拟和发布。Use for drafting, filing and updating issues; standalone “审查问题/看下报错/修一下” belongs to cpr-dev-guide, not automatic Issue submission.
description: 草拟、创建、补充或回复 GitHub Issue,包括缺陷、功能和协作规则反馈;单独排障或修复不自动发布 Issue
---
# 整理与提交 Issue
# Issue 协作
本仓库流程见 [贡献与审查](../../../CONTRIBUTING.md),按场景选择当前的
[接口问题反馈](../../../.github/ISSUE_TEMPLATE/api-bug-report.yml)、[常规问题反馈](../../../.github/ISSUE_TEMPLATE/general-bug-report.yml)
或 [功能建议](../../../.github/ISSUE_TEMPLATE/feature-request.yml) 表单。从表单读取字段与必填项,不在技能中复制一份字段清单。
用户指定其他仓库时,使用该仓库当前的贡献规则与 Issue 模板,不能把这里的表单套过去。
## 目标与范围
## 确定目标和动作
从用户 URL、remote 和上下文确认接收仓库,fork 场景区分自己的仓库与上游
- 根据用户 URL、仓库上下文和 remote 确认接收 Issue 的仓库。fork 场景核对用户要反馈给自己的 fork 还是上游。
- “写一份、整理内容、草拟 Issue”只准备正文;明确要求“提交、创建 Issue”才发布。
已经授权提交且信息足够时直接完成,不重复索要确认;仅自动选中技能不能授权发布。
- “有个 bug”“这个报错怎么修”本身是排障语境,不能推断要发 Issue;结合已有反馈任务识别省略的目标和动作。
- “补充这个 Issue”先读取原帖与已有讨论,按授权更新正文或追加证据,不重复新建;需要排查或修改本仓库时先执行 [cpr-dev-guide](../cpr-dev-guide/SKILL.md)。
- 缺少无法推断的目标或关键复现事实时,只问必要信息,并先整理其余已有证据。
- 使用可用的 GitHub 连接器或 `gh`。不要为提交 Issue 安装新工具、修改仓库配置或启用额外服务。
草拟只准备正文,明确创建或提交才发布;已有授权直接执行,自动命中技能本身不授权远程写入
## 证据与去重
补充已有 Issue 先读原帖和讨论,按授权更新或追加,不重复新建;无法推断的目标或关键事实才向用户补齐
1. 读取目标仓库实际 Issue 模板和配置,区分缺陷、功能建议与协作规则提案。
本仓库功能建议有独立表单,协作规则提案允许普通 Issue;其他仓库按其配置选择入口。
2. 先搜索 open 和 closed Issues,结合关键词、错误信息与触发条件检查重复,而不只比较标题。
同时核对相关 PR 与发布记录,关联已有修复及其发布版本;仅主分支已修复时明确尚未发布。
同一问题已有记录且没有新增证据时返回已有链接;用户仍明确要求新建时按其要求处理。
不因发现重复而擅自评论、重开或关闭已有 Issue。
3. 从用户描述、当前版本与已有诊断中整理事实:区分实际结果、预期结果、稳定复现条件和未经证实的推测。
不把本地开发版本当作发生问题的部署版本;无法复现时如实说明已知条件。
引用官方行为或提出实现方案时,按 [问题与方案依据](../../../CONTRIBUTING.md#问题与方案依据) 注明来源、版本与缺口;
不把找到官方源码或证明根因作为提交症状反馈的前提。
4. 只收集定位所需信息。正文、日志片段和附件去除密钥、Cookie、令牌、代理认证、私人请求内容等敏感信息;
不直接附完整配置或请求转储。记录与复现相关的代理、TUN、证书条件,不擅自修改这些环境来收集材料。
## 按需取材
## 编写正文
- 新建 Issue 时,本仓库只读[提交 Issue](../../../CONTRIBUTING.md#提交-issue)及本次匹配的一张表单:[接口问题](../../../.github/ISSUE_TEMPLATE/api-bug-report.yml)、[常规问题](../../../.github/ISSUE_TEMPLATE/general-bug-report.yml)或[功能建议](../../../.github/ISSUE_TEMPLATE/feature-request.yml);协作规则提案可用普通 Issue
- 回复时读取原帖、相关讨论与支撑答复的证据,按下方回复口吻组织内容,不套用新建 Issue 的表单
- 其他仓库读取它自己的相关规则与实际模板,长文档先定位标题再截取,不预读全部模板或 CONTRIBUTING
- 搜索同场景 open/closed Issues、相关 PR 和发布记录,区分主分支修复与已发布修复,不凭标题判断重复;没有新证据时返回已有链接,用户仍要求新建则按其要求处理
- 仅当需要额外排查或修改时使用 [cpr-dev-guide](../cpr-dev-guide/SKILL.md),提交症状反馈不以找到根因为前提
- 引用上游行为或提出方案时读取[问题与方案依据](../../../CONTRIBUTING.md#问题与方案依据),涉及 Codex 时按[参考仓库优先级](../cpr-dev-guide/SKILL.md#参考仓库的优先级)先查官方 Codex 源码,三方仓库仅作参考;注明适用版本、来源与缺口
- 标题写具体症状或所需行为,不预先断言尚未证明的根因。语言与目标仓库及用户要求一致。
- YAML Issue Form 按字段顺序使用 `attributes.label` 作为 Markdown 小标题,填写相应答案。
`gh issue create --body-file` 直接提交正文,不会替你完成网页表单校验;手动核对必填字段、选项和必选确认项。
不把原始 YAML、占位文字或尚未确认的勾选项当成答案提交。
- 无法获知的信息明确写未提供或不适用,并说明理由;如果该事实关系到能否理解或复现问题,发布前补齐。
功能或规则提案说明当前场景、缺少的能力、建议结果和必要取舍,不强行填写缺陷复现步骤。
- 附件只使用用户授权发布且实际可访问的地址;本机 `/tmp/...` 路径不能作为 GitHub 附件。
没有可用附件时保留操作描述并说明缺口,不声称截图已上传。
- 使用模板规定或用户要求的 labels、assignees;先确认它们在目标仓库存在,不自行猜测。
## 正文与证据
## 提交与收尾
标题说明症状或期望行为,区分发生问题的部署版本、实测结果、预期结果与根因假设
把核对后的 UTF-8 正文写入临时文件,或直接传给连接器的结构化字段。CLI 变量先从已确认的目标与正文赋值:
功能和规则提案说明场景、缺少的能力及建议结果,无需硬套缺陷复现步骤
YAML 表单按 `attributes.label` 和字段顺序组织正文,手工核对必填项、选项与确认项,CLI 不会代做表单校验
未知信息如实标明,影响理解或复现的关键事实先补齐,不提交原始 YAML、占位答案或未确认的勾选项
日志和附件仅含定位所需的脱敏片段,不公开凭据、完整配置或请求转储,不擅自修改代理、TUN、证书环境取证
附件须获授权且可访问,本机路径不算附件;labels 和 assignees 仅按模板或用户要求设置,并确认实际存在
Issue 可记录复现、排查和变更经过,不因此向常规文档追加历史;确需改文档时执行[文档检查](../cpr-dev-guide/references/documentation.md)
## 回复口吻
草拟和发布 Issue 回复时遵守以下约定:
- 用第一人称“我”表达,称呼对方为“佬友”,称呼自然出现即可,不逐句重复,也不用“我们”“本助手”代称自己
- 语气客气、委婉,直接回应对方的具体问题;补充信息或提出不同意见时说明缘由,避免命令式、责备式表达
- 使用自然短句,简单回复用短段,只有并列信息较多时才分点;少用句号,避免客服套话、机械模板和空泛保证
- 只把实际完成的排查、复现和验证写成“我已……”;未确认的事实如实说明,委婉表达也要说清结论、限制和下一步
## 发布与收尾
使用可用连接器或 `gh`,不为提交反馈安装工具、修改仓库配置或启用服务
多行正文通过结构化字段或 UTF-8 文件传递,以下变量须先从已确认的目标与正文赋值:
```bash
gh issue create --repo "$issue_repo" --title "$issue_title" --body-file "$issue_body_file"
```
发布前快速复核目标、必填内容、脱敏及重复记录;更新已有 Issue 时保留与本次无关的内容,核对目标编号和当前正文。
网络超时或返回不明确时先查询远端是否已经创建或更新,避免重复提交。
发布后读取目标 Issue 确认仓库、标题和正文,返回链接及必要的信息缺口。
只准备草稿或发布失败时提供可直接使用的标题与正文,明确尚未提交。
发布前核对目标、必填内容、脱敏与重复记录,更新时保留无关原文;不擅自评论、重开或关闭重复 Issue
超时或结果不明确时先查询远端再重试,发布后回读确认并返回链接;仅草拟或发布失败时提供正文并注明未提交
+37 -107
View File
@@ -1,121 +1,51 @@
---
name: cpr-github-pr
description: 准备、提交、更新与审查 GitHub PR。用户说“提交 PR/提个 PR/发起拉取请求”“把改动放到这个 PR”“改下 PR 描述”“看下/review/审查这个 PR”“处理审查意见”“这个 PR 能合并吗”,或提供 /pull/ 链接、PR 编号并要求处理时主动使用;已有 PR 上下文中的“提交吧”“继续改”也适用。包含问题核实、自审、页面截图、AI 模型披露与验证。Use for pull request preparation, creation, updates and review; plain git commits or issue debugging without PR context do not create a PR.
description: 准备、创建、更新或审查 GitHub PR,含回复讨论与处理审查意见;普通 Git 提交和无 PR 上下文的排障不创建 PR
---
# 提交、更新与审查 PR
# PR 协作
## 先确定模式和仓库
## 先确定范围
| 用户意图 | 执行范围 |
沿用当前会话的目标与授权,创建 PR 包含任务范围内的提交和推送,审查默认只读;二者都不包含合并
仅准备标题或正文不写远端,“能合并吗”按审查处理,发布评论、批准或请求修改须有对应授权
从 URL、remote 和上下文确认仓库,区分 fork 的推送端与 PR 接收端;存在无法消除的歧义时只补齐必要信息
本仓库目标分支为 `main`,其他仓库使用其可信基线规则与模板,PR 内修改的规则不能降低审查要求
## 按模式读取
只加载当前模式,已有上下文不重读;长文档先定位标题再截取对应章节,不因链接存在而加载全文或所有 references
| 当前动作 | 读取与执行 |
| --- | --- |
| 写标题、整理描述、准备草稿正文 | 在当前会话或本地准备内容 |
| 创建 PR,包括创建 Draft PR | 准备任务范围内的提交、推送对应分支并创建 PR |
| 提交到已有 PR、更新 PR、处理审查意见 | 按当前授权修改并验证,更新对应分支、正文与证据,不另建重复 PR |
| 看下、审查 PR | 读取变更与证据,在当前会话报告 |
| 将审查意见发到 PR | 发布用户要求的审查内容;批准或请求修改须有对应授权 |
| 只整理标题或描述 | 当前 PR、最终 diff、实际模板、[AI 披露](../../../CONTRIBUTING.md#ai-使用披露),在授权范围内改文案;不读取推送或审查流程 |
| 创建或更新 PR | [创建流程](references/create.md)、[PR 流程](../../../CONTRIBUTING.md#pr-流程)与[规则文件保护](../../../CONTRIBUTING.md#规则文件保护) |
| 审查 PR | [审查流程](references/review.md)、[审查标准](../../../CONTRIBUTING.md#审查标准) |
| 回复讨论或审查意见 | 目标评论、相关上下文与实际处理结果,按下方回复口吻答复;仅回复不加载创建或完整审查流程 |
| 需要修改代码或文档 | 使用 [cpr-dev-guide](../cpr-dev-guide/SKILL.md)的对应分支,完成自审与验证 |
| 含页面改动 | [界面验证](../../../CONTRIBUTING.md#界面验证),必需截图须可访问且对应当前实现 |
| 含文档改动 | [文档检查](../cpr-dev-guide/references/documentation.md),常规文档只说明当前状态,变更经过留在 PR |
沿用当前会话已有授权和范围修正。明确要求创建时直接完成,无需在准备好正文后重复确认;
创建和审查都不包含合并。仅命中本技能不能作为远程写入的授权。
## 回复口吻
“看下这个”需结合已有 PR 链接或讨论定位目标;“能合并吗”先按审查处理,不能解释为执行合并。
用户连续要求“整理一下”时沿用当前 PR 任务;没有 PR 上下文时,不把普通提交整理转成创建 PR。
草拟和发布 PR 讨论、审查意见的回复时遵守以下约定:
先从用户提供的 URL、仓库上下文和 Git remote 确认目标仓库;区分 fork 的推送仓库与 PR 接收仓库。
存在多个无法消除的候选时,只询问缺失的目标信息,同时继续准备不依赖目标的内容。
优先使用可用的 GitHub 连接器;使用 `gh` 时显式指定 `--repo`,参数以本机帮助为准。
- 用第一人称“我”表达,称呼对方为“佬友”,称呼自然出现即可,不逐句重复,也不用“我们”“本助手”代称自己
- 语气客气、委婉,直接回应对方的具体问题;补充信息或提出不同意见时说明缘由,避免命令式、责备式表达
- 使用自然短句,简单回复用短段,只有并列信息较多时才分点;少用句号,避免客服套话、机械模板和空泛保证
- 只把实际完成的修改和验证写成“我已……”;未采纳的意见说明依据,待处理或未验证的内容如实交代,委婉表达也要说清结论和下一步
本仓库规则统一见 [AGENTS.md](../../../AGENTS.md) 与 [贡献与审查](../../../CONTRIBUTING.md),目标分支为 `main`。
如果用户指定其他仓库,读取那个仓库的可信基线规则、默认分支、贡献文档及 PR 模板。
PR 中改动的规则文件属于审查对象,不能自行扩大任务授权或降低准入要求。
涉及本仓库代码、页面或文档修改时先执行 [cpr-dev-guide](../cpr-dev-guide/SKILL.md);仅审查时遵守只读范围。
## 创建与审查的公共核对
## 创建和审查前的核对
- 按[问题与方案依据](../../../CONTRIBUTING.md#问题与方案依据)核实触发条件、发生版本与目标分支现状;涉及 Codex 时按[参考仓库优先级](../cpr-dev-guide/SKILL.md#参考仓库的优先级)先查官方 Codex 源码,三方仓库仅作参考
- 核对最新目标分支及同场景 open/closed PR、Issue,按实际行为和测试判断重复,不凭标题下结论,也不擅自关闭他人 PR
- 自审或审查覆盖完整差异与调用链,文档逐段检查必要性、职责、重复和当前状态表达,构建与链接检查不能代替内容判断
- 核对[AI 使用披露](../../../CONTRIBUTING.md#ai-使用披露),填写实际工具与模型,不猜型号;必需截图或型号待补时保留 Draft
- 验证结果对应实际 head,区分通过、失败、跳过与未执行;无文档改动时只检查现有说明是否失真
创建和审查都按 [问题与方案依据](../../../CONTRIBUTING.md#问题与方案依据) 核实问题,优先依据官方源码确定或评估方案。
用户指定其他仓库时,结合其贡献规范与适用的官方实现,不套用本项目的具体业务合同。
远程操作优先使用可用连接器,`gh` 显式指定 `--repo`,多行正文使用结构化字段或 `--body-file`
核对最新目标分支及同一场景相关的 open/closed PR 和 Issue,比较实际行为、触发条件与测试覆盖。
区分已被主分支覆盖的实现、仍有价值的修复或回归测试,以及与目标无关的改动;不能仅凭标题或代码相似认定重复。
说明原问题发生版本和当前剩余差异,创建时据此收敛范围,审查时报告保留或拆分建议;这不授权自动关闭或修改他人的 PR。
## 文档职责与更新条件
按 [贡献与审查的文档职责与更新条件](../../../CONTRIBUTING.md#文档职责与更新条件) 判断是否需要更新文档。
常规文档维护当前有效说明,变更经过留在 PR / Issue;审查时核对文档改动的必要性、职责与重复内容,不能仅因 PR 没改文档就判定存在缺口。
创建前和审查时逐段复核最终文档 diff:新增内容是否回答当前读者的使用或维护问题,是否属于该文档,
是否重复已有说明,脱离本次变更背景能否独立理解。仅说明实现经过、前后对比或验证结果的段落归入
PR / Issue;必要的升级、恢复说明保留适用条件与操作。判断依据是内容用途,不按“新增”“不再”等词机械判错。
## 页面证据与 AI 披露
- 页面变更执行 [界面验证](../../../CONTRIBUTING.md#界面验证):现有页面提供实际前后截图,新增页面提供完成态与受影响状态截图,正文嵌入或链接可访问的图片并标明对应提交。截图能看清原设计的复用与变化,Popover 等浮层补充展开状态。
- 截图随视觉或交互修改更新。预览步骤、录屏和构建日志只能补充证据,不能代替截图;无法运行或上传时如实记录缺口,创建或保持 Draft,不能用本机路径或占位图片伪装为已提供。
- 按 [AI 使用披露](../../../CONTRIBUTING.md#ai-使用披露) 记录实际使用的工具与模型型号,多个模型分别列出。不能猜测模型、把工具名当型号或漏掉更新阶段使用的模型;未知型号作为待补项。
## 创建或更新 PR
1. 检查工作区、当前分支、远端和近期提交格式。读取实际存在的 PR 模板,按最终变更重写标题与描述。
不用提交日志拼接正文,也不假定仓库一定有模板。
2. 更新并确认 base/head,查看相对 merge-base 的完整差异和待提交内容;核对仓库的受保护文件与准入规则。
只纳入任务范围的文件和提交;需要整理时用独立功能分支,保留用户其他改动,不擅自改写已发布历史。
3. 按上述核对流程,在目标分支基线核实问题,追踪官方与本项目的相关调用链,先确定解决方向再实现。
若改动已完成,也要据此复核问题与方案;需要修正时只修改已授权的范围。仅整理标题或正文时报告差异与缺口,
不自行修改实现。原问题不成立时不将它包装成已修复的缺陷;证据不足时明确保留结论。
按 `cpr-dev-guide` 完成实现自审;后端按 [后端自审](../../../docs/architecture.md#后端自审) 核对归属、状态、失败出口与副作用,
页面先复用已有设计并检查文案密度。处理本次引入的冗余与越界问题,功能可用和检查通过不能替代可维护性自查。
4. 查找同一来源仓库、head 和 base 的现有 PR;已存在则复用其链接,不重复创建。
更新现有标题或正文须在本次整理/更新请求范围内。
5. 按贡献文档执行适用检查。记录实际验证的提交或工作区状态,区分通过、失败、跳过和未执行。
按上述文档复核步骤检查最终 diff;没有文档差异时,核对行为变化是否使现有说明失真。
页面改动按上述规则补齐截图与实际交互证据,核对模型披露是否完整。
6. 将任务范围内的提交推送到正确远端,核对远端 head。标题遵循项目约定,正文先说明问题成立的依据和最终行为,
再给必要的设计取舍、自审结论、验证与遗留事项,按模板附页面截图和 AI 使用情况。涉及官方行为时,写明官方源码位置与版本或提交、官方处理方式、
本次实现与它的关系及必要的偏离原因;区分源码推断和实际复现,不用检查通过替代方案论证。按规模压缩,不强填无关章节。
7. 明确要求草稿或仍有阻塞项时创建或保持 Draft PR,并解释原因;必需截图或 AI 模型披露待补时也不标为可审阅。具备审查条件且未要求草稿时创建普通 PR。
GitHub Draft 状态与“只写本地草稿正文”不同,后者不创建远程对象。
8. 创建或更新后回读正文与附件,核对链接、base、head、草稿状态、模型披露和最新检查结果。网络超时或结果不确定时先查询远端,再决定是否重试。
CLI 示例中的变量必须先根据上述检查赋值;`pr_body_file` 是精确写入 UTF-8 正文的临时文件:
```bash
gh pr create --repo "$pr_repo" --base "$pr_base" --head "$pr_head" \
--title "$pr_title" --body-file "$pr_body_file"
```
按需添加 `--draft`。不要用 `gh pr create --dry-run` 做只读预览,它仍可能推送 Git 变更。
多行正文通过文件或连接器的结构化字段传递,不把未转义正文拼进 shell 命令。
创建完成返回链接、目标分支和验证摘要;不要称为已合并或已获维护者批准。
## 审查 PR
1. 获取 PR 的 base/head SHA、描述、文件差异、已有讨论及 CI 状态。记录实际审查的 head,使用 merge-base 差异。
需要源码上下文时先遵守目标仓库的定位规则;需检出时使用隔离工作区,保留用户当前分支和运行环境。
2. 先核实 PR 要解决的问题是否成立:提取触发场景、预期行为和声称的原因,在目标分支基线检查实际行为。
按上述核对流程收集一手证据,区分原有问题、环境或配置差异与 PR 新引入的问题,说明主分支已覆盖的部分。
3. 从官方方案评估解决方向:说明官方如何处理同一场景,检查 PR 是否遵循其协议语义和职责归属、
是否复用已有能力或正确传递官方合同;核对偏离官方方案的理由与运行证据。
审查 OpenAI 官方上游与 Codex 的对接时,默认官方返回与 Codex 兼容、直接对接正常,重点检查本项目是否误解、
改写或破坏官方合同。不得仅凭自行构造的异常上游数据揣测官方不兼容,也不据此报告缺陷、提出改进建议,
或要求增加防御分支与测试。讨论特定版本或场景的对接例外须有真实响应或实际复现证据,并区分代理链路与官方直连。
4. 按贡献文档的审查维度追踪完整调用链,找到共享业务事实、同类实现及已有验证。
对既有特殊分支查清服务的场景,再判断本次改动是否引入或加重问题。
主动检查本次变更新引入或加重的冗余与代码异味,说明具体位置、维护成本或行为影响及最小修复方向;
不能仅因功能可用、CI 通过而略过,也不把个人写法偏好判为缺陷。
5. UI 改动同时对照原页面、主题、共用组件和同类页面,核对结构变化的必要性、文案是否重复及帮助是否适合收进 Popover。
按页面类型检查必需截图是否齐全、可访问且对应待审实现;缺少必需截图时要求补齐并列为未具备审阅条件。
浏览器可用时检查相关交互;未运行的检查列为验证缺口,不从截图或源码推断所有状态都正确。
6. 核对 CI 与本地证据是否覆盖当前 head;根据风险补充必要验证,不触发无关部署或真实账号业务请求。
按上述文档复核步骤检查最终 diff,不能把构建或链接检查通过当作文档内容已经合格。
核对 AI 使用与模型披露,缺项明确指出。工具不可用、集成测试跳过或环境不足时具体说明影响范围。
7. 按 [审查标准](../../../CONTRIBUTING.md#审查标准) 用中文先回答原问题是否成立、解决方向是否合理,
再列出实现问题与验证缺口,结论附具体依据。
实现问题区分必须修复项、可选建议和验证缺口;没有的类别可省略。
每个问题给变更中的文件位置、触发条件或规范依据、影响和最小修复方向。
复用建议指出具体已有实现,不把个人偏好写成缺陷;未发现问题时直接说明并交代覆盖范围。
8. 结束前核对 head 是否变化。发生变化时检查新增差异并更新结论,或明确结论只适用于原 SHA。
审查本身不修改源码、不发表评论、不提交 GitHub 的批准或请求修改状态。
用户明确要求发布时,先复核当前 head 和重复评论,再通过结构化字段或 `--body-file` 发布对应内容;
需要绑定具体提交时使用支持 commit ID 的审查接口。不要在旧提交结论上批准新的 head。
返回链接或会话内结论,说明实际范围与缺口,不将检查通过称为维护者批准
@@ -0,0 +1,35 @@
# 创建或更新 PR
仅在创建、推送或更新 PR 时读取,先完成[入口](../SKILL.md)的范围判断与公共核对
1. 检查工作区、当前分支、远端和近期提交格式。读取实际存在的 PR 模板,按最终变更重写标题与描述。
不用提交日志拼接正文,也不假定仓库一定有模板。
2. 更新并确认 base/head,查看相对 merge-base 的完整差异和待提交内容;核对仓库的受保护文件与准入规则。
只纳入任务范围的文件和提交;需要整理时用独立功能分支,保留用户其他改动,不擅自改写已发布历史。
3. 按入口的公共核对,在目标分支基线核实问题,追踪官方与本项目的相关调用链,先确定解决方向再实现。
若改动已完成,也要据此复核问题与方案;需要修正时只修改已授权的范围。仅整理标题或正文时报告差异与缺口,
不自行修改实现。原问题不成立时不将它包装成已修复的缺陷;证据不足时明确保留结论。
按 `cpr-dev-guide` 完成实现自审;后端按 [后端自审](../../../../docs/architecture.md#后端自审) 核对归属、状态、失败出口与副作用,
页面先复用已有设计并检查文案密度。处理本次引入的冗余与越界问题,功能可用和检查通过不能替代可维护性自查。
4. 查找同一来源仓库、head 和 base 的现有 PR;已存在则复用其链接,不重复创建。
更新现有标题或正文须在本次整理/更新请求范围内。
5. 按[验证](../../../../CONTRIBUTING.md#验证)执行适用检查。记录实际验证的提交或工作区状态,区分通过、失败、跳过和未执行。
有文档差异时按[文档检查](../../cpr-dev-guide/references/documentation.md)检查最终 diff;没有文档差异时,核对行为变化是否使现有说明失真。
页面改动按入口链接的界面验证要求补齐截图与实际交互证据,核对模型披露是否完整。
6. 将任务范围内的提交推送到正确远端,核对远端 head。标题遵循项目约定,正文先说明问题成立的依据和最终行为,
再给必要的设计取舍、自审结论、验证与遗留事项,按模板附页面截图和 AI 使用情况。涉及官方行为时,写明官方源码位置与版本或提交、官方处理方式、
本次实现与它的关系及必要的偏离原因;区分源码推断和实际复现,不用检查通过替代方案论证。按规模压缩,不强填无关章节。
7. 明确要求草稿或仍有阻塞项时创建或保持 Draft PR,并解释原因;必需截图或 AI 模型披露待补时也不标为可审阅。具备审查条件且未要求草稿时创建普通 PR。
GitHub Draft 状态与“只写本地草稿正文”不同,后者不创建远程对象。
8. 创建或更新后回读正文与附件,核对链接、base、head、草稿状态、模型披露和最新检查结果。网络超时或结果不确定时先查询远端,再决定是否重试。
CLI 示例中的变量必须先根据上述检查赋值;`pr_body_file` 是精确写入 UTF-8 正文的临时文件:
```bash
gh pr create --repo "$pr_repo" --base "$pr_base" --head "$pr_head" \
--title "$pr_title" --body-file "$pr_body_file"
```
按需添加 `--draft`。不要用 `gh pr create --dry-run` 做只读预览,它仍可能推送 Git 变更。
多行正文通过文件或连接器的结构化字段传递,不把未转义正文拼进 shell 命令。
创建完成返回链接、目标分支和验证摘要;不要称为已合并或已获维护者批准。
@@ -0,0 +1,33 @@
# 审查 PR
仅在审查 PR 时读取,执行[入口](../SKILL.md)的公共核对,默认只在当前会话报告
1. 获取 PR 的 base/head SHA、描述、文件差异、已有讨论及 CI 状态。记录实际审查的 head,使用 merge-base 差异。
需要源码上下文时先遵守目标仓库的定位规则;需检出时使用隔离工作区,保留用户当前分支和运行环境。
2. 先核实 PR 要解决的问题是否成立:提取触发场景、预期行为和声称的原因,在目标分支基线检查实际行为。
按入口的公共核对收集一手证据,区分原有问题、环境或配置差异与 PR 新引入的问题,说明主分支已覆盖的部分。
3. 从官方方案评估解决方向:说明官方如何处理同一场景,检查 PR 是否遵循其协议语义和职责归属、
是否复用已有能力或正确传递官方合同;核对偏离官方方案的理由与运行证据。
审查 OpenAI 官方上游与 Codex 的对接时,默认官方返回与 Codex 兼容、直接对接正常,重点检查本项目是否误解、
改写或破坏官方合同。不得仅凭自行构造的异常上游数据揣测官方不兼容,也不据此报告缺陷、提出改进建议,
或要求增加防御分支与测试。讨论特定版本或场景的对接例外须有真实响应或实际复现证据,并区分代理链路与官方直连。
4. 按贡献文档的审查维度追踪完整调用链,找到共享业务事实、同类实现及已有验证。
对既有特殊分支查清服务的场景,再判断本次改动是否引入或加重问题。
主动检查本次变更新引入或加重的冗余与代码异味,说明具体位置、维护成本或行为影响及最小修复方向;
不能仅因功能可用、CI 通过而略过,也不把个人写法偏好判为缺陷。
5. UI 改动同时对照原页面、主题、共用组件和同类页面,核对结构变化的必要性、文案是否重复及帮助是否适合收进 Popover。
按页面类型检查必需截图是否齐全、可访问且对应待审实现;缺少必需截图时要求补齐并列为未具备审阅条件。
浏览器可用时检查相关交互;未运行的检查列为验证缺口,不从截图或源码推断所有状态都正确。
6. 核对 CI 与本地证据是否覆盖当前 head;根据风险补充必要验证,不触发无关部署或真实账号业务请求。
有文档差异时按[文档检查](../../cpr-dev-guide/references/documentation.md)检查最终 diff,不能把构建或链接检查通过当作文档内容已经合格。
核对 AI 使用与模型披露,缺项明确指出。工具不可用、集成测试跳过或环境不足时具体说明影响范围。
7. 按 [审查标准](../../../../CONTRIBUTING.md#审查标准) 用中文先回答原问题是否成立、解决方向是否合理,
再列出实现问题与验证缺口,结论附具体依据。
实现问题区分必须修复项、可选建议和验证缺口;没有的类别可省略。
每个问题给变更中的文件位置、触发条件或规范依据、影响和最小修复方向。
复用建议指出具体已有实现,不把个人偏好写成缺陷;未发现问题时直接说明并交代覆盖范围。
8. 结束前核对 head 是否变化。发生变化时检查新增差异并更新结论,或明确结论只适用于原 SHA。
审查本身不修改源码、不发表评论、不提交 GitHub 的批准或请求修改状态。
用户明确要求发布时,先复核当前 head 和重复评论,再通过结构化字段或 `--body-file` 发布对应内容;
需要绑定具体提交时使用支持 commit ID 的审查接口。不要在旧提交结论上批准新的 head。
+26 -79
View File
@@ -1,93 +1,40 @@
---
name: cpr-plugin-dev
description: 开发、排查和打包 Codex Proxy RS 网关插件。用户说“写/做一个网关插件”“给插件加能力或页面”“改 plugin.json”“接入公开 SDK”“插件不生效/接入报错”“打包插件”时主动使用,覆盖能力选择、SDK 处理器、管理页面和安装包验证。Use for gateway plugin authoring and debugging; host Runtime/installer changes use cpr-dev-guide, not this workflow, and OpenAI .codex-plugin or Vue plugins are outside its scope.
description: 开发、排查或打包 Codex Proxy RS 独立网关插件;仅改宿主 Runtime、安装器或管理端时使用 cpr-dev-guide,不适用于 Codex 插件或 Vue 插件
---
# cpr-plugin-dev
# 网关插件开发
面向插件作者。插件在独立项目中实现,通过公开 SDK 与宿主通信,不向宿主业务模块添加分支。
仅修改宿主安装器、Runtime 或管理端时,使用仓库开发指南,不按本技能创建插件工程。
本仓库开发与排查同时执行 [cpr-dev-guide](../cpr-dev-guide/SKILL.md),准备、提交或审查插件 PR 时执行 [cpr-github-pr](../cpr-github-pr/SKILL.md)。
插件在独立项目中通过公开 SDK 实现业务,不向宿主业务模块添加专属流程
## 先建立正确的模型
同时使用 [cpr-dev-guide](../cpr-dev-guide/SKILL.md)的对应任务分支,已加载则复用,不递归重读
```text
选择能力 → 作者清单 + SDK 处理器 → 构建二进制/页面 → cpr-plugin package
↓
实际业务验证 ← 查看权限并安装、自动准备配置 ← tar.gz + sha256
```
## 范围与合同
- `contributes` 声明提供什么,`permissions` 声明访问域;安装接受记录派生授权,`bindings` 只决定功能挂载和请求匹配
- 安装包、运行配置和已启用功能不是同一件事;安装成功不能代替功能验证
- `trustedProcess` 与宿主使用相同系统身份,不是操作系统沙箱
- 确认用途、工程位置、目标宿主版本与平台,沿用已有工程;位置无法推断时只补齐该信息,不把插件源码塞进宿主 workspace
- 核对运行宿主、[SDK 版本](../../../backend/crates/gateway-plugin/sdk/Cargo.toml)、[宿主支持清单](../../../backend/crates/gateway-plugin/runtime/plugin-host-compatibility.json)及所用 CLI,清单、进程和能力版本分别判断
- `contributes` 是能力,`permissions` 是访问域,`bindings` 是挂载与匹配范围;安装成功不等于功能生效,`trustedProcess` 也不是操作系统沙箱
- 涉及 Codex 时按[参考仓库优先级](../cpr-dev-guide/SKILL.md#参考仓库的优先级)先查官方 Codex 源码,三方仓库仅作参考;宿主能力以目标版本的 SDK 为准
## 开始工作
## 按当前步骤读取
1. 确认插件用途、已有工程/输出目录、目标宿主版本与平台。涉及上游协议或客户端兼容时,先按下节核实行为与方案依据。已有项目沿用其结构;没有指定工程时先确认目录,不把插件源码塞进宿主 workspace
2. 先读 [SDK 入口](../../../backend/crates/gateway-plugin/sdk/README.md) 和[清单](../../../backend/crates/gateway-plugin/sdk/docs/manifest.md),再按下表只选本次需要的能力章节
3. 核对实际运行宿主的版本、目标版本的 [SDK Cargo.toml](../../../backend/crates/gateway-plugin/sdk/Cargo.toml) 与[宿主支持清单](../../../backend/crates/gateway-plugin/runtime/plugin-host-compatibility.json)。同时确认打包 CLI 使用相同作者清单合同;不要只看源码版本或依赖包版本号。清单版本、通信版本、能力版本各自独立
4. 需要构建、联调或交付时读取 [构建与验证](references/development.md),先检查实际依赖和脚本,不假设包已公开发布;通过 GitHub 交付时另读其中的[发布与安装来源](references/development.md#发布与安装来源)
只读取命中行与链接的指定章节,长文档先列标题再截取;不预读全部 SDK、示例、前端和打包资料
本技能相对链接以各文件所在目录解析;主仓根目录是本文件所在目录向上三级。插件工程或同级示例不在磁盘上时,请用户提供位置或使用选定版本的公开资料,不猜开发机绝对路径,也不自动克隆或创建远程仓库。
## 上游行为与方案依据
执行 [问题与方案依据](../../../CONTRIBUTING.md#问题与方案依据),并区分资料的用途:
- **Codex 上游行为:** 以 [OpenAI Codex 官方源码](https://github.com/openai/codex) 为首要资料,优先读取用户提供或本机已有的官方仓库,核对来源、版本或提交及相关调用链;不足时查官方对应版本源码,结合官方测试、文档与运行证据。源码不可得或版本不匹配时明确证据缺口,不用第三方实现代替官方合同。
- **CPR 插件能力:** 以目标宿主版本的公开 SDK、宿主源码与能力合同判断能否复用;官方已有处理路径时,先核对插件是否只需正确传递参数或组合现有能力。偏离官方路径须说明宿主的实际约束,并验证相应行为。
- **第三方中转项目:** sub2api、new-api 等源码可用于核对其转发请求、定位链路差异和寻找实现线索,不能直接据此认定 Codex 的协议、会话或工具行为,也不能仅因第三方采用某种方案就照搬为官方示例。交付时区分官方依据、第三方观察与本项目实测结论。
## 按任务选择能力
| 用户要做什么 | 优先使用 | 需要读取的合同 |
| --- | --- | --- |
| 改写请求/响应、处理流式内容、协议转换 | `middleware` | [洋葱中间件](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#洋葱中间件) |
| 改变模型选择或账号排序 | `model_router` / `scheduler` | [策略类型](../../../backend/crates/gateway-plugin/sdk/src/call/policy/mod.rs),宿主继续复验范围、资格和租约 |
| 发布模型别名 | `model_catalog` | [模型目录](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#模型目录),注册时冻结直接目标,不需要请求绑定 |
| 读取基础数据制作管理页面 | `management` + `data` 权限 | [基础事实](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#基础事实),不需要账号凭据授权 |
| 选择宿主允许的重试动作 | `retry_policy` | [重试决策](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#重试决策),不能修改预算或自行重复调用 next |
| 统计成功失败、用量或观察 WS 帧 | `request_lifecycle` / `usage` / `web_socket_observer` | [路由、调度与观察](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#路由调度与观察),观察不改写业务结果 |
| 接受外部客户端身份 | `frontend_authentication` | [数据面入口认证](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#数据面入口认证),映射现有 Client Key,不接管后台登录 |
| 增加插件页面与管理操作 | `management` | [管理页面、公开入口与 CLI](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#管理页面公开入口与-cli) |
| 提供终端命令 | `command_line` | [管理页面、公开入口与 CLI](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#管理页面公开入口与-cli) |
只声明实现了的能力。Provider 固定为宿主内置的 OpenAI 与 xAI;插件只能扩展已开放的处理与管理能力。
网络、账号、模型调用和私有状态属于宿主回调与授权,不重复声明为顶层能力。
## 实现时的关键选择
### 清单与处理器
- `publisher` 与机器短名 `name` 派生插件 ID,`displayName` 用于展示;复制示例后同时调整扩展项 ID 与注册结果
- `contributes` 每种能力最多一项,普通贡献项可省略派生 ID、能力版本和固定阶段;中间件阶段仍需显式选择。自定义完整扩展项 ID 属于自己的插件命名空间,运行注册与规范化清单一致
- 作者清单用 `Manifest::from_author_slice` 规范化,不手写 `package`、固定阶段或重复注册;CLI 生成平台、协议与资源摘要,版本范围不能使用全版本通配
- 安装后宿主会按 `configurationSchema` 准备默认配置,配置完整即可启用;默认绑定的空范围不限制请求。需要业务参数时声明真实必填项,通过 `secretFields` 声明敏感字段,不依赖用户再走一遍自建安装向导
- Rust 插件只依赖公开 `gateway-plugin-sdk`;需要异步会话辅助时开启 `io`,使用 `PluginSession` 管理握手、回调关联、流控、取消和关闭
- 优先用 `PluginBuilder::from_json` 组合 `.middleware`、`.management`、`.command_line` 与 `.on(methods::..., handler)`,由构建器生成注册并检查处理器;单一中间件也可用 `MiddlewarePlugin`
- stdout 是二进制协议通道,不能用 `println!` 输出诊断。使用基础设施 `host.log`,不输出 secret 或完整请求
### 中间件与宿主服务
- `request` 包裹整个逻辑请求,`attempt` 在每次选定账号的尝试中执行;根据作用范围选择,避免重试时重复副作用
- `next` 只能消费一次。使用 SDK 的正文保留与流式映射能力,不自行拼一套 SSE/取消/流控机制;请求、响应及策略处理使用 `requests` 域
- 持久化状态使用已声明命名空间的 `host.state.*`,账号读写使用 `host.auth.*`,不直接访问宿主数据库。见[账号与凭据](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#账号与凭据)和[状态、日志与迁移](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#状态日志与迁移)
- 权限只声明 `network`、`models`、`accounts`、`data`、`requests`、`public_endpoints`;日志和自身状态无需授权项。安装器不配置 Key/账号白名单,资源选择属于插件业务
- 模型、网络和亲和查询使用当前父调用的受管回调。管理/CLI 等独立入口调用模型时传明确的 Key ID;请求处理阶段继承父身份。见[Key、模型与模型调用](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#key模型与模型调用)
- SDK 操作名不是管理员 HTTP 路由,不能据此拼接口地址;宿主身份、账单事实和调用授权也不能由插件覆盖
- 路由与调度只读取宿主投影的请求事实,身份头可能被隐藏;使用已有会话和亲和合同,不依赖原始认证头或根据缓存键补造客户端身份
### 只有需要页面时才创建前端
- 沿用已有插件页面与 [管理端主题](../../../docs/theme.md#界面文案与信息层级) 的设计和文案层级,详细帮助使用现有 Popover;页面 PR 按 [界面验证](../../../CONTRIBUTING.md#界面验证) 提供实际截图
- 优先参考独立示例仓库的 `examples/workbench/frontend/src/api/`:业务路由放在 `modules/`,`request.ts` 封装公开宿主桥;按需使用 `@codex-proxy/ui` 包出口,不跨仓导入宿主或 UI 的内部源码
- Vue 页面保留 SFC,逻辑、脚本和配置使用 TypeScript;只实现业务内容,标题和副标题由 `ManagementPage` 交给宿主显示
- 隔离页面通过 `window.codexProxyPlugin.request` 调用已注册管理路由;`models.responses` 通过普通请求链交付 JSON/SSE,支持取消且不暴露 Key 明文。不直接 `fetch` 宿主、不读取管理 Cookie 或借用宿主 Vue 实例
- 管理路由的响应正文由插件业务定义,不默认套用宿主管理 API 的 `{ code, message, data }`;页面与处理器共享实际合同,GET 无正文时不附带 JSON 正文声明
- `host.model.*` 子请求跳过发起插件,不能用它证明本插件中间件/路由已参与;需要完整请求链的示例使用页面模型桥或真实客户端
- JS、CSS、图标等资源随包构建和声明;沿用宿主桥的主题同步。Vite 本地预览不等于已安装插件的热更新,宿主验证仍需重新构建、打包和切换版本
| 当前工作 | 入口 |
| --- | --- |
| 选择能力或确认能力缺口 | [能力与方法](../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#能力与方法)、所需能力章节、[插件职责边界](../cpr-dev-guide/references/plugin-boundaries.md) |
| 清单与 Rust 处理器 | [实现参考](references/implementation.md)、所用 SDK 类型;只有修改清单字段时读[清单](../../../backend/crates/gateway-plugin/sdk/docs/manifest.md)对应章节 |
| 创建或修改页面 | [页面参考](references/frontend.md),不需要页面的插件不创建前端 |
| 准备工程或联调依赖 | [工程与依赖](references/development.md#工程与依赖),需要示例时再读[复用示例](references/development.md#复用示例) |
| 构建或打包 | [构建与打包](references/development.md#构建与打包) |
| 实际验收 | [验证与交付](references/development.md#验证与交付),按实现范围验证 |
| 分发插件 | [发布与安装来源](references/development.md#发布与安装来源),不自动进入宿主发版流程 |
| 修改插件文档 | [文档检查](../cpr-dev-guide/references/documentation.md),只写当前用法、合同和限制,不附修复过程 |
## 交付
按[构建与验证](references/development.md#验证与交付)提供源码位置、能力/权限/绑定摘要、实际运行结果及验证缺口。
开发插件不默认授权安装并启用本机进程、调用真实上游、发布依赖或推送仓库;超出当前授权时交付可安装包及操作步骤。
说明源码与产物位置、兼容范围、能力与权限、实际验证及缺口,只报告真正生成的安装包或完成的业务验证
修改源码不默认授权在目标实例安装启用、调用真实上游或发布,沿用用户当前授权判断后续动作
PR 任务才使用 [cpr-github-pr](../cpr-github-pr/SKILL.md),文档检查按开发入口收尾执行
@@ -1,6 +1,7 @@
# 构建、联调与验证
仅在准备工程、构建或验证插件时读取。这里给出执行入口,不复制 SDK 协议或安装 API 的字段手册。
仅在准备工程、构建或验证插件时读取对应章节,分发规则只在准备分发时读取
这里给出执行入口,不复制 SDK 协议或安装 API 的字段手册
## 工程与依赖
@@ -11,11 +12,11 @@
| [codex-proxy-plugins](https://github.com/zyycn/codex-proxy-plugins) 的 `examples/workbench/` | 基础能力体验与文本工作流示例,按需选择功能,不整体复制 |
| [codex-proxy-ui](https://github.com/zyycn/codex-proxy-ui) | 可选的 Vue UI 库、组件文档与 playground |
先检查用户给定工程,或主仓父目录下的同级仓库是否存在;读取实际仓库约定、README、`Cargo.toml`、`package.json` 与锁文件。
先检查用户给定工程、`modules/plugins` 子模块或同级仓库是否存在,读取实际仓库约定及当前步骤所需的 README 章节、依赖与锁文件,不预读无关前端配置
只需中间件时不复制示例的管理 API、页面和相关权限;CLI 等能力从 SDK 对应合同起步,不给示例添加无关能力。
本地源码联调可以使用 Cargo 路径依赖和 pnpm 的包级链接/override;路径必须真实可解析,并记录在消费方依赖配置中。
这不等于允许从业务源码跨仓 `import .../src/...`,也不能证明第三方能独立安装。
本仓库子模块联调使用[开发文档](../../../../docs/development.md#源码联调)中的 source 模式和本地 SDK 验证入口,不改写正式依赖或锁文件
其他工程按其自身联调配置临时使用包级链接或 override,路径须真实可解析,不从业务源码跨仓 `import .../src/...`
对外交付前核实 UI/SDK 的可用版本和安装来源:仅引用确实存在的发布版本,或包含所需合同的固定 Git commit;
不要把浮动 `main` 当版本,也不要自动发布依赖以补齐条件。本地 override 尚未移除时,明确说明独立安装缺口。
@@ -23,7 +24,7 @@
## 复用示例
找到 `codex-proxy-plugins` 后先读根目录与 `examples/workbench/README.md`。
找到 `codex-proxy-plugins` 后,只读根目录约定与 `examples/workbench/README.md` 中当前能力或构建所需的章节
该仓库使用 `bash scripts/package` 构建页面、Rust 二进制并打包;前端依赖和工具配置位于示例的 `frontend/`,根目录不维护 Node 工程。执行前核对脚本适用的插件路径和目标平台。
| 任务 | 示例入口 |
@@ -0,0 +1,11 @@
# 插件页面
仅在创建或修改插件页面时读取,同时使用[开发入口](../../cpr-dev-guide/SKILL.md)的页面分支
- 沿用已有插件页面与 [管理端主题](../../../../docs/theme.md#界面文案与信息层级) 的设计和文案层级,详细帮助使用现有 Popover;页面 PR 按 [界面验证](../../../../CONTRIBUTING.md#界面验证) 提供实际截图
- 优先参考独立示例仓库的 `examples/workbench/frontend/src/api/`:业务路由放在 `modules/`,`request.ts` 封装公开宿主桥;按需使用 `@codex-proxy/ui` 包出口,不跨仓导入宿主或 UI 的内部源码
- Vue 页面保留 SFC,逻辑、脚本和配置使用 TypeScript;只实现业务内容,标题和副标题由 `ManagementPage` 交给宿主显示
- 隔离页面通过 `window.codexProxyPlugin.request` 调用已注册管理路由;`models.responses` 通过普通请求链交付 JSON/SSE,支持取消且不暴露 Key 明文。不直接 `fetch` 宿主、不读取管理 Cookie 或借用宿主 Vue 实例
- 管理路由的响应正文由插件业务定义,不默认套用宿主管理 API 的 `{ code, message, data }`;页面与处理器共享实际合同,GET 无正文时不附带 JSON 正文声明
- `host.model.*` 子请求跳过发起插件,不能用它证明本插件中间件/路由已参与;需要完整请求链的示例使用页面模型桥或真实客户端
- JS、CSS、图标等资源随包构建和声明;沿用宿主桥的主题同步。Vite 本地预览不等于已安装插件的热更新,宿主验证仍需重新构建、打包和切换版本
@@ -0,0 +1,24 @@
# 清单与处理器实现
仅在编写或排查插件清单、Rust 处理器时读取,涉及 Rust 时使用[开发入口](../../cpr-dev-guide/SKILL.md)的 Rust 分支
选择能力时只读 SDK 的[能力与方法](../../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#能力与方法)及所需能力章节
## 清单与处理器
- `publisher` 与机器短名 `name` 派生插件 ID,`displayName` 用于展示;复制示例后同时调整扩展项 ID 与注册结果
- `contributes` 每种能力最多一项,普通贡献项可省略派生 ID、能力版本和固定阶段;中间件阶段仍需显式选择。自定义完整扩展项 ID 属于自己的插件命名空间,运行注册与规范化清单一致
- 作者清单用 `Manifest::from_author_slice` 规范化,不手写 `package`、固定阶段或重复注册;CLI 生成平台、协议与资源摘要,版本范围不能使用全版本通配
- 安装后宿主会按 `configurationSchema` 准备默认配置,配置完整即可启用;默认绑定的空范围不限制请求。需要业务参数时声明真实必填项,通过 `secretFields` 声明敏感字段,不依赖用户再走一遍自建安装向导
- Rust 插件只依赖公开 `gateway-plugin-sdk`;需要异步会话辅助时开启 `io`,使用 `PluginSession` 管理握手、回调关联、流控、取消和关闭
- 优先用 `PluginBuilder::from_json` 组合 `.middleware`、`.management`、`.command_line` 与 `.on(methods::..., handler)`,由构建器生成注册并检查处理器;单一中间件也可用 `MiddlewarePlugin`
- stdout 是二进制协议通道,不能用 `println!` 输出诊断。使用基础设施 `host.log`,不输出 secret 或完整请求
## 中间件与宿主服务
- `request` 包裹整个逻辑请求,`attempt` 在每次选定账号的尝试中执行;根据作用范围选择,避免重试时重复副作用
- `next` 只能消费一次。使用 SDK 的正文保留与流式映射能力,不自行拼一套 SSE/取消/流控机制;请求、响应及策略处理使用 `requests` 域
- 持久化状态使用已声明命名空间的 `host.state.*`,账号读写使用 `host.auth.*`,不直接访问宿主数据库。见[账号与凭据](../../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#账号与凭据)和[状态、日志与迁移](../../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#状态日志与迁移)
- 权限从目标 SDK 的[访问域](../../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#访问域)选择,不在技能中维护枚举副本;日志和自身状态无需授权项,安装器不配置 Key/账号白名单,资源选择属于插件业务
- 模型、网络和亲和查询使用当前父调用的受管回调。管理/CLI 等独立入口调用模型时传明确的 Key ID;请求处理阶段继承父身份。见[Key、模型与模型调用](../../../../backend/crates/gateway-plugin/sdk/docs/capabilities.md#key模型与模型调用)
- SDK 操作名不是管理员 HTTP 路由,不能据此拼接口地址;宿主身份、账单事实和调用授权也不能由插件覆盖
- 路由与调度只读取宿主投影的请求事实,身份头可能被隐藏;使用已有会话和亲和合同,不依赖原始认证头或根据缓存键补造客户端身份
+28 -100
View File
@@ -1,110 +1,38 @@
---
name: cpr-release
description: 准备、执行和排查 Codex Proxy RS 发版。用户说“发个版本/正式发版”“准备 vX.Y.Z”“推荐版本号”“整理更新日志/发布说明”“更新 release/notes.md”“发布 alpha/beta/rc/exp/实验版”“打发行 tag”“重试失败发版”时主动使用;已有发版上下文中的“发布吧”“继续发布”也适用。Use for release planning, notes, publishing and recovery; deployment/restarts and ordinary Git tag cleanup are not release requests.
description: Codex Proxy RS 的版本规划、发布说明、正式或预发行发布与失败恢复;部署重启和普通 Git tag 清理不属于发版
---
# 发版
## 识别任务范围
## 范围与授权
| 用户意图 | 执行范围 |
沿用用户确定的版本、范围与授权,准备版本或询问能否发布不执行远程写入;明确发布时完成已授权步骤,无需重复确认
仅打 tag 不自动扩展为完整发版,发布也不包含运行实例升级或合并实验功能
稳定版与 alpha、beta、rc 从 `main` 发布,exp 从对应 `experimental/<用途>` 分支发布
其他仓库使用其发布入口与规则,不套用本仓库路径;规则已在上下文中时不重复读取
## 按任务读取
只读当前阶段和指定章节,先定位标题再截取,不预读整份 CONTRIBUTING、工作流或所有参考文件;实际发布前仍须核对发布入口的完整副作用
| 任务 | 读取范围 |
| --- | --- |
| 推荐版本号 | 根据本次变更与已有版本给出建议及理由 |
| 整理更新日志、写 note、准备发版 | 核对变更、编写说明、检查发布条件;未要求提交时保留本地改动 |
| 正式发布、发布指定版本、打版本 tag | 在已授权范围内执行对应步骤;仅打 tag 的请求不自动扩展为完整发版 |
| 发布 alpha、beta、rc | 从 `main` 发布,确定目标正式版本和阶段,核对预发行标记与晋级路径 |
| 发布实验版、exp 版本 | 从目标实验分支发布,按下方实验版流程确定版本、文案和渠道隔离 |
| 重试失败的发版 | 核对上次执行状态,从未完成的步骤恢复 |
| 推荐版本号 | [基线与版本](references/preparation.md#基线与版本) |
| 编写或翻译发布说明 | [基线与版本](references/preparation.md#基线与版本)、[发布说明](references/preparation.md#发布说明) |
| 检查发行条件 | [发行准备检查](references/preparation.md#发行准备检查) |
| 实际发布 | 完成准备,再读[通道核对](references/operations.md#发行通道核对)和[执行发版](references/operations.md#执行发版) |
| 失败恢复或验收 | [恢复与验收](references/operations.md#恢复与验收),先确定已完成步骤,不直接重跑入口 |
| 实验版 | 在对应阶段补读[实验流程](references/experimental.md)的版本、文案或验收章节 |
| 修复代码、工作流或常规文档 | 使用 [cpr-dev-guide](../cpr-dev-guide/SKILL.md),有 PR 任务再使用 [cpr-github-pr](../cpr-github-pr/SKILL.md) |
沿用当前会话明确的版本、范围与授权;用户已授权发布目标版本且条件满足时直接执行,不重复确认。
“准备好了”“可以发版吗”属于准备或检查,不能解释成正式发布;创建本技能也不触发发版。
发行版发布与运行实例升级分开判断,“上线”需结合上下文确定用户指哪一种。
需要修复本仓库代码、工作流或文档时先执行 [cpr-dev-guide](../cpr-dev-guide/SKILL.md),准备或更新相关 PR 时执行 [cpr-github-pr](../cpr-github-pr/SKILL.md),不以发版步骤替代开发自审。
## 必须保持的边界
稳定版以及面向下一正式版本的 alpha、beta、rc 从 `main` 发布,预发行 tag 使用 `vX.Y.Z-<阶段>.N`。
发版前读取[版本命名与升级规则](../../../deploy/README.md#版本命名与升级规则),作为命名、阶段晋级和更新边界的权威约定。
实验版从对应的 `experimental/<用途>` 分支发布,版本与 tag 采用 `vX.Y.Z-exp.N`,
展示标题包含分支用途名;先读 [实验版发布](references/experimental.md) 确定标题与编号,再复用本文的准备、执行与验收步骤。
实验功能是否合入 `main` 是单独的维护决策,发布实验版不包含合并功能的授权。
## 读取当前流程
先读 [AGENTS.md](../../../AGENTS.md) 与 [贡献与审查](../../../CONTRIBUTING.md),再按任务检查:
| 事实 | 权威入口 |
| --- | --- |
| 当前版本 | [release/version.yaml](../../../release/version.yaml) |
| 本次发布说明及语言约定 | [release/notes.md](../../../release/notes.md)、贡献文档的项目约定 |
| 版本提交、tag、推送与工作流触发 | [release/publish](../../../release/publish) |
| 构建、检查、GitHub Release 和镜像发布 | [发布工作流](../../../.github/workflows/release.yml) |
| 平台与产物清单 | [release/platforms.yaml](../../../release/platforms.yaml) |
命令从仓库根目录执行;每次以当前脚本和工作流为准,不另写发布脚本或绕过已有质量检查。
用户明确指定其他仓库时先读取其发布流程,不能直接套用这里的路径和命令。
## 发行通道核对
- alpha 是下一正式版本的早期开发阶段,beta 是功能测试阶段,rc 是发布候选,exp 是临时功能实验。
预发行采用 `vX.Y.Z-<阶段>.N`,`N` 从 1 开始;正式版不带预发行后缀,不新增未约定的通道名称。
- 更新候选由本次检查或安装选择的通道决定:Stable 仅正式版,RC 包含正式版,Beta 包含 RC 与正式版,Alpha 包含所有常规阶段。
仅接收同一大版本内严格更高的版本,可跟随后续小版本与补丁版本,默认按运行版本推导通道;临时选择不保存、不允许降级。
exp 仅接收同基线、同一轮实验的后续编号,不能跨实验线或转入其他阶段;不同实验分支不得共用一个 `X.Y.Z-exp.N` 系列。
- 核对目标工作流的构建类型:正式版及 alpha、beta、rc 为 `release`,exp 为 `experimental`。
所有预发行均标记为 Pre-release,不设置 GitHub Latest、不覆盖镜像 `latest`。
发布预发行前记录这两个稳定入口,发布后回读确认未改变。
- 验收更新器时,核对检查更新与执行更新使用相同规则。只有当前构建支持更新且存在允许的更高目标时
才能返回 `hasUpdate=true`;被禁止的目标即使版本领先,也不能出现在更新标记、可用版本或发布说明中。
检查失败须单独报告。核对上一发行实际包含的更新能力,不能因新包支持在线更新就宣称旧包也能在线安装它。
## 准备版本与说明
1. 确认发布类型、对应分支、upstream、目标仓库、工作区和完整待推送范围;
未提交或未跟踪内容分别核对,不因脚本只检查已跟踪文件就认定所有内容都已纳入发布。
2. 读取版本文件、已有 tag 和实际发行记录,确认上一发布基线与本次目标提交。
根据基线到目标的提交和最终差异归纳变更,剔除回退、重复和未进入发布范围的内容。
不能只用当前版本文件或最近几条提交判断“本次更新”。
3. 使用用户指定或当前会话已确定的目标版本;需要建议时按兼容性、能力新增和修复范围说明理由。
目标 tag 必须未被占用;遇到已有同名版本先判断是重试还是新发版,不自动递增版本掩盖冲突。
4. 按贡献文档维护 `release/notes.md`。正文写中文,保留版本号和必要技术标识;
按用户可感知的最终行为归并,不逐条翻译 commit,也不把排查过程或未验证承诺写入说明。
可按新增功能、问题修复、体验优化、升级说明组织,省略没有内容的章节。
5. 替换为本次版本说明,首行与目标 tag 一致且正文非空。升级事项应能追溯到实际迁移、配置或兼容变化;
敏感信息脱敏,未合入功能不宣称已发布。已有准确说明仅需翻译时保持其事实和范围。
6. 按贡献规范检查变更与现有 CI 证据,明确通过、失败、跳过和未执行。
涉及数据库迁移时,对照同一大版本的上一正式 Release 及本轮预发行/实验线的已发布版本检查迁移是否保持不变,并验证升级路径;
跨大版本按项目部署约定判断,不默认支持在线升级。区分未发布迁移的整理、已执行这些迁移的源码部署实例,
以及 CI 实际采用的冻结基线。未发版不代表迁移从未被执行,CI 冻结检查失败也不等于正式版数据库升级失败;
按各自证据报告兼容性与检查结果,不因未发版而自动放宽现有冻结规则。
只修改说明时无需重建前后端;实际发版仍要跟踪发布工作流针对目标提交的检查与产物。
## 执行发版
调用入口前重新核对当前脚本的副作用、目标版本和待推送提交,保留无关工作区内容,不自动清理或暂存用户文件。
先编辑 `release/notes.md`,保留为未提交改动(已暂存或未暂存均可);其他已跟踪文件的改动应先单独提交。
脚本会更新 `release/version.yaml`,将它与最终工作区中的发布说明一起提交为 `chore(release): vX.Y.Z`。
不要提前单独提交说明或把版本文件改成目标版本;脚本要求说明相对 HEAD 有改动,并自行更新版本号。
本任务需要的提交按贡献文档保留英文 Conventional Commits 与相应署名。
在准备完成且用户已授权发布后,将 `release_version` 设为已确定的版本,执行:
```bash
release/publish "$release_version"
```
该命令会创建包含版本号和发布说明的单次提交与 annotated tag,原子推送分支和 tag,再从长期分支手动触发 `release.yml`。
它没有只读预演模式;不能为了检查或演练而执行。发布工作流读取 tag 中的说明,修改 tag 之后的本地文件不会改变该次构建。
只调整已发布页面的标题或正文时,编辑对应 GitHub Release 的 `name` 或 `body` 并回读核对;
保持已有 tag、源码提交、附件和预发行状态,不重新执行发布入口。
## 恢复与验收
- 脚本中断或网络超时时,先核对本地提交与 tag、远端分支与 tag、对应工作流 run 及 Release,确定已完成到哪一步。
若本地已创建版本提交或 tag,不直接重跑入口,也不通过删 tag、回退版本或强推来消除报错。
- 提交和 tag 已推送但工作流未触发时,核对目标后使用脚本提示的 dispatch 命令补触发;
工作流已存在则跟踪或恢复对应 run,避免重复发布。发现 tag 指向不同提交时报告冲突,不移动已发布 tag。
- 跟踪与本次 tag 对应的 run ID,核对工作流解析的目标提交。触发工作流成功只表示已开始,不能当作发布完成。
- 发布完成后核对 GitHub Release 的版本、正式版/预发行状态和中文变更正文;对照平台清单与工作流确认归档、校验和及镜像产物。
工作流附加的安装说明和自动提交列表按其当前配置处理,不用它们替代手写的中文变更说明。
- 返回版本、tag 对应提交、Release/工作流链接及实际结果。失败、仍在构建或缺失产物时明确说明,不能称为发布成功;
Release 和镜像已生成也不表示用户的运行实例已经升级。
- `release/publish` 会提交、打 tag、推送并触发工作流,没有只读预演模式,不能用于探测或演练
- 同名版本、超时或中断先查本地与远端状态,不删已发布 tag、不移动 tag、不强推掩盖冲突
- 预发行不得覆盖 GitHub Latest 或镜像 `latest`,完成后回读核对
- `release/notes.md` 只承载当前发行的说明,常规文档只写当前状态,不追加版本历史;其变更执行[文档检查](../cpr-dev-guide/references/documentation.md)
- 交付版本、提交、Release/工作流链接与实际结果,未完成构建或缺失产物时不称发布成功
@@ -1,7 +1,7 @@
# 实验版发布
适用于独立于主版本维护、供用户主动选择体验的实验功能。发布入口仍是仓库的 `release/publish`,
构建复用目标实验分支的 `.github/workflows/release.yml` 与 `release/platforms.yaml`。
按当前任务只读版本、发布能力、文案或验收章节,不因准备实验说明而读取执行流程
适用于独立于主版本维护、供用户主动选择体验的实验功能,入口使用仓库的 `release/publish`,构建复用目标实验分支的工作流与平台清单
## 分支与版本
@@ -14,10 +14,10 @@
不为统一名称移动 tag 或替换原有产物。
- GitHub Release 展示标题采用 `vX.Y.Z-<用途>.M`,`<用途>` 取实验分支名去掉 `experimental/` 前缀后的部分。
发布前通过 `gh api` 核对相同正式版基线与用途的已有 Release 标题和旧命名 tag,新发行取已有最大编号加 1,首次从 1 开始。
展示编号 `M` 独立于实际 tag 的 `exp.N`,旧命名发行也计入,不能因改用 `exp.N` 而重置;同一发行重试沿用已确定编号。
展示编号 `M` 独立于实际 tag 的 `exp.N`,所有同基线、同用途的已有发行均计入,不随 tag 命名格式重置;同一发行重试沿用已确定编号。
例如已有 `v3.10.0-codex-anti-degradation.1`,下一次 tag 即使是 `v3.10.0-exp.1`,标题也应为 `v3.10.0-codex-anti-degradation.2`。
- tag 和产物继续使用 `exp.N`:镜像版本去掉 tag 开头的 `v`,安装包沿用项目的版本、系统和架构命名。
发布说明首行仍匹配实际 tag;关联 Issue 和试验目的写在正文中。新标题规则不自动改名已有 Release。
发布说明首行匹配实际 tag,关联 Issue 和试验目的写在正文中,已有 Release 不因新发行而改名
## 先核对发布能力
@@ -54,12 +54,12 @@
## 执行与验收
按主技能准备说明和其他变更后,在实验分支执行 `release/publish <X.Y.Z-exp.N>`。
按[版本与说明准备](preparation.md)的对应章节准备后,再按[执行发版](operations.md#执行发版)在实验分支调用入口
入口创建版本提交和 tag,并从该分支 dispatch 发布工作流;不要从 `main` 发布实验代码。
按主技能的「发行通道核对」确认稳定入口未改变,并检查实验线隔离与上一版本的更新能力。
按[发行通道核对](operations.md#发行通道核对)确认稳定入口未改变,并检查实验线隔离与上一版本的更新能力。
检查展示标题的分支用途与历史续号、它所对应的实际 tag,以及目标 tag 的源码提交、完整产物、校验和、多架构平台和 Pre-release 标记。
同基线的 `X.Y.Z-exp.N` 按 SemVer 排序低于正式 `X.Y.Z`;渠道隔离仍以更新器过滤和发布标签规则为准。
失败时按主技能从未完成步骤恢复,不通过换版本号或移动 tag 掩盖失败。
失败时按[恢复与验收](operations.md#恢复与验收)从未完成步骤恢复,不通过换版本号或移动 tag 掩盖失败。
仅维护已有页面文案时不重新构建;报告实际完成状态、发布链接及验证缺口。
@@ -0,0 +1,49 @@
# 执行、恢复与验收
只在实际发布、恢复失败步骤或验收发行时读取对应章节,先核对[入口](../SKILL.md)中的范围与授权
## 发行通道核对
- alpha 是下一正式版本的早期开发阶段,beta 是功能测试阶段,rc 是发布候选,exp 是临时功能实验。
预发行采用 `vX.Y.Z-<阶段>.N`,`N` 从 1 开始;正式版不带预发行后缀,不新增未约定的通道名称。
- 更新候选由本次检查或安装选择的通道决定:Stable 仅正式版,RC 包含正式版,Beta 包含 RC 与正式版,Alpha 包含所有常规阶段。
仅接收同一大版本内严格更高的版本,可跟随后续小版本与补丁版本,默认按运行版本推导通道;临时选择不保存、不允许降级。
exp 仅接收同基线、同一轮实验的后续编号,不能跨实验线或转入其他阶段;不同实验分支不得共用一个 `X.Y.Z-exp.N` 系列。
- 核对目标工作流的构建类型:正式版及 alpha、beta、rc 为 `release`,exp 为 `experimental`。
所有预发行均标记为 Pre-release,不设置 GitHub Latest、不覆盖镜像 `latest`。
发布预发行前记录这两个稳定入口,发布后回读确认未改变。
- 验收更新器时,核对检查更新与执行更新使用相同规则。只有当前构建支持更新且存在允许的更高目标时
才能返回 `hasUpdate=true`;被禁止的目标即使版本领先,也不能出现在更新标记、可用版本或发布说明中。
检查失败须单独报告。核对上一发行实际包含的更新能力,不能因新包支持在线更新就宣称旧包也能在线安装它。
## 执行发版
调用入口前重新核对当前脚本的副作用、目标版本和待推送提交,保留无关工作区内容,不自动清理或暂存用户文件。
先编辑 `release/notes.md`,保留为未提交改动(已暂存或未暂存均可);其他已跟踪文件的改动应先单独提交。
脚本会更新 `release/version.yaml`,将它与最终工作区中的发布说明一起提交为 `chore(release): vX.Y.Z`。
不要提前单独提交说明或把版本文件改成目标版本;脚本要求说明相对 HEAD 有改动,并自行更新版本号。
本任务需要的提交按[项目约定](../../../../CONTRIBUTING.md#项目约定)保留英文 Conventional Commits 与相应署名。
在准备完成且用户已授权发布后,将 `release_version` 设为已确定的版本,执行:
```bash
release/publish "$release_version"
```
该命令会创建包含版本号和发布说明的单次提交与 annotated tag,原子推送分支和 tag,再从长期分支手动触发 `release.yml`。
它没有只读预演模式;不能为了检查或演练而执行。发布工作流读取 tag 中的说明,修改 tag 之后的本地文件不会改变该次构建。
只调整已发布页面的标题或正文时,编辑对应 GitHub Release 的 `name` 或 `body` 并回读核对;
保持已有 tag、源码提交、附件和预发行状态,不重新执行发布入口。
## 恢复与验收
- 脚本中断或网络超时时,先核对本地提交与 tag、远端分支与 tag、对应工作流 run 及 Release,确定已完成到哪一步。
若本地已创建版本提交或 tag,不直接重跑入口,也不通过删 tag、回退版本或强推来消除报错。
- 提交和 tag 已推送但工作流未触发时,核对目标后使用脚本提示的 dispatch 命令补触发;
工作流已存在则跟踪或恢复对应 run,避免重复发布。发现 tag 指向不同提交时报告冲突,不移动已发布 tag。
- 跟踪与本次 tag 对应的 run ID,核对工作流解析的目标提交。触发工作流成功只表示已开始,不能当作发布完成。
- 发布完成后核对 GitHub Release 的版本、正式版/预发行状态和中文变更正文;对照平台清单与工作流确认归档、校验和及镜像产物。
工作流附加的安装说明和自动提交列表按其当前配置处理,不用它们替代手写的中文变更说明。
- 返回版本、tag 对应提交、Release/工作流链接及实际结果。失败、仍在构建或缺失产物时明确说明,不能称为发布成功;
Release 和镜像已生成也不表示用户的运行实例已经升级。
@@ -0,0 +1,36 @@
# 版本与说明准备
只在规划版本、编写发布说明或准备发行时读取对应部分,不因准备文案加载执行流程
## 基线与版本
1. 确认类型、分支、upstream、目标仓库、工作区与完整待发布范围,保留无关改动;未跟踪文件也需核对
2. 读取 [version.yaml](../../../../release/version.yaml)、相关 tag 和实际 Release,确定上一发布基线与本次目标提交,再核对二者间的最终 diff,不只看最近几条提交
3. 使用用户确定的目标版本,建议版本时说明兼容性、能力或修复依据;同名 tag 先区分重试与新发行,不递增版本掩盖冲突
4. 命名与晋级按[版本命名与升级规则](../../../../deploy/README.md#版本命名与升级规则),实验版另读[实验流程](experimental.md#分支与版本)
## 发布说明
读取当前 [notes.md](../../../../release/notes.md)与[项目约定](../../../../CONTRIBUTING.md#项目约定)中的发布说明条目
- 只在发布说明任务中修改 `release/notes.md`,替换为本次版本内容,首行为目标 tag,正文使用中文
- 按用户可感知的最终变化归并,省略空章节,不逐条翻译提交,不写排查过程或未经验证的承诺
- 剔除回退、重复及未进入发行范围的内容,升级事项须能追溯到实际迁移、配置或兼容变化
- 只翻译已有准确说明时保持事实与范围,不混入其他变更
- 常规文档仍只描述当前状态;必要修订执行[文档检查](../../cpr-dev-guide/references/documentation.md),不复制发布说明中的版本变化列表
实际发布由 `release/publish` 提交说明与版本文件,准备说明时不要提前单独提交或把版本文件改成目标版本
用户只要求准备时保留本地改动,纯说明修改无需重建前后端
## 发行准备检查
进入实际发行准备后,读取目标分支的 [release/publish](../../../../release/publish)、[工作流](../../../../.github/workflows/release.yml)与[平台清单](../../../../release/platforms.yaml),按当前入口执行,不另写脚本或绕过检查
按[验证要求](../../../../CONTRIBUTING.md#验证)核对目标提交的证据,区分通过、失败、跳过与未执行
数据库变更同时读取[迁移冻结规则](../../../../backend/migrations/README.md#冻结规则):
- 对照同一大版本的上一正式版和本轮预发行/实验线的已发布版本,验证迁移未改写及升级路径
- 区分未发布迁移、已执行迁移的源码部署实例与 CI 冻结基线,未发版不代表未执行,也不授权放宽冻结规则
- 兼容性与 CI 检查分别报告,跨大版本不默认支持在线升级
正式执行时再读取[通道核对](operations.md#发行通道核对)与[执行发版](operations.md#执行发版),触发工作流不等于发布成功
+14 -7
View File
@@ -5,15 +5,21 @@
| 任务意图与常见说法 | 必用技能 |
| --- | --- |
| 开发、实现、修复、排查报错、审查问题或代码、修改文档 | [cpr-dev-guide](.agents/skills/cpr-dev-guide/SKILL.md) |
| 提交/提个 PR、准备或更新 PR、提交到已有 PR、看下 PR、处理 PR 审查意见 | [cpr-github-pr](.agents/skills/cpr-github-pr/SKILL.md);需要修改时同时执行 cpr-dev-guide |
| 提 Issue、反馈到仓库、把问题或建议整理成 Issue | [cpr-github-issue](.agents/skills/cpr-github-issue/SKILL.md) |
| 提交/提个 PR、准备或更新 PR、看下 PR、回复 PR 讨论、处理 PR 审查意见 | [cpr-github-pr](.agents/skills/cpr-github-pr/SKILL.md);需要修改时同时执行 cpr-dev-guide |
| 提 Issue、反馈到仓库、把问题或建议整理成 Issue、补充或回复 Issue | [cpr-github-issue](.agents/skills/cpr-github-issue/SKILL.md) |
| 写网关插件、修改插件能力、排查插件接入、打包插件 | [cpr-plugin-dev](.agents/skills/cpr-plugin-dev/SKILL.md),同时执行 cpr-dev-guide |
| 发版、准备版本、发布 beta/实验版、整理发布说明、打发行 tag | [cpr-release](.agents/skills/cpr-release/SKILL.md) |
结合上下文识别“提交吧”“看看这个问题”等省略说法:普通 Git 提交不自动变成 PR,代码排查不自动变成 Issue;审查与发版准备不代表获准远程写入。
任务进入新阶段时补载对应技能,不能只读取文件而跳过其中的问题核实、自审、验证与交付步骤。协作流程和审查标准见 [CONTRIBUTING.md](CONTRIBUTING.md)。
结合上下文识别“提交吧”“看看这个问题”等省略说法:普通 Git 提交不自动变成 PR,代码排查不自动变成 Issue;审查与发版准备不代表获准远程写入
只加载当前任务命中的技能、模式和文档章节,复用已有上下文,进入新阶段再补读;链接是按需入口,不是全文或全部参考文件的必读清单
执行所选流程中的问题核实、自审、验证与交付要求,协作流程和审查标准见 [CONTRIBUTING.md](CONTRIBUTING.md)
Issue 和 PR 回复分别按对应技能的[Issue 回复口吻](.agents/skills/cpr-github-issue/SKILL.md#回复口吻)与[PR 回复口吻](.agents/skills/cpr-github-pr/SKILL.md#回复口吻)执行
模块职责以 [系统架构](docs/architecture.md) 为准,界面约定以 [管理端主题](docs/theme.md) 为准;
没有明确约定时保持与相邻实现一致,不把单次页面反馈扩展成全仓规则。
没有明确约定时保持与相邻实现一致,不把单次页面反馈扩展成全仓规则
<!-- CODEGRAPH_START -->
## CodeGraph
@@ -28,11 +34,12 @@ If there is no `.codegraph/` directory, skip CodeGraph entirely — indexing is
## 工作方式
- 开发和审查按 [问题与方案依据](CONTRIBUTING.md#问题与方案依据) 先核实问题,以官方源码为首要参考确定或评估方案。
- 开发和审查按 [问题与方案依据](CONTRIBUTING.md#问题与方案依据) 先核实问题;涉及 Codex 行为、协议或兼容性时,按[参考仓库优先级](.agents/skills/cpr-dev-guide/SKILL.md#参考仓库的优先级)以官方 Codex 仓库对应版本源码为第一手资料,三方仓库仅作参考
- 先定位变更所属模块、同类实现和上下游调用关系,再选择最小合理改动。复用已有能力,也避免为尚不存在的需求增加抽象。
- 提交前自审最终差异,先处理本次引入的职责混淆、重复规则、冗余状态和无依据的兼容分支;后端按 [后端自审](docs/architecture.md#后端自审) 追踪状态与资源生命周期,不能把自审留给 CI 或 PR 审查者。
- 页面变更先复用已有页面设计与组件,不因个人偏好重排页面或另建交互。文案取舍遵循 [界面文案与信息层级](docs/theme.md#界面文案与信息层级),页面 PR 必须提供 [实际截图](CONTRIBUTING.md#界面验证)。
- 代码注释使用中文,解释原因与边界;提交信息使用英文,沿用历史中的 Conventional Commits 格式。
- 常规文档只描述当前状态,不记录变更历史;按[文档职责与更新条件](CONTRIBUTING.md#文档职责与更新条件)在所属章节就地修订,分清层次与详略,短段和列表少用句号,交付前逐段检查必要性、归属、重复与当前状态表达
- 按变更范围执行验证,记录命令、结果和缺口。跳过的测试不算通过;界面与集成行为需要对应运行证据,构建通过不能替代实际验收。
- 审查请求默认只读。修复、提交、推送和合并按用户当前授权执行;“本地验证通过”不等于用户已经审阅批准。
- 不读取或输出无关凭据,不把真实密钥、代理认证、账号令牌或请求转储放入提交、截图及审查报告。
@@ -43,7 +50,7 @@ If there is no `.codegraph/` directory, skip CodeGraph entirely — indexing is
### 审查范围
- 确认目标分支与当前变更范围,结合调用方、被调用方及已有测试审查;结论对应实际审查的提交或工作区状态。
- 按 CONTRIBUTING.md 的维度检查设计、正确性、安全、复杂度、性能、可读性、验证、界面和文档,深度与变更风险相称。
- 按[审查标准](CONTRIBUTING.md#审查标准)检查设计、正确性、安全、复杂度、性能、可读性、验证、界面和文档,深度与变更风险相称
- 重点核对现有架构和数据合同:共享业务事实是否重复解释,异步与并发操作是否破坏状态,权限和敏感数据是否越界,协议适配是否影响兼容性。
- 修改既有特殊分支时先查场景和历史;放宽检查、修改测试或新增例外需要业务依据,不能仅以让检查通过为理由。
+84 -68
View File
@@ -1,47 +1,47 @@
# 贡献与审查
贡献应解决明确的问题,并保持或改善项目的可维护性。提交者、自动检查和审查者使用同一套标准,让重复问题在提交和初审阶段得到处理。
贡献应解决明确的问题,并保持或改善项目的可维护性。提交者、自动检查和审查者使用同一套标准,让重复问题在提交和初审阶段得到处理
本文定义协作流程与审查标准;模块职责以 [系统架构](docs/architecture.md) 为准,视觉规则以 [管理端主题](docs/theme.md) 为准。没有明确约定时遵循相邻实现;具体页面的布局决定不自动成为全项目限制。
本文定义协作流程与审查标准;模块职责以 [系统架构](docs/architecture.md) 为准,视觉规则以 [管理端主题](docs/theme.md) 为准。没有明确约定时遵循相邻实现;具体页面的布局决定不自动成为全项目限制
## 交流与反馈
[讨论区](https://github.com/zyycn/codex-proxy-rs/discussions) 用于使用问答、想法讨论、实验反馈和经验分享。
提问时按需补充版本、部署方式、已尝试的方法及脱敏信息;问题解决后,请在「使用问答」中标记有帮助的答案。
提问时按需补充版本、部署方式、已尝试的方法及脱敏信息;问题解决后,请在「使用问答」中标记有帮助的答案
明确的 Bug、具体功能需求或协作规则建议使用 Issue,无需先开讨论。
讨论形成可执行事项后,可以新建 Issue 并互相附链接;已有 Issue 的后续信息继续补充在原帖,避免重复记录。
围绕问题友善交流,欢迎不同使用场景和方案观点。
围绕问题友善交流,欢迎不同使用场景和方案观点
### 提交 Issue
按场景选择 [接口问题反馈](.github/ISSUE_TEMPLATE/api-bug-report.yml)、[常规问题反馈](.github/ISSUE_TEMPLATE/general-bug-report.yml) 或 [功能建议](.github/ISSUE_TEMPLATE/feature-request.yml) 表单,按对应提示提供信息。故障反馈填写实际运行版本和中转链路;接口问题区分客户端显示与网关实际响应,页面问题附相关截图或录屏。
按场景选择 [接口问题反馈](.github/ISSUE_TEMPLATE/api-bug-report.yml)、[常规问题反馈](.github/ISSUE_TEMPLATE/general-bug-report.yml) 或 [功能建议](.github/ISSUE_TEMPLATE/feature-request.yml) 表单,按对应提示提供信息。故障反馈填写实际运行版本和中转链路;接口问题区分客户端显示与网关实际响应,页面问题附相关截图或录屏
日志和配置只提供定位所需片段,不上传真实凭据或完整请求转储。功能建议说明使用场景、当前不便和期望行为;协作规则调整可直接提交普通 Issue,说明原因和建议方案。
日志和配置只提供定位所需片段,不上传真实凭据或完整请求转储。功能建议说明使用场景、当前不便和期望行为;协作规则调整可直接提交普通 Issue,说明原因和建议方案
## 问题与方案依据
开发、创建 PR 和审查都先核实待解决的问题、触发条件与预期行为,再确定或评估实现。区分问题发生版本、目标分支现状和本次变更后的行为,不把需求描述、PR 描述或第三方项目的做法直接当成事实。目标分支已修复的问题,应说明当前仍需补齐的差异,不能据此否定旧版本中的问题。
开发、创建 PR 和审查都先核实待解决的问题、触发条件与预期行为,再确定或评估实现。区分问题发生版本、目标分支现状和本次变更后的行为,不把需求描述、PR 描述或第三方项目的做法直接当成事实。目标分支已修复的问题,应说明当前仍需补齐的差异,不能据此否定旧版本中的问题
涉及上游协议、客户端行为或兼容性时,以对应版本的官方源码为首要资料。优先读取用户提供或本机已有的官方源码,核对仓库来源、版本或提交;本地资料不足时查官方仓库的对应版本源码,再结合官方测试、文档、发布说明和运行证据。第三方资料只作线索或补充。引用具体文件、符号或行号,对外引用优先使用固定提交的源码链接;源码不可得或版本不匹配时说明证据缺口,不把最新实现当成当前部署行为。无上游依赖的内部变更,以本项目代码、合同和运行证据为依据。
涉及上游协议、客户端行为或兼容性时,以对应版本的官方源码为首要资料。优先读取用户提供或本机已有的官方源码,核对仓库来源、版本或提交;本地资料不足时查官方仓库的对应版本源码,再结合官方测试、文档、发布说明和运行证据。第三方资料只作线索或补充。引用具体文件、符号或行号,对外引用优先使用固定提交的源码链接;源码不可得或版本不匹配时说明证据缺口,不把最新实现当成当前部署行为。无上游依赖的内部变更,以本项目代码、合同和运行证据为依据
官方已有同一场景的处理方式时,优先沿用其协议语义、职责归属和解决路径,检查是否只需复用已有能力或正确传递官方合同。新增覆盖配置、补偿逻辑或采用第三方兼容方案,应说明官方路径为何不足;偏离官方方案需有本项目的实际约束和验证依据,不机械照搬源码,也不默认接受已有提案。
官方已有同一场景的处理方式时,优先沿用其协议语义、职责归属和解决路径,检查是否只需复用已有能力或正确传递官方合同。新增覆盖配置、补偿逻辑或采用第三方兼容方案,应说明官方路径为何不足;偏离官方方案需有本项目的实际约束和验证依据,不机械照搬源码,也不默认接受已有提案
反馈 Issue 时区分观察到的症状、根因假设和方案建议,关联已有 Issue、PR 及包含修复的发布版本。能说明问题的反馈可以先提交,不要求报告者先找到官方源码或证明根因;引用官方行为或提出实现方案时,按上述原则注明依据与缺口。
反馈 Issue 时区分观察到的症状、根因假设和方案建议,关联已有 Issue、PR 及包含修复的发布版本。能说明问题的反馈可以先提交,不要求报告者先找到官方源码或证明根因;引用官方行为或提出实现方案时,按上述原则注明依据与缺口
## PR 流程
1. **准备变更。** 按上述依据核实问题与方案,找到所属模块、相关调用链和最接近的现有实现,在功能分支完成修改。提交前自审完整差异,清理本次引入的重复逻辑、冗余状态与职责越界;后端按 [后端自审](docs/architecture.md#后端自审) 执行。涉及架构、公开合同或较大交互变化时,先说明方案与取舍。
2. **发起 PR。** 目标分支使用 `main`,按 [PR 模板](.github/pull_request_template.md) 说明问题依据、触发场景、最终行为、自审和实际验证结果;涉及官方行为时注明源码位置、版本和方案关系。页面变更必须按 [界面验证](#界面验证) 附实际截图,使用 AI 时按 [AI 使用披露](#ai-使用披露) 写明模型。一个 PR 围绕一个明确目标,必要重构说明关联原因。
3. **自动检查与初审。** CI 执行适用的格式、静态分析、测试和构建检查;启用 AI 审查后,由它按本规范辅助检查语义、复用和一致性。提交者先处理明确问题,维护者重点审阅方案、风险与用户体验。
4. **修改与合并。** 更新代码后重新执行受影响的检查,确认审查意见已处理,验证证据对应当前变更。维护者审阅批准后再合并;需要升级说明的变更同步相关文档。
1. **准备变更**:按上述依据核实问题与方案,找到所属模块、相关调用链和最接近的现有实现,在功能分支完成修改。提交前自审完整差异,清理本次引入的重复逻辑、冗余状态与职责越界;后端按 [后端自审](docs/architecture.md#后端自审) 执行。涉及架构、公开合同或较大交互变化时,先说明方案与取舍
2. **发起 PR**:目标分支使用 `main`,按 [PR 模板](.github/pull_request_template.md) 说明问题依据、触发场景、最终行为、自审和实际验证结果;涉及官方行为时注明源码位置、版本和方案关系。页面变更必须按 [界面验证](#界面验证) 附实际截图,使用 AI 时按 [AI 使用披露](#ai-使用披露) 写明模型。一个 PR 围绕一个明确目标,必要重构说明关联原因
3. **自动检查与初审**:CI 执行适用的格式、静态分析、测试和构建检查;启用 AI 审查后,由它按本规范辅助检查语义、复用和一致性。提交者先处理明确问题,维护者重点审阅方案、风险与用户体验
4. **修改与合并**:更新代码后重新执行受影响的检查,确认审查意见已处理,验证证据对应当前变更。维护者审阅批准后再合并;需要升级说明的变更同步相关文档
AI 开发和审查入口是根目录的 [AGENTS.md](AGENTS.md)。在仓库上下文中要求“审查 PR #编号”时,应按下列标准执行,无需再次列举要求。
AI 开发和审查入口是根目录的 [AGENTS.md](AGENTS.md)。在仓库上下文中要求“审查 PR #编号”时,应按下列标准执行,无需再次列举要求
### 项目技能
仓库技能位于 `.agents/skills/`,随代码共享。技能提供执行流程,协作与审查标准仍以本文和 `AGENTS.md` 为准。
AI 开发、排查、代码审查和文档修改必须执行 `cpr-dev-guide`;准备、创建、更新及审查 PR 必须执行 `cpr-github-pr`,不以用户是否显式点名技能为条件。PR 内修复仍须经过开发流程,不能用填写 PR 正文代替实现自审与验证。
AI 开发、排查、代码审查和文档修改必须执行 `cpr-dev-guide`;准备、创建、更新及审查 PR 必须执行 `cpr-github-pr`,不以用户是否显式点名技能为条件。PR 内修复仍须经过开发流程,不能用填写 PR 正文代替实现自审与验证
| 技能 | 用途 | 调用示例 |
| --- | --- | --- |
@@ -51,26 +51,26 @@ AI 开发、排查、代码审查和文档修改必须执行 `cpr-dev-guide`;
| [cpr-github-issue](.agents/skills/cpr-github-issue/SKILL.md) | 整理或提交 Issue | `$cpr-github-issue 提交当前问题` |
| [cpr-release](.agents/skills/cpr-release/SKILL.md) | 准备发布说明或正式发版 | `$cpr-release 整理本次发布说明` / `$cpr-release 发布 vX.Y.Z` |
审查请求默认在当前会话报告;明确要求创建 PR、提交 Issue 或正式发版时才执行对应远程操作。技能安装不等于启用 GitHub 自动审查。
审查请求默认在当前会话报告;明确要求创建 PR、提交 Issue 或正式发版时才执行对应远程操作。技能安装不等于启用 GitHub 自动审查
### AI 使用披露
PR 正文说明提交者是否使用 AI。使用 AI 参与方案、代码、文档、测试或自审时,写明工具名称和实际模型型号;使用多个模型时分别列出,可简述各自参与范围。
`Codex`、`ChatGPT`、`Claude Code` 等工具或产品名称不能代替模型型号,优先填写工具显示或调用配置中的具体模型标识。
无法获知型号时如实标记「型号未提供,待补充」,不能猜测或伪造,也不能把待补信息称为已完成披露;补齐前保留 Draft。
更新 PR 时同步新增的模型信息;这一披露不替代提交者自审、验证责任或已有提交署名约定。
更新 PR 时同步新增的模型信息;这一披露不替代提交者自审、验证责任或已有提交署名约定
### 规则文件保护
PR 不接受根目录或子目录中 `AGENTS.md` 的新增、修改、删除或重命名。调整这些文件时先提交 Issue 说明原因和建议,由维护者处理;[规则保护工作流](.github/workflows/agents-md-guard.yml) 会提示并关闭包含此类变更的 PR。撤销相关变更后可以重新打开 PR,继续正常检查。
PR 不接受根目录或子目录中 `AGENTS.md` 的新增、修改、删除或重命名。调整这些文件时先提交 Issue 说明原因和建议,由维护者处理;[规则保护工作流](.github/workflows/agents-md-guard.yml) 会提示并关闭包含此类变更的 PR。撤销相关变更后可以重新打开 PR,继续正常检查
源码变更遵守现有架构边界与迁移冻结规则,由对应 CI 检查执行;不额外设立整块业务目录的 PR 禁改清单。构建、测试和安全扫描复用现有工作流。
源码变更遵守现有架构边界与迁移冻结规则,由对应 CI 检查执行;不额外设立整块业务目录的 PR 禁改清单。构建、测试和安全扫描复用现有工作流
## 审查标准
按改动涉及的范围选择检查深度,无需为每个 PR 填写所有维度的报告。
按改动涉及的范围选择检查深度,无需为每个 PR 填写所有维度的报告
结论先回答原问题是否成立(成立、部分成立、不成立或证据不足),说明触发条件和依据;再判断解决方向是否合理,交代适用的官方方案、本次实现与它的关系及必要调整,最后报告实现问题与验证缺口。原问题是否成立与 PR 实现是否正确分别判断。源码推断、实际复现和检查结果分别说明,CI 通过不能代替问题与方案的论证。
结论先回答原问题是否成立(成立、部分成立、不成立或证据不足),说明触发条件和依据;再判断解决方向是否合理,交代适用的官方方案、本次实现与它的关系及必要调整,最后报告实现问题与验证缺口。原问题是否成立与 PR 实现是否正确分别判断。源码推断、实际复现和检查结果分别说明,CI 通过不能代替问题与方案的论证
| 维度 | 检查重点 |
| --- | --- |
@@ -86,59 +86,81 @@ PR 不接受根目录或子目录中 `AGENTS.md` 的新增、修改、删除或
审查意见区分三类:
- **必须修复:** 有具体触发条件的缺陷、违反现有合同或明确项目规范的问题。说明位置、依据、影响和最小修复方向。
- **建议:** 不影响合同或规范的替代实现、命名偏好和局部优化。个人偏好不能单独阻止合并,也不应强求与本次目标无关的重构。
- **验证缺口:** 尚未检查的行为、状态或运行环境。说明缺少什么证据,由维护者评估是否需要补齐,不能把未验证当作已通过或已发现缺陷。
- **必须修复:** 有具体触发条件的缺陷、违反现有合同或明确项目规范的问题。说明位置、依据、影响和最小修复方向
- **建议:** 不影响合同或规范的替代实现、命名偏好和局部优化。个人偏好不能单独阻止合并,也不应强求与本次目标无关的重构
- **验证缺口:** 尚未检查的行为、状态或运行环境。说明缺少什么证据,由维护者评估是否需要补齐,不能把未验证当作已通过或已发现缺陷
复杂度不按行数判断,复用不只看语法相似。建议合并重复逻辑时应指出共享的业务含义;建议保留特殊分支时应能说明它服务的场景。例外应有依据,不能仅为绕过检查而放宽规则。
复杂度不按行数判断,复用不只看语法相似。建议合并重复逻辑时应指出共享的业务含义;建议保留特殊分支时应能说明它服务的场景。例外应有依据,不能仅为绕过检查而放宽规则
## 项目约定
- AI 进行 Rust 开发时,加载并使用 `$rust-best-practices` 技能。模块、可见性、依赖方向和测试目录按架构文档执行,使用现有 Rustfmt 与 Clippy 配置。
- Vue 使用 TypeScript、`<script setup>` 和现有 ESLint 配置;API 请求通过已有模块及 `request` 封装,沿用逐行请求配置格式。
- 业务数据的解析、校验和展示优先复用已有逻辑。新增抽象放在拥有该职责的模块,避免重复事实源或无实际需求的通用层。
- 新增代码注释使用中文。提交信息使用英文 Conventional Commits,参考近期历史,按完整功能归并;确由 Codex 参与的提交保留 `Co-authored-by: Codex <noreply@openai.com>`。
- 发布说明写入 `release/notes.md`,标题首行为目标版本的 `# vX.Y.Z`,章节标题和正文使用中文;命令、配置键、协议名等保留原文。只说明本次版本的实际变化,按影响交代升级事项。
- PR 聚焦目标;无关格式化、重构和依赖升级单独处理。
- AI 进行 Rust 开发时,加载并使用 `$rust-best-practices` 技能。模块、可见性、依赖方向和测试目录按架构文档执行,使用现有 Rustfmt 与 Clippy 配置
- Vue 使用 TypeScript、`<script setup>` 和现有 ESLint 配置;API 请求通过已有模块及 `request` 封装,沿用逐行请求配置格式
- 业务数据的解析、校验和展示优先复用已有逻辑。新增抽象放在拥有该职责的模块,避免重复事实源或无实际需求的通用层
- 新增代码注释使用中文。提交信息使用英文 Conventional Commits,参考近期历史,按完整功能归并;确由 Codex 参与的提交保留 `Co-authored-by: Codex <noreply@openai.com>`
- 发布说明写入 `release/notes.md`,标题首行为目标版本的 `# vX.Y.Z`,章节标题和正文使用中文;命令、配置键、协议名等保留原文。只说明本次版本的实际变化,按影响交代升级事项
- PR 聚焦目标;无关格式化、重构和依赖升级单独处理
## 文档职责与更新条件
常规文档说明当前有效的用法、接口合同、模块职责和维护约束,不承担变更历史。
只有改动使对应说明失真、缺少必要信息,或改变读者需要执行的操作与判断时,才更新文档。
内部修复、重构和界面微调不默认附带文档改动;审查时不能仅因 PR 没改文档就判定存在缺口。
**文档只描述当前有效状态,不记录变更历史**,在所属章节就地修订,不追加“本次新增”“此前行为”或修复经过
- 按 [开发指南的文档入口](.agents/skills/cpr-dev-guide/SKILL.md#按任务读取) 找到内容所属文档,在原有章节就地修订,
不按每次变更追加说明。同一事实只保留一处完整说明,其他文档按读者任务提供必要摘要和链接。
- API 文档保留调用所需的字段、状态、语义和限制;架构文档保留职责与不变量;部署文档保留配置、操作和排障。
不把界面布局、内部锁或索引实现写进接口说明,也不把前端库的实现选择写进部署说明。
- 改动原因、前后对比、排查经过和本次验证结果放在 PR / Issue 或任务报告。版本变更与升级影响按维护者的发版安排维护
`release/notes.md`,不为普通开发任务或 PR 自动追加发布说明。
- 仍影响实际升级、恢复或兼容使用的限制与操作应保留,并注明适用条件;不能仅因涉及旧版就删除。
审查文档改动时核对必要性、所属职责与重复内容,区分当前使用说明和仅解释此次修复的内容。
只有用法、合同、职责、约束或读者需要执行的操作发生变化时才更新,内部重构和界面微调不默认附带文档改动
### 内容归属
| 文档 | 回答的问题 | 详略边界 |
| --- | --- | --- |
| [README](README.md) | 项目能做什么,如何开始 | 简短介绍、最短可用路径和文档导航 |
| [部署与运维](deploy/README.md) | 如何安装、接入、排障和恢复 | 前置条件、配置、操作步骤、结果判断与风险 |
| [开发与源码联调](docs/development.md) | 如何准备环境、运行源码和联调独立仓库 | 可执行命令、目录关系和依赖维护 |
| [API 参考](docs/api.md) | 调用者如何正确请求和解释结果 | 鉴权、字段、默认值、错误、限制与幂等语义,不展开内部实现或界面布局 |
| [系统架构](docs/architecture.md) | 业务事实归谁,调用如何协作 | 模块边界、状态所有权、生命周期和不变量,不重复字段清单 |
| [管理端主题](docs/theme.md) | 界面如何保持一致,主题如何扩展 | 文案与视觉约束、Token 合同、实现入口和验收范围 |
| [插件使用](docs/plugins.md) | 如何安装、授权、配置和使用插件 | 管理员操作与影响,不展开 SDK 调用或宿主事务实现 |
| [插件 SDK](backend/crates/gateway-plugin/sdk/README.md) / [CLI](backend/apps/plugin-cli/README.md) | 如何编写和打包插件 | 作者合同、权限边界、示例与打包验证 |
| [数据库迁移](backend/migrations/README.md) | 如何维护 schema 和准备测试库 | 冻结规则、升级约束和测试环境,不罗列迁移历史 |
| 本文 | 如何协作、审查和交付 | 流程、判断标准与验证要求,任务入口由 [开发技能](.agents/skills/cpr-dev-guide/SKILL.md) 串联 |
- 同一事实只在所属文档完整说明,其他位置按读者任务保留必要摘要和链接
- 改动原因、前后对比、排查经过和验证结果放在 PR、Issue 或任务报告;`release/notes.md` 是发版时使用的发布说明,不作为常规文档的历史章节,也不随普通开发任务自动追加
- 仍影响升级、恢复或兼容使用的内容,写成明确的适用条件与操作,保留必要限制,不叙述演变过程
### 层次与行文
- 按读者任务组织章节,先给入口和操作,再解释限制;长篇参考文档提供导航,独立主题使用小标题
- 快速开始只保留走通流程的必要步骤;API、配置、恢复和权限等容易误用的内容写清输入、默认值、边界与失败处理
- 短说明直接成段,并列信息用列表,可比较的字段或职责用表格,避免一段堆叠多个主题
- 标题、表格单元格、短列表项和独立短段通常不加句号;长段优先拆分,确需区分完整句意时保留句号,不机械替换成逗号或分号
- 命令、URL、版本号、字段名和原始输出保持准确,不为统一标点改写代码示例
审查时逐段核对:是否属于当前状态、是否回答读者问题、是否放在正确文档、是否重复、脱离变更背景能否独立理解
没有文档改动本身不构成验证缺口,判断依据是当前说明是否仍然准确、完整
## 界面验证
AI 进行前端开发时,加载并使用 `$frontend-design` 技能。设计方案以本项目的用户任务、[管理端主题](docs/theme.md) 和已有页面为依据,优先延续原页面的布局、信息层级与交互;设计技能不构成重做页面的理由。
AI 进行前端开发时,加载并使用 `$frontend-design` 技能。设计方案以本项目的用户任务、[管理端主题](docs/theme.md) 和已有页面为依据,优先延续原页面的布局、信息层级与交互;设计技能不构成重做页面的理由
开发前查找原页面、同类页面及 `modules/ui/src/components` 中的基础组件,明确参考实现和可复用部分,再确定最小改动。只有原设计不能满足本次操作或存在具体问题时才调整结构,并在 PR 中说明原因;不顺带换风格、重排无关区域或增加重复的模式入口。需要扩展时在对应职责层修改;局部样式的适用范围遵循主题文档,不一律禁止,也不靠页面覆盖掩盖共用组件的问题。
开发前查找原页面、同类页面及 `modules/ui/src/components` 中的基础组件,明确参考实现和可复用部分,再确定最小改动。只有原设计不能满足本次操作或存在具体问题时才调整结构,并在 PR 中说明原因;不顺带换风格、重排无关区域或增加重复的模式入口。需要扩展时在对应职责层修改;局部样式的适用范围遵循主题文档,不一律禁止,也不靠页面覆盖掩盖共用组件的问题
列表列宽、序号、按钮、分页和弹窗尺寸由实际任务与数据规模决定,以同类页面为参照。新增或调整交互时说明用户需要完成什么操作,不把某个页面的取舍推广成全局禁令。
列表列宽、序号、按钮、分页和弹窗尺寸由实际任务与数据规模决定,以同类页面为参照。新增或调整交互时说明用户需要完成什么操作,不把某个页面的取舍推广成全局禁令
提交者在浏览器中验证受影响路径,页面变更的 PR 必须提供实际运行截图,包括布局、文案、样式和交互状态的变化:
- 修改现有页面提供变更前后截图,尽量保持相同数据、主题和窗口尺寸;新增页面提供完成态截图。截图保留必要上下文,让审查者能判断布局和信息密度,不只截取孤立控件。
- 按影响范围补充浅色/深色、窄窗口及空/加载/错误等受影响状态;共用主题或基础组件变更按主题文档扩大验证范围。弹出层中的内容需展示展开状态,不能只截关闭后的页面。
- 在 PR 正文嵌入或链接审查者可访问的图片,注明页面入口、状态和对应提交。录屏、预览地址和复现步骤用于补充,不能替代截图;本机绝对路径不算可供远端审阅的附件。
- 视觉或交互继续修改后更新受影响截图,使证据对应待审实现。截图与模拟数据不得包含真实凭据,不用设计稿、生成图片或旧版截图冒充运行结果。
- 环境不足时说明缺少的截图和交互验证,不宣称验收通过;页面 PR 在截图补齐前保留 Draft,不标为可审阅或建议合并。
- 修改现有页面提供变更前后截图,尽量保持相同数据、主题和窗口尺寸;新增页面提供完成态截图。截图保留必要上下文,让审查者能判断布局和信息密度,不只截取孤立控件
- 按影响范围补充浅色/深色、窄窗口及空/加载/错误等受影响状态;共用主题或基础组件变更按主题文档扩大验证范围。弹出层中的内容需展示展开状态,不能只截关闭后的页面
- 在 PR 正文嵌入或链接审查者可访问的图片,注明页面入口、状态和对应提交。录屏、预览地址和复现步骤用于补充,不能替代截图;本机绝对路径不算可供远端审阅的附件
- 视觉或交互继续修改后更新受影响截图,使证据对应待审实现。截图与模拟数据不得包含真实凭据,不用设计稿、生成图片或旧版截图冒充运行结果
- 环境不足时说明缺少的截图和交互验证,不宣称验收通过;页面 PR 在截图补齐前保留 Draft,不标为可审阅或建议合并
截图用于视觉审阅,保存、切换、错误恢复和键盘操作等行为仍需实际验证并记录覆盖范围,不能仅凭静态图片推断交互正确。
截图用于视觉审阅,保存、切换、错误恢复和键盘操作等行为仍需实际验证并记录覆盖范围,不能仅凭静态图片推断交互正确
涉及浏览器能力或访问环境差异时,按项目支持的部署方式核对相关 HTTP/HTTPS、localhost/实际部署地址与浏览器版本,记录实际覆盖的条件;本地预览正常不能替代部署环境中的交互验收。
涉及浏览器能力或访问环境差异时,按项目支持的部署方式核对相关 HTTP/HTTPS、localhost/实际部署地址与浏览器版本,记录实际覆盖的条件;本地预览正常不能替代部署环境中的交互验收
## 验证
选择与变更相关的检查,纯文档修改无需重建前后端。行为修复补充能覆盖触发条件和结果的测试,不增加仅复述实现的断言。
选择与变更相关的检查,纯文档修改无需重建前后端。行为修复补充能覆盖触发条件和结果的测试,不增加仅复述实现的断言
| 范围 | 现有检查 | 仍需提供的证据 |
| --- | --- | --- |
@@ -148,15 +170,9 @@ AI 进行前端开发时,加载并使用 `$frontend-design` 技能。设计方
| 依赖安全 | Cargo Audit、pnpm Audit | 本次变更的权限、数据与信任边界 |
| 工作流与容器 | Actionlint、相关容器检查 | 改动涉及的运行行为 |
UI 与示例源码分别在独立的 `codex-proxy-ui`、`codex-proxy-plugins` 仓库维护。前端的依赖、锁文件和 ESLint 配置均在 `frontend/` 管理,仓库根目录不维护 Node 包。UI 使用固定 GitHub 标签或提交依赖,锁文件记录实际提交、源码归档地址及完整性摘要,常规开发和 CI 不需要检出同级仓库。安装时 pnpm 按 UI 自身锁文件准备构建依赖,再执行 `prepack` 生成 JS、CSS 和类型声明。普通开发与生产构建都使用 UI 编译产物,源码联调通过 `@codex-proxy/ui/vite` 显式开启。
环境准备、依赖安装和服务启动见 [开发与源码联调](docs/development.md),联合修改组件库或插件时使用其中的子模块入口
pnpm 版本由 `frontend/package.json` 的 `packageManager` 固定,CI 和 Docker 从该字段读取。`frontend/pnpm-workspace.yaml` 的 `allowBuilds` 按仓库放行自有 UI 包的构建脚本;升级依赖时核对源码并更新锁文件,无需逐次修改构建许可,不开放全部依赖脚本。
```bash
pnpm --dir frontend install --frozen-lockfile
```
需要联合修改组件库或插件时,使用 [源码联调](docs/development.md) 中的子模块与开发入口;联调只临时替换解析路径,不改写正式依赖或锁文件。独立插件的 SDK 固定到包含所需合同的 Git 提交,不依赖浮动分支。
正式依赖固定到标签或提交,联调只临时替换解析路径,不改写正式依赖或锁文件
前端从仓库根目录执行:
@@ -166,16 +182,16 @@ pnpm --dir frontend build
git diff --check
```
`build` 已包含类型检查;不维护独立前端测试代码。后端的工具链、命令与线程栈设置统一见 [修改与验收](docs/architecture.md#12-修改与验收)。
`build` 已包含类型检查;不维护独立前端测试代码。后端的工具链、命令与线程栈设置统一见 [修改与验收](docs/architecture.md#12-修改与验收)
依赖 PostgreSQL / Redis 的测试使用 [专用测试环境](backend/migrations/README.md#本地测试库),未配置导致跳过时明确记录。CI 中已有对应服务和检查,见 [质量工作流](.github/workflows/_quality.yml) 与 [安全扫描](.github/workflows/security-scan.yml)。
依赖 PostgreSQL / Redis 的测试使用 [专用测试环境](backend/migrations/README.md#本地测试库),未配置导致跳过时明确记录。CI 中已有对应服务和检查,见 [质量工作流](.github/workflows/_quality.yml) 与 [安全扫描](.github/workflows/security-scan.yml)
集成变更应验证实际业务路径及结果,不能只凭健康检查或探测成功认定全链路正常。协议和网络改动按需核对认证、证书、代理、流式交付及客户端结果;记录影响复现的环境条件。缺少运行条件时说明缺口,不用模拟结果代替真实集成结论。
集成变更应验证实际业务路径及结果,不能只凭健康检查或探测成功认定全链路正常。协议和网络改动按需核对认证、证书、代理、流式交付及客户端结果;记录影响复现的环境条件。缺少运行条件时说明缺口,不用模拟结果代替真实集成结论
## 自动检查与合并条件
[CI](.github/workflows/ci.yml) 已按变更范围调用现有质量检查。新增自动规则优先使用现有工具,要求依据明确、结果稳定、误报可控;无法可靠判定的设计与视觉问题保留审查判断,避免把具体源码写法锁成测试。
[CI](.github/workflows/ci.yml) 已按变更范围调用现有质量检查。新增自动规则优先使用现有工具,要求依据明确、结果稳定、误报可控;无法可靠判定的设计与视觉问题保留审查判断,避免把具体源码写法锁成测试
AI 自动审查需要在所用工具中单独启用。其支持的检查范围和优先级过滤以工具说明为准;即使能读取 `AGENTS.md`,也不能保证所有可维护性和视觉问题都会被报告。配置参考 [Codex GitHub 审查文档](https://learn.chatgpt.com/docs/third-party/github)。
AI 自动审查需要在所用工具中单独启用。其支持的检查范围和优先级过滤以工具说明为准;即使能读取 `AGENTS.md`,也不能保证所有可维护性和视觉问题都会被报告。配置参考 [Codex GitHub 审查文档](https://learn.chatgpt.com/docs/third-party/github)
要由 GitHub 强制执行合并条件,维护者需配置对应分支的必需状态检查、审阅批准和讨论解决要求,并按需要设置新提交后的重新审阅。配置前核对实际检查名称与触发范围,见 [GitHub 分支保护](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches)。规则文档本身不会启用 AI 服务或更改分支设置。
要由 GitHub 强制执行合并条件,维护者需配置对应分支的必需状态检查、审阅批准和讨论解决要求,并按需要设置新提交后的重新审阅。配置前核对实际检查名称与触发范围,见 [GitHub 分支保护](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches)。规则文档本身不会启用 AI 服务或更改分支设置
+19 -12
View File
@@ -30,14 +30,17 @@
| 账号 | `admin@cpr.local` |
| 密码 | `039c18de2aeac46d23ead7766bb07bbbe3747233c66a128e` |
预览服务运行已发布版本,展示的账号、代理与使用记录均为模拟数据,每天北京时间 `00:00` 自动生成当天数据。
预览服务运行已发布版本,展示的账号、代理与使用记录均为模拟数据,每天北京时间 `00:00` 自动生成当天数据
这是公开共享的功能预览环境,不提供真实模型调用;请勿导入真实账号、密钥或其他敏感信息
## 快速开始
使用 Docker Compose 部署版本固定的发布镜像,同时启动 PostgreSQL 和 Redis。
以下命令适用于 Linux amd64/arm64,需要 Docker Engine、Docker Compose Plugin、curl 和 OpenSSL。已有部署请先看
[升级说明](deploy/README.md#镜像升级与源码构建),不要覆盖原配置
使用 Docker Compose 部署版本固定的发布镜像,同时启动 PostgreSQL 和 Redis
以下命令适用于 Linux amd64/arm64,需要 Docker Engine、Docker Compose Plugin、curl 和 OpenSSL
已有部署请先看 [升级说明](deploy/README.md#镜像升级与源码构建),不要覆盖原配置
### 一键安装
@@ -48,7 +51,9 @@ curl -fsSL https://raw.githubusercontent.com/zyycn/codex-proxy-rs/main/deploy/in
```
[安装脚本](deploy/install.sh) 默认安装到当前目录下的 `codex-proxy-rs/`,下载同一正式 Release 的部署文件,
自动生成密码、设置目录权限并启动服务。完成后会显示访问地址和管理员密码,请保存密码,再按下方步骤
自动生成密码、设置目录权限并启动服务
完成后会显示访问地址和管理员密码,请保存密码,再按下方步骤
[添加账号与客户端密钥](#添加账号与客户端密钥)
可在执行 `bash install.sh` 时传入环境变量:
@@ -65,7 +70,7 @@ curl -fsSL https://raw.githubusercontent.com/zyycn/codex-proxy-rs/main/deploy/in
INSTALL_DIR="$HOME/services/codex-proxy-rs" bash install.sh
```
重复运行时请使用同一安装目录。检测到 `deploy/config.yaml` 后,脚本保留现有配置和部署文件,
重复运行时请使用同一安装目录,检测到 `deploy/config.yaml` 后,脚本保留现有配置和部署文件,
忽略传入的管理员密码,也不执行版本升级
### 手动安装
@@ -74,9 +79,9 @@ INSTALL_DIR="$HOME/services/codex-proxy-rs" bash install.sh
### 登录管理端
部署完成后,打开 `http://127.0.0.1:8080`,使用 `admin@cpr.local` 和管理员密码登录。
API Key 持有者可在同一登录页切换登录身份,进入 `/key-usage` 查看自己的用量、趋势、请求日志、额度与健康时间线;不能访问管理员页面。
页面右上角的「密钥配置」支持复制 Codex 配置文件和导入 CCSwitch,导入时同时启用当前 Key 的日/周额度查询,默认刷新间隔为 30 分钟
部署完成后,打开 `http://127.0.0.1:8080`,使用 `admin@cpr.local` 和管理员密码登录
API Key 持有者可在登录页切换身份,查看自己的用量与额度,并通过「密钥配置」复制 Codex 配置或导入 CCSwitch
默认地址只能在服务器本机访问,从其他设备使用时,需要配置
[HTTPS 反向代理](deploy/README.md#公网访问)
@@ -89,8 +94,9 @@ API Key 持有者可在同一登录页切换登录身份,进入 `/key-usage`
## 客户端接入
**Codex CLI / 桌面端**:在「使用密钥」中按操作系统复制配置,或通过 CCSwitch 导入。
合并到客户端配置后重启 Codex。完整步骤、生图配置与排障见[客户端配置](deploy/README.md#客户端配置)
**Codex CLI / 桌面端**:在「使用密钥」中按操作系统复制配置,或通过 CCSwitch 导入,合并配置后重启 Codex
完整步骤、生图配置与排障见[客户端配置](deploy/README.md#客户端配置)
**其他 Responses API 客户端**:填写以下信息:
@@ -114,7 +120,8 @@ curl http://127.0.0.1:8080/v1/models \
| 接口集成 | [API 参考](docs/api.md) · [模型定价](docs/api.md#模型定价) |
| 使用插件 | [安装、配置与使用](docs/plugins.md) |
| 开发插件 | [SDK 与合同](backend/crates/gateway-plugin/sdk/README.md) · [打包工具](backend/apps/plugin-cli/README.md) |
| 开发宿主 | [贡献与验证](CONTRIBUTING.md) · [源码联调](docs/development.md) · [系统架构](docs/architecture.md) · [管理端主题](docs/theme.md) · [数据库迁移](backend/migrations/README.md) |
| 开发宿主 | [开发与源码联调](docs/development.md) · [系统架构](docs/architecture.md) · [管理端主题](docs/theme.md) · [数据库迁移](backend/migrations/README.md) |
| 参与协作 | [贡献、审查与验证](CONTRIBUTING.md) |
## 社区
+8 -7
View File
@@ -1,7 +1,7 @@
# Codex Proxy Plugin CLI
`cpr-plugin` 校验并规范化作者清单、收集声明的资源,生成可安装归档及 SHA-256 校验文件。
当前输出使用 `manifestVersion: 1`、`protocolVersion: 1` 和 SDK `0.1` 合同。
输出合同与 [SDK 版本](../../crates/gateway-plugin/sdk/README.md)一致
## 安装
@@ -11,7 +11,7 @@
cargo install --locked --path backend/apps/plugin-cli --root /path/to/local-tools
```
将 `/path/to/local-tools/bin` 加入 `PATH`,或使用可执行文件的完整路径。
将 `/path/to/local-tools/bin` 加入 `PATH`,或使用可执行文件的完整路径
## 打包
@@ -26,24 +26,25 @@ cpr-plugin package \
--output-dir /path/to/output
```
资源映射相对于作者清单所在目录解析。输出包括完整插件 ID、版本和目标 triple 命名的 `.tar.gz` 及 `.sha256`;归档根目录直接包含 `plugin.json`、可执行文件和已声明资源,不包含开发依赖。
资源映射相对于作者清单所在目录解析。输出包括完整插件 ID、版本和目标 triple 命名的 `.tar.gz` 及 `.sha256`;归档根目录直接包含 `plugin.json`、可执行文件和已声明资源,不包含开发依赖
作者清单可省略普通贡献项的 `id`、`version` 和固定阶段;`middleware` 的 `request`/`attempt` 仍须显式选择。
CLI 与 SDK 的 `Manifest::from_author_slice`、`PluginBuilder::from_json` 共用规范化入口;归档中的
`plugin.json` 写入补全后的贡献项字段,不保留另一份作者声明。
完整规则见 [SDK 清单](../../crates/gateway-plugin/sdk/docs/manifest.md)。
完整规则见 [SDK 清单](../../crates/gateway-plugin/sdk/docs/manifest.md)
支持 `x86_64-unknown-linux-gnu`、`aarch64-unknown-linux-gnu` 和 `aarch64-apple-darwin`。完整开发示例在同级 `codex-proxy-plugins/examples/workbench` 仓库目录;本工具不负责源码构建、上传或启用插件。
支持 `x86_64-unknown-linux-gnu`、`aarch64-unknown-linux-gnu` 和 `aarch64-apple-darwin`。
完整开发示例在独立仓库 `codex-proxy-plugins` 的 `examples/workbench`,可通过[子模块](../../../docs/development.md#源码联调)检出;本工具不负责源码构建、上传或启用插件
### 图标资源
在作者清单的 `icon` 中指定图标,并在 `resources` 中登记对应路径与 MIME 类型,配置方式见
[SDK 插件图标](../../crates/gateway-plugin/sdk/docs/manifest.md#插件图标)。图标与其他声明资源一样参与打包和摘要计算,
无需额外命令参数;工具保留文件原始内容,宿主安装时验证图标内容与资源限制。
无需额外命令参数;工具保留文件原始内容,宿主安装时验证图标内容与资源限制
## 开发
Rust 包名为 `codex-proxy-plugin-cli`,位于宿主的 `backend` workspace,共享工具链、锁文件和检查规则;内部仅依赖 `gateway-plugin-sdk`。
Rust 包名为 `codex-proxy-plugin-cli`,位于宿主的 `backend` workspace,共享工具链、锁文件和检查规则;内部仅依赖 `gateway-plugin-sdk`
```bash
cargo run --manifest-path backend/Cargo.toml -p codex-proxy-plugin-cli -- package --help
+12 -12
View File
@@ -2,20 +2,20 @@
用于编写 Codex Proxy 插件的 Rust 合同与可选会话辅助。插件只依赖本包,不依赖宿主 Core、Admin、Store 或 Host。
SDK 仍处于实验阶段;能力是否可用取决于宿主支持、清单声明、实际注册和安装时接受的访问域,
而不只是枚举中存在该名称。
而不只是枚举中存在该名称
| 版本 | 当前值 | 用途 |
| --- | --- | --- |
| SDK 包 | `0.1.0` | Rust 开发依赖,独立于网关版本 |
| `manifestVersion` | `1` | 安装清单格式 |
| `package.protocolVersion` | `1` | 宿主与插件的进程通信 |
| `contributes.*.version` | `1` | 单项能力合同 |
| `contributes.*.version` | 默认 `1`,`middleware` 可选 `2` | 单项能力合同,见[扩展项声明](docs/manifest.md#扩展项简写) |
开发顺序:编写[清单](docs/manifest.md) → 实现[能力处理器](docs/capabilities.md) → 用 [CLI](../../../apps/plugin-cli/README.md)
打包 → [安装、配置与启用](../../../../docs/plugins.md)。完整可运行示例位于独立仓库
`codex-proxy-plugins` 的 `examples/workbench`。
`codex-proxy-plugins` 的 `examples/workbench`
使用 AI 协作开发时,可调用仓库技能 [`$cpr-plugin-dev`](../../../../.agents/skills/cpr-plugin-dev/SKILL.md),按任务定位合同、示例和验证入口。
使用 AI 协作开发时,可调用仓库技能 [`$cpr-plugin-dev`](../../../../.agents/skills/cpr-plugin-dev/SKILL.md),按任务定位合同、示例和验证入口
## 模块与依赖
@@ -34,7 +34,7 @@ SDK 仍处于实验阶段;能力是否可用取决于宿主支持、清单声
| `client` | 通过 `io` feature 开启的 `PluginBuilder`、会话、中间件与异步帧收发 |
`call/` 按业务能力组织数据,`message.rs` 定义消息与帧。请求/响应改写、协议转换和思考参数映射统一使用
`call::middleware`;WebSocket 观察接口不能改写连接。
`call::middleware`;WebSocket 观察接口不能改写连接
插件使用的导入路径例如:
@@ -53,13 +53,13 @@ use gateway_plugin_sdk::client::{read_frame, write_frame};
不能用另一调用的 ID 扩大权限。插件程序自行选择运行时和启动入口,不会启动网关。
`SessionConfig.maximum_stream_chunk_bytes` 默认 1 MiB,只约束单个业务流分块;
流式响应还需满足宿主实际授予的信用窗口。普通调用与回调的正文不受此预算限制。
业务处理器仍接收完整正文,内存占用随正文增长。
业务处理器仍接收完整正文,内存占用随正文增长
## 通信边界
启用 `io` 时优先用 [`PluginBuilder`](docs/capabilities.md#类型化作者入口) 组合业务处理器,再交给
`PluginSession::run`。仅实现中间件时也可直接使用
[`MiddlewarePlugin`](docs/capabilities.md#洋葱中间件)。
[`MiddlewarePlugin`](docs/capabilities.md#洋葱中间件)
```mermaid
sequenceDiagram
@@ -81,16 +81,16 @@ sequenceDiagram
协议 v1 的帧头由大端 `u32` 元数据长度和大端 `u64` 正文长度组成,随后依次传输 JSON 元数据和原始二进制正文。
元数据保留 64 KiB 上限;正文总量不由插件传输层额外限制。传输内部以 256 KiB 为单次 I/O 块,
不逐块发起 RPC,也不要求插件配置与宿主同步。读取按进度扩展缓冲区;截断正文必须报错,不能作为完整消息交付。
不逐块发起 RPC,也不要求插件配置与宿主同步。读取按进度扩展缓冲区;截断正文必须报错,不能作为完整消息交付
`read_frame` / `write_frame` 使用当前协议,不接受历史开发协议。帧写入队列之前先用 `validate_frame` 校验元数据,
`read_frame` / `write_frame` 使用协议 v1。帧写入队列之前先用 `validate_frame` 校验元数据,
避免本地输入错误关闭共享传输。协议校验不会自动重试业务调用;部分读写中断后不能把当前位置当作新帧边界。
底层 `read_frame` / `write_frame` 不是取消安全的;自行编排会话时,不能在 `select!` 的其他分支完成后
重建半途中的帧读写。`PluginSession` 会在处理调用完成通知时保留同一个读取 future,直到完整帧到达或会话关闭。
重建半途中的帧读写。`PluginSession` 会在处理调用完成通知时保留同一个读取 future,直到完整帧到达或会话关闭
会话由宿主发送 `Hello`,插件校验协议后返回携带相同 incarnation 的 `Ready`。
注册结果必须与清单中的能力一致。一次 `Call` 的结果、错误和流帧按 ID 关联;回调必须
携带当前父调用 ID。实例、代次、阶段、账号或资源 ID 是关联数据,不能自行充当授权。
携带当前父调用 ID。实例、代次、阶段、账号或资源 ID 是关联数据,不能自行充当授权
## 本地验证
@@ -104,4 +104,4 @@ RUST_MIN_STACK=16777216 RUSTDOCFLAGS='-D warnings' cargo +1.97.0 doc --manifest-
源码中的类型和字段是当前合同;宿主接入、目录边界与 Runtime 集成验证见
[系统架构](../../../../docs/architecture.md)。Provider 固定为宿主内置的 OpenAI 与 xAI;插件实现只依赖公开 SDK 合同。
单元测试与文档构建不代替目标平台上的安装、授权及实际能力验证。
单元测试与文档构建不代替目标平台上的安装、授权及实际能力验证
@@ -3,7 +3,14 @@
[返回 SDK](../README.md) · [清单与图标](manifest.md)
`contributes` 声明插件提供什么功能,`permissions` 声明插件需要访问哪些宿主资源。两者不是同一件事:
例如,管理页面声明 `management` 能力,需要调用模型时再声明 `models` 访问域。
例如,管理页面声明 `management` 能力,需要调用模型时再声明 `models` 访问域
| 开发任务 | 入口 |
| --- | --- |
| 组合处理器并声明权限 | [作者入口](#类型化作者入口) · [能力与方法](#能力与方法) · [访问域](#访问域) |
| 使用宿主资源 | [基础事实](#基础事实) · [额度刷新](#额度观测刷新) · [账号](#账号与凭据) · [自有资源](#自有资源与维护) · [Key 预算](#client-key-预算) · [模型](#key模型与模型调用) · [网络](#网络) |
| 扩展请求链 | [中间件](#洋葱中间件) · [模型目录](#模型目录) · [重试](#重试决策) · [路由、调度与观察](#路由调度与观察) · [入口认证](#数据面入口认证) |
| 管理与交互 | [状态、日志与迁移](#状态日志与迁移) · [管理页面](#管理页面公开入口与-cli) · [页面宿主桥](#页面宿主桥-v2) · [命令行](#命令行) |
## 类型化作者入口
@@ -32,11 +39,11 @@ session.run(plugin).await?;
`management`、`command_line` 和 `middleware` 是常用组合入口;其余能力使用
`on(methods::..., handler)`。方法常量固定请求、响应、阶段与载荷位置,作者不手写 RPC 方法名。
状态迁移不是 capability:清单任一 `state[]` 声明了 `migratesFrom` 时,注册
`methods::STATE_MIGRATE`,构建器也会要求该处理器存在。
`methods::STATE_MIGRATE`,构建器也会要求该处理器存在
类型化合同故意区分三种数据:普通控制元数据、可能敏感的 JSON 二进制载荷、原始字节。
例如 CLI 参数、账号凭据和状态迁移批次使用二进制 JSON,管理 HTTP 正文保持原始字节。
错误地把敏感载荷放进控制参数会在业务处理器运行前被拒绝。
错误地把敏感载荷放进控制参数会在业务处理器运行前被拒绝
## 能力与方法
@@ -59,7 +66,7 @@ Provider 固定为宿主内置的 OpenAI 与 xAI。插件提供以下扩展能
## 访问域
安装时接受清单声明的访问域,不存在另一套逐方法、逐用途或资源 ID 白名单。宿主仍会检查父调用、阶段、
期限、实例代次、资源归属,以及 Key、账号和 Provider 自身的业务规则。
期限、实例代次、资源归属,以及 Key、账号和 Provider 自身的业务规则
| 权限 | 开放的宿主能力 |
| --- | --- |
@@ -75,17 +82,17 @@ Provider 固定为宿主内置的 OpenAI 与 xAI。插件提供以下扩展能
| `quota_observations` | 在管理/命令/维护阶段读取及刷新全部账号的额度观测,不暴露凭据或执行上游额度重置 |
`host.log` 和本插件声明的 `host.state.*` 是基础设施,不需要额外 permission。公开调用阶段不开放任何
宿主回调;权限也不能把一个父调用的句柄、流或上下文转移到另一个调用。
宿主回调;权限也不能把一个父调用的句柄、流或上下文转移到另一个调用
## 宿主资源回调
所有回调都通过当前 `TypedCall.host` 发起,并受父调用截止时间和取消信号约束。
所有回调都通过当前 `TypedCall.host` 发起,并受父调用截止时间和取消信号约束
### 基础事实
纯展示页面声明 `management` 和 `data` 即可,不需要请求处理 binding 或 `accounts` 权限。
`data` 是独立的管理员授权域,只允许 `management`、`command_line`、`maintenance` 阶段使用;请求链、注册及公开回调均拒绝。
它可读取全部账号与 Key 的下列最小事实,不继承或授予客户端 Key 的模型执行权。
它可读取全部账号与 Key 的下列最小事实,不继承或授予客户端 Key 的模型执行权
| SDK 方法 | 回调 | 查询与结果 |
| --- | --- | --- |
@@ -95,16 +102,16 @@ Provider 固定为宿主内置的 OpenAI 与 xAI。插件提供以下扩展能
类型在 `call::data`。控制参数为 `{}`,查询和结果使用二进制 JSON;结果固定 `schema_version=1`。
SDK 解析响应时忽略未知字段,包含账号分页、账号及额度窗口;已知字段按声明校验类型与必填性,查询拒绝未知字段。
插件须通过 `engines.codex-proxy-rs` 限定支持所用接口和字段的宿主版本。
插件须通过 `engines.codex-proxy-rs` 限定支持所用接口和字段的宿主版本
`key_facts` 每次读取当前管理数据,返回 `client_key_id`、`enabled`、`group_ids`;不存在的 Key 沿用事实接口的 `rejected` 错误。它需要 `data` 权限,`keys`、`key_budgets` 和 `quota_observations` 权限不能替代。
`key_facts` 每次读取当前管理数据,返回 `client_key_id`、`enabled`、`group_ids`;不存在的 Key 沿用事实接口的 `rejected` 错误。它需要 `data` 权限,`keys`、`key_budgets` 和 `quota_observations` 权限不能替代
`group_ids` 表示显式绑定,包含停用分组,不是最终可路由账号集合;空绑定也不代表单账号范围。通过 `account_facts` 关联账号时需完整遍历分页,结果包含停用账号。跨查询关联及同步策略由插件负责,这些调用不构成跨查询事务,管理员修改绑定后应重新检查。
账号仅返回 `account_id`、`provider_id`、`name`、`email`、`group_ids`、`enabled` 和 `updated_at_ms`,不附带令牌或代理信息。
`name` 和 `email` 来自宿主已保存的账号资料;没有邮箱时 `email=null`,读取不会请求上游个人信息。
额度仅返回观测时间与窗口的 `key`、`window_seconds`、`used_percent`、`reset_at_ms`。
时间均为 UTC Unix 毫秒,比例为百分数;未知值保留 `null`,不能解释为 0。`observed_at_ms=null` 表示没有可用观测时间,
不保证当前缓存新鲜,不提供历史快照或多个查询之间的原子一致性。
不保证当前缓存新鲜,不提供历史快照或多个查询之间的原子一致性
```rust,ignore
use gateway_plugin_sdk::call::data::{AccountFactsQuery, QuotaFactsQuery};
@@ -118,26 +125,26 @@ for account in page.accounts {
```
已有 `usage` 观察提供请求最终用量、时间和结算事实;该投递有界、不是历史补偿接口。
本接口不提供 SQL、池汇总、健康分、额度预测或历史使用记录查询;插件结果保存在自身 `host.state.*` 中。
本接口不提供 SQL、池汇总、健康分、额度预测或历史使用记录查询;插件结果保存在自身 `host.state.*` 中
### 额度观测刷新
`quota_observations` 授权访问所有当前及未来账号的额度观测。它与只读 `data`、含原始凭据的 `accounts`
分别授权;不会授予账号目录、凭据读取、账号修改、上游额度重置或 Key 预算操作权限。
需要枚举账号时另行声明 `data`,已知账号 ID 时可直接查询或刷新。
需要枚举账号时另行声明 `data`,已知账号 ID 时可直接查询或刷新
- `call.host.quota_facts(query)` / `host.data.quota.get`:`data` 或 `quota_observations` 任一域可读取已有快照,不访问上游。
- `call.host.refresh_account_quota(query)` / `host.quota_observations.refresh`:仅 `quota_observations` 可请求 Provider 刷新观测。
- `call.host.quota_facts(query)` / `host.data.quota.get`:`data` 或 `quota_observations` 任一域可读取已有快照,不访问上游
- `call.host.refresh_account_quota(query)` / `host.quota_observations.refresh`:仅 `quota_observations` 可请求 Provider 刷新观测
两个方法仅在 `management`、`command_line`、`maintenance` 阶段开放;均使用 `call::data::QuotaFactsQuery`
和 `QuotaFacts`,控制参数为 `{}`,输入输出在二进制 JSON 中。刷新复用原生 Provider 的凭据、代理、超时及
观测落库规则,返回同一最小事实投影,不返回凭据、预测、个人资料或 Provider 原始响应。
刷新观测不会消费额度重置券,也不会自动重置任何 Key;Provider 仍可能依据真实上游响应更新账号可用性观测。
刷新观测不会消费额度重置券,也不会自动重置任何 Key;Provider 仍可能依据真实上游响应更新账号可用性观测
接口不承诺每次得到不同的周期或样本,也不保证结果对应一个账号重置事件。不存在账号返回 `rejected`;
Provider 不支持刷新(如 OpenAI URL + Key 账号)返回 `invalid_input`,其他失败沿用原生管理错误映射。
刷新受父调用期限和取消约束;结果未知不表示观测未落库。SDK 不自动重复调用,插件应控制刷新频率并处理失败,
不能把失败、空值或旧快照当作额度恢复。维护阶段只允许这条受控原生刷新,不因此获得任意网络访问权。
不能把失败、空值或旧快照当作额度恢复。维护阶段只允许这条受控原生刷新,不因此获得任意网络访问权
### 账号与凭据
@@ -148,14 +155,14 @@ Provider 不支持刷新(如 OpenAI URL + Key 账号)返回 `invalid_input`
- `save` 新建时显式提供 Provider,由宿主生成账号 ID;替换时必须提交精确 credential revision
插件可以自行选择其业务需要的账号和 Provider,不需要安装器预先配置允许列表。宿主仍校验账号归属、资格、
凭据版本和并发事务;原始凭据不得进入日志、审计正文或控制元数据。
凭据版本和并发事务;原始凭据不得进入日志、审计正文或控制元数据
### 自有资源与维护
声明 `maintenance` 并注册 `methods::RECONCILE`,宿主会在实例启用发布、进程恢复及配置变更后调用
`plugin.reconcile`,并每 30 秒补偿一次。处理器必须幂等;调用可能重复,配置通知会合并,不表示逐条账号事件。
每个实例串行执行,不同实例相互独立;调用限时 30 秒,失败后等待 5 秒重试。维护失败不回滚已经提交的资源,
下一次对账继续补齐。只读校验、准备候选与 CLI 帮助不会启动维护;停用、替换和宿主关闭时取消旧任务。
下一次对账继续补齐。只读校验、准备候选与 CLI 帮助不会启动维护;停用、替换和宿主关闭时取消旧任务
维护阶段允许日志、私有状态,以及已授权的 `data`、`groups`、`keys`、`key_budgets`、`quota_observations` 回调;不开放任意网络、凭据或模型执行。
其他管理/命令入口也可使用下列资源方法:
@@ -169,21 +176,21 @@ Provider 不支持刷新(如 OpenAI URL + Key 账号)返回 `invalid_input`
资源键使用 1~64 位小写字母、数字、`_`、`.`、`-`,首位为字母或数字,在实例和资源类型内唯一。
归属由宿主签发,插件不能指定其他实例。`ensure` 只在首次创建时使用属性,不覆盖管理员对名称、启用状态或限制的修改;
同名的管理员资源不会被接管,名称冲突会失败。账号已被删除时增量加入会忽略该 ID。
无实际变化时不会写审计或递增配置版本。创建与归属登记、成员变更、授权复验均在同一事务完成。
无实际变化时不会写审计或递增配置版本。创建与归属登记、成员变更、授权复验均在同一事务完成
插件升级沿用实例资源;停用保留分组和 Key,其原生启用状态保持不变。删除实例解除归属,资源仍由管理员管理;
重新安装为新实例不会接管旧资源。管理员删除自有资源后,仍启用的维护逻辑可在下次对账重新创建。
Key 明文仍通过宿主管理面查看,插件模型调用使用返回的 Key ID。
Key 明文仍通过宿主管理面查看,插件模型调用使用返回的 Key ID
典型处理器先确保分组存在,再通过 `data` 分页查询账号并增量补齐成员,最后确保 Key 存在。
安装 `groups` 域即授权纳入全部当前及未来账号;插件可按自己的配置筛选账号,但该筛选不构成宿主的权限边界。
安装 `groups` 域即授权纳入全部当前及未来账号;插件可按自己的配置筛选账号,但该筛选不构成宿主的权限边界
### Client Key 预算
`key_budgets` 是原生 Key 预算访问域,仅在 `management`、`command_line`、`maintenance` 阶段使用。
接受该域即允许查询、设置金额上限及清零全部当前及未来 Client Key,包括管理员和其他插件创建的 Key;
插件配置中的 Key 筛选不构成宿主权限边界。`keys` 只负责插件自有 Key 的创建,不能替代这一预算管理授权。
`key_budgets` 不授予模型执行、密钥读取、Key 创建、名称/分组/画像或并发/RPM 配置修改权限。
`key_budgets` 不授予模型执行、密钥读取、Key 创建、名称/分组/画像或并发/RPM 配置修改权限
类型位于 `call::key_budgets`;下列预算方法控制参数为 `{}`,输入输出为二进制 JSON:
@@ -194,24 +201,24 @@ Key 明文仍通过宿主管理面查看,插件模型调用使用返回的 Key
| `reset_key_budget` / `host.keys.reset_budget` | `{client_key_id, period}`;`period` 必填,取 `daily`、`weekly` 或 `all`;返回 `{client_key_id}` |
`list_keys` / `host.keys.list` 仍可查询非秘密目录,仅返回 `{id,name,enabled}` 和游标,沿用目录的控制参数编码。
上述操作均接受 Key ID,不接受 Key 明文,也不要求先查询目录。
上述操作均接受 Key ID,不接受 Key 明文,也不要求先查询目录
金额为非负十进制字符串,沿用原生金额精度;上限 `"0"` 表示不限。`KeyBudget` 的字段为
`daily_limit_usd`、`weekly_limit_usd`、`daily_used_usd`、`weekly_used_usd`、`daily_resets_at_ms`、`weekly_resets_at_ms`;
时间为 UTC Unix 毫秒,`null` 表示尚未使用或窗口已过期。读取不触发准入、开启窗口或清零,停用的 Key 仍可管理。
时间为 UTC Unix 毫秒,`null` 表示尚未使用或窗口已过期。读取不触发准入、开启窗口或清零,停用的 Key 仍可管理
上限更新仅写入提供的日/周金额;省略或 `null` 的项保持不变。它保留已用金额、窗口到期时间、费用历史及其他 Key 配置。
写入复用 Key 行锁,与结算串行;实际变化时授权、修改、配置 revision 和审计在同一事务提交,并通知原生配置发布。
相同值再次赋值不产生新 revision 或审计;并发更新按事务顺序生效,同一字段由后提交的值覆盖,接口不提供调用去重或比较交换。
将非零上限降低到已用金额及以下会拒绝后续准入,提高限额可恢复准入,在途请求仍按原生规则完成和结算。
将非零上限降低到已用金额及以下会拒绝后续准入,提高限额可恢复准入,在途请求仍按原生规则完成和结算
重置只清零所选周期,保留额度上限、密钥、窗口到期时间和历史费用事件;未使用的 Key 不因此开启窗口。
费用按完成时间归属,重置前完成但延迟落盘的费用不会重新扣入已重置周期,重置后完成的在途请求仍正常扣额。
授权复验、清零和审计在同一事务提交,不推进配置 revision。每次调用均执行新重置,可能清掉两次调用之间的新消费。
授权复验、清零和审计在同一事务提交,不推进配置 revision。每次调用均执行新重置,可能清掉两次调用之间的新消费
SDK 不自动重试。超时或断连不能证明写入未提交;重试上限赋值也可能覆盖期间其他调用的修改。
不存在的 Key 返回 `rejected`,无权限或阶段不符返回 `permission_denied`;写入事务复验发现实例停用、版本或授权变化时返回 `conflict`,
非法输入返回 `invalid_input`。账号关联、预算分配和重置触发由插件决定,宿主不自动串联上述接口。
非法输入返回 `invalid_input`。账号关联、预算分配和重置触发由插件决定,宿主不自动串联上述接口
### Key、模型与模型调用
@@ -226,14 +233,14 @@ SDK 不自动重试。超时或断连不能证明写入未提交;重试上限
`ModelExecuteRequest.client_key_id` 中显式选择执行 Key;`request`、`attempt`、`routing` 和
`scheduling` 阶段将该字段留空并继承父请求身份,不能切换 Key。Key 的启用状态、模型规则、账号组、并发和预算
始终使用调用时的当前事实。
插件不能读取 Key 明文,也不能用一个 Key 查询结果替另一个 Key 执行。
插件不能读取 Key 明文,也不能用一个 Key 查询结果替另一个 Key 执行
模型事件使用 `call::model::ExecutionEvent` 的 `GPE1` 封套,将 canonical 事实与原始 wire 分段编码。
非流式模型结果通过 `ModelEventBatch` 的 `HME1` 有界二进制批次返回;流式句柄只在创建它的父调用内有效。
发起回调的插件实例在子调用中跳过,Core 还会限制递归深度、总数和并发,避免插件调用自身形成环。
发起回调的插件实例在子调用中跳过,Core 还会限制递归深度、总数和并发,避免插件调用自身形成环
`host.affinity.lookup` 属于 `requests` 域,只查询 Provider 已持久化的真实亲和键。命中只是账号偏好,
后续执行仍重新检查当前 Key 规则、账号资格和租约。
后续执行仍重新检查当前 Key 规则、账号资格和租约
### 网络
@@ -242,7 +249,7 @@ SDK 不自动重试。超时或断连不能证明写入未提交;重试上限
地址被拒绝返回 `permission_denied`,期限耗尽返回 `timeout`,解析、连接或响应读取失败返回 `upstream`。
这些 HTTP 业务错误的 `message` 是宿主生成的安全分类提示,不包含目标 URL、凭据或上游错误正文,
插件可以在相应操作中展示;协议故障不应直接展示内部消息。Fake-IP 环境按[部署排障](../../../../../deploy/README.md#插件取文与-fake-ip-dns)
调整 DNS,不通过放宽地址范围解决。
调整 DNS,不通过放宽地址范围解决
## 请求扩展
@@ -250,16 +257,16 @@ SDK 不自动重试。超时或断连不能证明写入未提交;重试上限
`middleware` 的 `request` mount 在逻辑请求外层执行一次,`attempt` mount 在每次选定 Provider 和账号后重建。
作者必须在清单中显式选择一个或两个阶段。`PluginBuilder::middleware` 接收 `MiddlewareCall`,其中
`next.run(request)` 是 single-use continuation;响应按相反顺序穿过中间件。
`next.run(request)` 是 single-use continuation;响应按相反顺序穿过中间件
请求正文和响应帧受 `requests` 域保护。没有正文时仍要区分“保持原正文”和“替换为空”:默认使用
`MiddlewareRequestBody::Preserve`,只有 `replace_body(Vec::new())` 才表示清空。Header 修改采用增量
remove/append,保留合法多值。`MiddlewareBody::map_frames` 复用会话的流控和唯一终态,不建立插件全局流表。
remove/append,保留合法多值。`MiddlewareBody::map_frames` 复用会话的流控和唯一终态,不建立插件全局流表
**不改写就 1:1 保留**:同一网关处理边界上,默认 `next.run(request)` 保留原始正文、未修改的 header 和响应流字节及顺序。
只解析 `request.body` 不会触发替换;SDK 比较原投影,宿主持有的隐藏 header 不会被脱敏视图覆盖。
只观察响应可使用 `response.body.inspect_frames(|frame| { /* 读取,不修改 */ })?`,保留来源信封、背压和取消语义。
这一保证比较插件前后的同一边界,Provider 自身的协议适配、两次调用的随机值和网络分段不在比较范围内。
这一保证比较插件前后的同一边界,Provider 自身的协议适配、两次调用的随机值和网络分段不在比较范围内
需要声明功能转换时,在清单选择 `middleware.version=2`,普通 v1 插件仍可原样运行。
`request.declare_capabilities(CapabilityDeclaration { handled, required })` 随单次 `next` 发出,必须同时显式替换正文且具有 `requests` 权限。
@@ -268,7 +275,7 @@ remove/append,保留合法多值。`MiddlewareBody::map_frames` 复用会话
插件须负责对应的提示词转换及普通响应、错误、流式响应还原。`required` 为额外上游需求,不能覆盖正文推导出的要求。
重复项、同一项同时出现在两组、空声明、v1 使用声明、attempt 阶段使用声明均会拒绝。
原始语义中未由插件承担的功能继续约束路由;实际正文重新加入某功能时,该需求仍生效。
原生续接不能由声明豁免,attempt 阶段的既有冻结与保护字段检查继续生效。
原生续接不能由声明豁免,attempt 阶段的既有冻结与保护字段检查继续生效
```rust,ignore
use gateway_plugin_sdk::call::middleware::{CapabilityDeclaration, RequestFeature};
@@ -282,14 +289,14 @@ restore_and_validate_response(response).await
```
仅实现中间件时也可使用轻量的 `MiddlewarePlugin`;需要与管理或其他方法组合时使用
`PluginBuilder`,两者复用相同的 `MiddlewareCall` 和 `MiddlewareResponse`。
`PluginBuilder`,两者复用相同的 `MiddlewareCall` 和 `MiddlewareResponse`
### 模型目录
`model_catalog` v1 在 registration 阶段一次提交 `ModelCatalogRegistration { models: Vec<ModelAlias> }`,不需要请求级 binding。
每条 `ModelAlias { id, provider, model }` 定义公开 ID、内置 Provider(`openai` 或 `xai`)及直接上游目标。
上游目标须已存在于该 Provider 的当前目录;同名原生模型、静态映射、其他插件条目、别名链和循环均拒绝发布。
每实例最多 256 条,整个二进制 JSON 最多 64 KiB;不允许覆盖原生元数据或自行宣称目标不支持的功能。
每实例最多 256 条,整个二进制 JSON 最多 64 KiB;不允许覆盖原生元数据或自行宣称目标不支持的功能
```rust,ignore
use gateway_plugin_sdk::call::catalog::{ModelAlias, ModelCatalogRegistration};
@@ -302,17 +309,17 @@ let plugin = PluginBuilder::from_json(include_bytes!("../plugin.json"))?
有效目录、路由和目标能力随同一个不可变快照发布,`/v1/models`、详情、原生目录和 `host.models.list` 共用该事实及现有 Key/账号模型范围。
原生别名继承目标的完整可公开对象;更新失败保留旧快照,禁用后新请求不再使用该别名,在途请求持有旧代次。
v1 是直接别名,动态选择可组合 `model_router`;实际路由仍须通过宿主的权限和能力校验,不提供多目标虚拟模型注册合同。
v1 是直接别名,动态选择可组合 `model_router`;实际路由仍须通过宿主的权限和能力校验,不提供多目标虚拟模型注册合同
### 重试决策
`retry_policy` v1 固定绑定 `retry` 阶段,故障策略只能为 `delegate`。使用 `methods::RETRY_DECISION` 处理 `RetryDecisionRequest`。
输入包含失败分类、必要状态码、发送状态、attempt 序号、Provider/上游模型、剩余路由次数和期限,以及 `allowed_actions`。
不发送账号凭据、请求正文或上游错误转储。路由次数不等同于 Provider 自有传输恢复预算。
不发送账号凭据、请求正文或上游错误转储。路由次数不等同于 Provider 自有传输恢复预算
返回 `RetryDecision::Delegate`、`Stop` 或 `Retry`。`Retry` 必须出现在宿主允许动作中,并且仅继续宿主已经选定的恢复路径;
不能指定账号、延迟、目标或提高预算。响应已交付、发送不明确、外部副作用及续接绑定等既有安全门不可绕过。
Core 处理建立响应流前与流中的失败,并在策略返回后再次检查期限、取消和副作用。
Core 处理建立响应流前与流中的失败,并在策略返回后再次检查期限、取消和副作用
```rust,ignore
use gateway_plugin_sdk::{client::TypedReply, call::policy::RetryDecision};
@@ -328,53 +335,53 @@ let plugin = PluginBuilder::from_json(include_bytes!("../plugin.json"))?
按 binding 顺序、插件 ID、实例 ID 确定委托链,首个合法非委托结果生效。整条链最多等待 2 秒,并受请求剩余期限约束。
退出、超时、未知字段及不允许的动作委托后续处理,最终回到宿主决定;故障不改写原始上游错误。
本阶段宿主只开放日志与插件私有状态回调,不允许额外网络、模型调用或账号访问。middleware 的 `next` 仍最多调用一次。
本阶段宿主只开放日志与插件私有状态回调,不允许额外网络、模型调用或账号访问。middleware 的 `next` 仍最多调用一次
### 路由、调度与观察
`policy.route_model` 和 `policy.schedule_account` 只提出决定,宿主随后复核 Key 规则、模型能力、账号资格和租约。
`policy.observe_request` 接收一次最终请求事实;`request_lifecycle` 提供终态,`usage` 提供最终标准化用量、费用和耗时。
缺失值表示宿主没有该事实,不等于零,也不能由插件重新计价。
缺失值表示宿主没有该事实,不等于零,也不能由插件重新计价
`websocket.response_event` 只读观察真实上游 WS 事件,元数据与原始帧分开传输。事件序号在请求内单调递增;
有界队列丢弃可能产生间隔。观察超时、过载或插件失败不改变业务响应,插件也不能修改 WS 帧。
有界队列丢弃可能产生间隔。观察超时、过载或插件失败不改变业务响应,插件也不能修改 WS 帧
观察计划在请求开始时冻结,最终事件最多派发一次。`Handshake`、`Message`、`Frame` 和 `PluginFault` 的
调试输出会隐藏配置、载荷与错误正文;插件自己的日志同样不能输出凭据或完整请求。
调试输出会隐藏配置、载荷与错误正文;插件自己的日志同样不能输出凭据或完整请求
## 状态、日志与迁移
清单 `state[]` 声明命名空间、`schemaVersion`、JSON schema、记录与字节配额。运行时用
`host.state.get/put/delete` 访问;创建要求键不存在,更新和删除必须提供精确版本。状态始终绑定当前
插件实例、制品和 schema,不能直接读写宿主数据库。
插件实例、制品和 schema,不能直接读写宿主数据库
不兼容升级由宿主停用并排空旧会话,再调用 `plugin.state.migrate` 分批迁移到暂存状态。插件逐项返回
keep、replace 或 delete;全部完成后宿主才原子提交新配置与状态,失败时丢弃暂存结果。
keep、replace 或 delete;全部完成后宿主才原子提交新配置与状态,失败时丢弃暂存结果
`host.log` 接收固定事件名、级别和有界字段。宿主脱敏并限制速率;`recorded: true` 只表示已交给有界日志任务。
日志和私有状态无需 permission,但注册、配置与公开管理阶段仍受各自的回调生命周期限制。
日志和私有状态无需 permission,但注册、配置与公开管理阶段仍受各自的回调生命周期限制
## 管理页面、公开入口与 CLI
`PluginBuilder::management` 同时冻结 `ManagementRegistration` 并绑定 `management.handle`。请求元数据在控制参数,
原始 HTTP 正文在二进制载荷;响应同样分离。宿主不转交管理 Cookie、Authorization 或任意 header,插件也不能
返回任意响应 header。账号操作通过 `accounts` 域调用宿主,资源由调用参数选择,不在路由注册中固定 Provider。
返回任意响应 header。账号操作通过 `accounts` 域调用宿主,资源由调用参数选择,不在路由注册中固定 Provider
页面运行在隔离 iframe 中,静态资源必须同时出现在清单 `resources` 和管理注册中。标记为公开的资源及一次性
票据回调需要 `public_endpoints`;`management.callback` 使用 `public_management` 阶段,并且不能调用账号、
网络、模型、状态或日志端口。
网络、模型、状态或日志端口
宿主自动注入尺寸监听与上报脚本,为 `body` 设置扣除页面头部和外侧留白后的最小高度,整页滚动由管理端承接。
插件无需调用 resize、发送尺寸消息或安装桥接依赖;Vue 等框架的根容器需要延续最小高度时,使用普通 CSS `min-height: inherit`。
页面使用自然文档流,内容增加或减少时自动同步高度;不使用 `100vh`、`height: 100%`、整页固定定位或内部滚动容器代替文档流。
编辑器、日志等局部区域可自行限高滚动,脱离文档流的浮层不会用于撑高页面。
编辑器、日志等局部区域可自行限高滚动,脱离文档流的浮层不会用于撑高页面
### 页面宿主桥 v2
宿主注入只读的 `window.codexProxyPlugin`。`request({ method, path, query?, contentType?, body? })` 只能访问当前版本
已注册的管理路由,返回 `{ status, contentType, body: ArrayBuffer }`;`callbackTicket({ path, ttlSeconds })` 为已注册
回调签发 1–600 秒的一次性地址;`resourceUrl(path)` 只解析同一制品已注册的非 HTML 资源。`theme` 为当前
`light` / `dark` 值,变化时触发 `codex-proxy-themechange`。页面不能指定其他实例、制品、版本、URL 或请求 header。
`light` / `dark` 值,变化时触发 `codex-proxy-themechange`。页面不能指定其他实例、制品、版本、URL 或请求 header
普通模型请求使用标准 `Response` 接口:
@@ -417,14 +424,16 @@ for await (const chunk of response.body!) {
observer 也按常规计划执行;它不是插件执行期间的嵌套 `host.model` 回调。正文最多 8 MiB,每页最多 4 个并发模型
请求,总时限 10 分钟;响应以不超过 64 KiB 的单块 pull/ack 传输。页面 30 秒不继续消费响应、调用
`AbortController.abort()` / `ReadableStream.cancel()`,或页面被切换、停用、换版时,宿主都会取消请求。桥不接受
任意 URL、header 或 Key 原文;只转交响应状态、安全 header 以及原生 JSON / SSE 正文。
任意 URL、header 或 Key 原文;只转交响应状态、安全 header 以及原生 JSON / SSE 正文
### 命令行
`PluginBuilder::command_line` 冻结命令帮助并绑定执行函数。注册和执行的 JSON 都位于二进制载荷,控制信封为 `{}`。
命令若返回待保存账号,需要 `accounts` 域;只有退出码为零时宿主才按顺序执行各自独立的 CAS 提交。
模型命令通过 `host.keys.list` 展示非秘密 Key,再显式选择 Key 查询模型和执行,不依赖安装时预绑定。
模型命令通过 `host.keys.list` 展示非秘密 Key,再显式选择 Key 查询模型和执行,不依赖安装时预绑定
## 数据面入口认证
`frontend_auth.identifier` 返回稳定认证器标识;`frontend_auth.authenticate` 接收敏感的 Authorization 载荷并返回
外部 principal。插件不能指定 Client Key,宿主根据当前入口配置完成映射,并继续检查 Key 的启用状态、模型规则、
账号组、并发和预算。该能力只处理数据面身份,不接管管理端登录。
账号组、并发和预算。该能力只处理数据面身份,不接管管理端登录
@@ -3,7 +3,7 @@
[返回 SDK](../README.md) · [能力与回调](capabilities.md)
`Manifest` 对应包内 `plugin.json`。作者源清单由 `Manifest::from_author_slice` 读取,CLI 打包也调用同一入口;
因此本地构建器、生成的安装清单和运行注册共享一份规范化结果。
因此本地构建器、生成的安装清单和运行注册共享一份规范化结果
| 字段 | 规则 |
| --- | --- |
@@ -20,7 +20,7 @@
作者清单不能包含 `package`。使用 `Manifest::from_author_slice()` 校验并规范化;直接反序列化的 `Manifest`
代表完整合同,不会隐式补全作者字段。打包后,`main` 和所有资源都必须进入 `package.files`;宿主用
`package_for(host, os, architecture)` 检查版本与平台。运行模式仅为 `trustedProcess`:插件拥有与宿主
相同的系统身份,**不是进程沙箱**。
相同的系统身份,**不是进程沙箱**
## 扩展项简写
@@ -52,34 +52,21 @@ capability 固定并由工具生成:
| `maintenance` | `maintenance` |
`middleware` 必须显式选择 `request`、`attempt` 或两者;协议格式等真实业务选择也不能省略。
安装清单若携带不同的固定阶段会被拒绝,而不是在加载时静默改写。
安装清单若携带不同的固定阶段会被拒绝,而不是在加载时静默改写
## 权限
权限使用以下稳定访问域:
`permissions` 按[访问域](capabilities.md#访问域)声明所需宿主资源,安装时统一接受该版本的声明
| 标识 | 含义 |
| --- | --- |
| `network` | 使用宿主受管网络 |
| `models` | 查询非秘密 Key 与模型并调用模型,可能产生消耗 |
| `accounts` | 查询、读取原始凭据和修改账号 |
| `data` | 仅在管理、命令与维护阶段只读全部账号的基础信息和已有额度观测 |
| `requests` | 查看和处理请求、响应、路由、调度与观察事实 |
| `groups` | 创建本实例分组并管理所有当前及未来账号在这些分组中的成员关系 |
| `keys` | 创建绑定本实例分组的 Key,不读取密钥明文 |
| `key_budgets` | 查询全部 Client Key 的预算、修改日/周金额上限及重置用量,不读取密钥或修改其他配置 |
| `quota_observations` | 查询和刷新全部账号的额度观测,不暴露凭据或执行上游额度重置 |
| `public_endpoints` | 提供无需登录即可访问的资源或回调 |
不填写逐方法、用途、Key、账号或 Provider 白名单;调用仍受阶段、父调用、Key 规则、账号 revision、资源归属和流生命周期约束
安装时统一接受清单声明的域,不再填写逐方法、用途、Key、账号或 Provider 白名单。日志与清单声明的
本插件私有状态是基础设施,无需单独 permission。权限并不替代方法阶段、父调用、Key 规则、账号 revision、
资源归属和流生命周期校验,具体接口见[能力与回调](capabilities.md#访问域)。
日志与清单声明的本插件私有状态属于基础设施,无需单独 permission
## 插件图标
`icon` 指定管理端展示的插件图标。值可以是一个包内文件路径,也可以是包含 `light`、`dark` 两个路径的对象。
使用单个路径时,浅色和深色主题共用同一张图;使用主题对象时,管理端按当前主题选择,两项都必须填写。
不填写 `icon` 时显示通用图标。图标路径不是远程 URL,也不接受内置图标名称。
不填写 `icon` 时显示通用图标。图标路径不是远程 URL,也不接受内置图标名称
在 `plugin.json` 中同时设置 `icon` 和对应的 `resources` 项。以下是单图标配置片段:
@@ -128,4 +115,4 @@ capability 固定并由工具生成:
将图标文件与清单一同交给 [插件 CLI](../../../../apps/plugin-cli/README.md) 打包。工具收集 `resources` 声明的文件,
生成 `package.files` 摘要;宿主在安装时验证图标内容。管理端通过 [图标读取接口](../../../../../docs/api.md#12-插件管理)
以图片方式展示,不将 SVG 源码插入页面 HTML。
以图片方式展示,不将 SVG 源码插入页面 HTML
+10 -10
View File
@@ -1,28 +1,28 @@
# 数据库迁移
sqlx 在服务启动、监听请求之前按编号执行迁移,并把文件 checksum 记入 `_sqlx_migrations`。
已应用迁移的字节与记录不一致时,服务会拒绝启动。
已应用迁移的字节与记录不一致时,服务会拒绝启动
初始化基线与后续 schema 变更按递增编号组织。
完整清单以本目录 SQL 文件和 `.frozen-sha256` 为准。
完整清单以本目录 SQL 文件和 `.frozen-sha256` 为准
## 冻结规则
- 同一大版本内,已合入 main 的迁移文件**永久冻结**,一个字节都不能改——包括注释和
空白。任何改动都会让该大版本的已部署实例在下次重启时因 checksum 失配而无法启动,
且没有自动恢复路径。
且没有自动恢复路径
- 同一大版本内的 schema 变更一律新增编号迁移,永远不回改
旧文件。切换大版本时允许把已完成升级的 schema 折叠成新的初始化基线,因为跨大版本
不支持在线升级,目标版本必须使用全新数据目录部署。
不支持在线升级,目标版本必须使用全新数据目录部署
- 支持连续升级的预发行线从首次发布起同样冻结迁移。alpha、beta、rc 到对应正式版必须保留
已发布迁移的编号与字节;同一轮 exp 的后续发行也只能追加迁移。发布前以该发行线已发布版本
的数据库验证升级路径,不能只与上一正式版比较。不同 exp 实验线之间、exp 与正式版之间
不保证迁移历史兼容。
不保证迁移历史兼容
- `.frozen-sha256` 是冻结清单(`sha256sum` 格式)。CI 会校验:清单内文件
字节未变,且目录里每个 `*.sql` 都已入册。新增迁移时在同一提交里执行
`sha256sum 000N_xxx.sql >> .frozen-sha256` 入册。
`sha256sum 000N_xxx.sql >> .frozen-sha256` 入册
- 若同一大版本内确需修正已冻结迁移里的错误,用新迁移做补偿性变更(`alter` / 回填),
不要动原文件。
不要动原文件
从迁移目录检查已登记文件:
@@ -32,7 +32,7 @@ sha256sum --check --strict .frozen-sha256
```
CI 还会检查是否遗漏新 SQL 文件,并在 PR 中检查清单只增不改。
遇到 checksum 不一致,先核对运行版本和文件来源;不要修改数据库中的 checksum 来绕过校验。
遇到 checksum 不一致,先核对运行版本和文件来源;不要修改数据库中的 checksum 来绕过校验
## 本地测试库
@@ -46,9 +46,9 @@ export CPR_TEST_REDIS_URL='redis://:<password>@127.0.0.1:6379'
测试自建随机 schema / key 前缀做隔离,但仍应使用专用开发或测试实例,不要连接生产库。
凭据从自己的部署配置或 CI Secret 中读取,不要粘贴未脱敏的 `docker inspect` 输出。
环境变量未设置导致的跳过不算数据库测试通过。
环境变量未设置导致的跳过不算数据库测试通过
运行包含 `StoreBundle` 初始化的完整集成测试时,两条测试 URL 都须包含密码,且密码满足 Store
启动配置的 48 位十六进制要求;仅能连接数据库并不代表该初始化合同通过。专用服务使用对应测试密码,
并在测试进程中清除 `CPR_DATABASE_URL`、`CPR_REDIS_URL`、`CPR_DATABASE_PASSWORD` 和
`CPR_REDIS_PASSWORD`,避免启动配置被部署环境覆盖。
`CPR_REDIS_PASSWORD`,避免启动配置被部署环境覆盖
+153 -139
View File
@@ -2,7 +2,17 @@
首次安装可按 [快速开始](../README.md#快速开始) 操作。
本文补充客户端配置、权限、备份和升级;部署命令从安装目录 `codex-proxy-rs/` 执行,
其中 `deploy/` 存放 Compose 文件和配置,`.runtime/` 存放持久化数据。
其中 `deploy/` 存放 Compose 文件和配置,`.runtime/` 存放持久化数据
| 任务 | 入口 |
| --- | --- |
| 安装与启动 | [手动安装](#手动安装) · [启动](#启动) · [公网访问](#公网访问) |
| 接入客户端 | [客户端配置](#客户端配置) · [登录与生图排查](#登录与生图排查) |
| 日常运维 | [运行设置](#启动后的运行设置) · [持久化与日志](#持久化与备份) · [请求排查](#请求错误排查) · [密码轮换](#密码语义) |
| 更新与恢复 | [镜像升级](#镜像升级与源码构建) · [在线更新](#管理端在线更新) · [备份与恢复](#备份与恢复) · [优雅关停](#优雅关停) |
| 运行源码 | [开发与源码联调](../docs/development.md) |
## 配置归属
配置按职责保存,不都在 YAML 中:
@@ -14,8 +24,12 @@
| 浏览器本地 | 主题和界面偏好 | 当前浏览器的主题设置 |
项目不使用 `.env` 配置文件。Compose 环境变量用于容器地址、镜像选择和构建发布。
配置加载会忽略未知或已移除的字段,并在启动控制台提示字段名;不输出对应值。缺少必填字段时会指出缺项并停止启动,
已知字段的类型和取值仍需合法;可选字段省略时使用默认值。
配置加载会忽略未知字段,并在启动控制台提示字段名;不输出对应值。缺少必填字段时会指出缺项并停止启动,
已知字段的类型和取值仍需合法;可选字段省略时使用默认值
后端从当前目录向上查找 `deploy/config.yaml`,相对数据、日志和静态资源路径以该文件所在目录解析
Compose 通过环境变量将监听和数据库地址设为容器内地址,并指定镜像中的静态资源目录
## 部署结构
@@ -29,13 +43,13 @@ flowchart LR
App -. 数据库备份 .-> Backup[S3 / R2]
```
网关端口默认只绑定本机;数据库和 Redis 不对公网开放。插件以网关同一系统身份运行,不能作为不可信代码沙箱。
网关端口默认只绑定本机;数据库和 Redis 不对公网开放。插件以网关同一系统身份运行,不能作为不可信代码沙箱
## 手动安装
本节适用于 Linux amd64/arm64,需准备 Docker Engine、Docker Compose Plugin、curl 和 OpenSSL,
并确保当前用户能访问 Docker、可通过 `sudo` 或 root 设置目录权限。一键安装入口见
[快速开始](../README.md#一键安装),脚本会自动完成本节准备和服务启动。
[快速开始](../README.md#一键安装),脚本会自动完成本节准备和服务启动
下载同一正式 Release 的部署文件并设置目录权限:
@@ -56,7 +70,7 @@ sudo install -m 0640 -o "$(id -u)" -g 10001 deploy/config.example.yaml deploy/co
```
也可将 `CPR_RELEASE_TAG` 设置为指定的发布标签。Release 附带的 `compose.yaml` 默认使用该版本镜像,
配置模板和部署文件均包含在 `checksums.txt` 中;不要混用 `main` 分支模板与已发布镜像。
配置模板和部署文件均包含在 `checksums.txt` 中;不要混用 `main` 分支模板与已发布镜像
为 PostgreSQL 与 Redis 分别生成一个密码:
@@ -72,14 +86,14 @@ openssl rand -hex 24
另行设置 `admin.default_password`。它至少需要 12 个字符,不能是常见弱口令,也不能包含 `$`。
`client.session_ttl_minutes` 控制统一登录中密钥身份的固定会话有效期,默认 1440 分钟;管理员有效期仍由
`admin.session_ttl_minutes` 控制。两种身份共用一个 Cookie,成功登录替换旧会话;不改变 `/v1/*` 鉴权和限额。
`admin.session_ttl_minutes` 控制。两种身份共用一个 Cookie,成功登录替换旧会话;不改变 `/v1/*` 鉴权和限额
PostgreSQL 与 Redis 密码必须是 48 位十六进制字符。Compose 通过 `config.yaml` 的凭据桥接区
引用同一密码;三个值都不需要额外导出为环境变量,数据库和 Redis 密码也不能嵌入连接 URL。
引用同一密码;三个值都不需要额外导出为环境变量,数据库和 Redis 密码也不能嵌入连接 URL
Linux 上应用容器以 `10001:10001` 运行。上述命令将应用数据和日志目录设为 `0770`,
配置设为 `0640`,均由当前用户持有、容器组 `10001` 访问。
`config.yaml` 通过 Compose `configs` 只读挂载,普通 Compose 保留宿主机文件的 UID/GID 和 mode。
`config.yaml` 通过 Compose `configs` 只读挂载,普通 Compose 保留宿主机文件的 UID/GID 和 mode
### Redis ACL 用户
@@ -93,12 +107,12 @@ store:
```
将这两项合并到已有配置,保留其他配置和密码锚点;Redis 服务端需事先创建并授权该用户。
用户名中的 `@`、`:` 等特殊字符需要 URL 百分号编码。未填写用户名时使用 Redis 的 `default` 用户。
用户名中的 `@`、`:` 等特殊字符需要 URL 百分号编码。未填写用户名时使用 Redis 的 `default` 用户
环境变量 `CPR_REDIS_URL` 优先于 `store.redis.url`。默认 `deploy/compose.yaml` 已将它设为
`redis://redis:6379/`,使用 ACL 用户时还需把应用服务的该环境变量改为
`redis://u1@redis-host:6379/0`,其中主机名必须能从应用容器访问;只改 `config.yaml` 不会生效。
默认 Compose Redis 服务使用密码认证,不会自动创建 ACL 用户。
默认 Compose Redis 服务使用密码认证,不会自动创建 ACL 用户
## 启动
@@ -115,10 +129,10 @@ docker compose -f deploy/compose.yaml ps
curl -i http://127.0.0.1:8080/healthz
```
`204 No Content` 表示应用、PostgreSQL、Redis 和后台任务的健康检查通过,不代表每个上游账号都可用。
`204 No Content` 表示应用、PostgreSQL、Redis 和后台任务的健康检查通过,不代表每个上游账号都可用
不要把未脱敏的 `docker compose config` 或 `docker inspect` 输出上传到工单;它们会包含
PostgreSQL/Redis 启动密码。日常校验使用 `config --quiet`。
PostgreSQL/Redis 启动密码。日常校验使用 `config --quiet`
### 启动后的运行设置
@@ -132,7 +146,7 @@ PostgreSQL/Redis 启动密码。日常校验使用 `config --quiet`。
位置覆盖只影响 OpenAI 请求中受支持的位置、日期与时区信息,不改变真实出口 IP、epoch 时间戳或系统时区。
`openai.residency` 是独立的部署约束。身份与位置字段见 [运行设置 API](../docs/api.md#8-运行设置)。
后台账号操作使用 Provider 自己的官方画像;发布资料刷新不改写用户选择或 `config.yaml`。
后台账号操作使用 Provider 自己的官方画像;发布资料刷新不改写用户选择或 `config.yaml`
### 插件命令行
@@ -140,64 +154,66 @@ PostgreSQL/Redis 启动密码。日常校验使用 `config --quiet`。
```bash
codex-proxy-rs plugin --help
codex-proxy-rs plugin <实例ID> <命令> --help
codex-proxy-rs plugin <实例ID> <命令> --参数 值
codex-proxy-rs plugin '实例ID' '命令' --help
codex-proxy-rs plugin '实例ID' '命令' --参数 '值'
```
将示例中的实例 ID、命令、参数名和值替换为插件实际提供的内容
不带子命令或使用 `serve` 时启动网关;插件命令不监听 HTTP、不启动后台任务,也不恢复或清理正在运行的
网关准入状态。顶层 `--help/--version` 不读取配置。插件帮助只读数据库,不执行迁移、登录或账号保存,
因此需要先由正常部署完成数据库初始化。宿主诊断保留在配置的文件日志中,stdout/stderr 专用于命令结果。
因此需要先由正常部署完成数据库初始化。宿主诊断保留在配置的文件日志中,stdout/stderr 专用于命令结果
参数支持 `--名称=值` 或 `--名称 值`;布尔参数可直接写 `--名称`,关闭时写 `--名称=false`。
duration 使用 `500ms`、`1m30s` 等带单位形式。参数可能进入 shell 历史和系统进程列表,不能因为插件声明
了敏感参数就认为命令行是安全的密钥输入通道;真实凭据优先使用对应插件提供的受控登录或导入入口。
了敏感参数就认为命令行是安全的密钥输入通道;真实凭据优先使用对应插件提供的受控登录或导入入口
成功命令原样返回插件退出码与输出。命令受总期限约束,`Ctrl-C` 或终止信号会取消调用并回收插件进程。
若命令产生账号,只有成功结果才按顺序通过 Admin 保存;调用受制品访问域、`command_line` 阶段和当前资源事实约束,
模型执行 Key 由具体调用选择,不在实例中预绑定。
部分保存后失败或取消不会自动重放,先核对账号和审计记录,再决定是否重新执行。
部分保存后失败或取消不会自动重放,先核对账号和审计记录,再决定是否重新执行
独立仓库 `codex-proxy-plugins` 的 `examples/request-workbench` 提供不访问真实上游的综合示例。
独立仓库 `codex-proxy-plugins` 的 `examples/request-workbench` 提供不访问真实上游的综合示例
## 公网访问
Compose 默认只绑定 `127.0.0.1`。从其他设备访问时,在应用前配置反向代理,
不要把 PostgreSQL 或 Redis 暴露到公网。
不要把 PostgreSQL 或 Redis 暴露到公网
同源登录页的管理员与 API Key 登录都支持 HTTP 和 HTTPS 登录。反向代理应原样保留浏览器的 `Origin`,
不要清除它或改写 Cookie 的 `Secure` 属性;HTTPS 反代可以使用 HTTP 回源。
HTTP 传输不加密,公网部署仍建议使用 HTTPS。
会话 Cookie 合同见 [管理接口鉴权](../docs/api.md#管理接口)。
会话 Cookie 合同见 [管理接口鉴权](../docs/api.md#管理接口)
反向代理需要保留 `Authorization`,支持 `/v1/responses` 的 WebSocket Upgrade,
并关闭 SSE 响应缓冲。读取超时应覆盖长时间生成任务。
客户端使用部署地址下的 `/v1`,协议与部署一致,不要使用前端开发服务的 `5173/dev/v1`。
当前应用只支持单副本,不能通过复制容器扩容。
当前应用只支持单副本,不能通过复制容器扩容
流式响应在首个上游事件提交后,每 15 秒无输出会发送一次 SSE 注释保活,
并设置 `X-Accel-Buffering: no` 和 `Cache-Control: no-cache, no-transform`。
反向代理仍需允许这些响应头生效;首个事件到达前的等待也需要足够的读取超时。
反向代理仍需允许这些响应头生效;首个事件到达前的等待也需要足够的读取超时
OpenAI 上游池化 WebSocket 默认每 25 秒发送一次 Ping,发出后允许等待 30 秒;
收到 Pong 或其他入站帧即解除本次心跳截止,持续无响应则以 `pong_timeout` 关闭连接。
此策略也覆盖正在生成的请求,与等待下一条上游消息的 `stream_idle_timeout_ms` 分别计时。
此策略也覆盖正在生成的请求,与等待下一条上游消息的 `stream_idle_timeout_ms` 分别计时
若 Codex 在压缩或长时间生成时出现 `error decoding response body`,这表示
客户端读取 HTTP 响应体失败。请结合网关请求诊断中的 `upstream.read.failed`、
`downstream.body.closed` 和反向代理日志判断断开位置,不能仅凭此消息认定是 JSON 格式错误。
SSE 注释保活用于防止传输链路空闲断开,不会重置 Codex 等待完整 SSE 事件的超时;
若报错为 `idle timeout waiting for SSE`,再检查客户端的 `stream_idle_timeout_ms`。
若报错为 `idle timeout waiting for SSE`,再检查客户端的 `stream_idle_timeout_ms`
## 客户端配置
在管理端创建客户端密钥,打开「使用密钥」,按操作系统复制 `config.toml` 和 `auth.json`,
或通过 CCSwitch 导入。已有文件先备份,合并后完全退出并重启 Codex。
CCSwitch 导入同时配置当前 Key 的日/周额度查询,当前 Provider 默认每 30 分钟刷新。
查询地址和凭据随导入生成,在 CCSwitch 中修改 Provider 的地址或 Key 后,需重新导入以同步用量查询。
查询地址和凭据随导入生成,在 CCSwitch 中修改 Provider 的地址或 Key 后,需重新导入以同步用量查询
Linux/macOS 默认目录为 `~/.codex/`,Windows 为 `%USERPROFILE%\.codex\`;
设置过 `CODEX_HOME` 时以该目录为准。Provider 设置应写入用户配置,不要只写到项目目录。
配置层级见 [官方配置参考](https://learn.chatgpt.com/docs/config-file/config-reference)。
配置层级见 [官方配置参考](https://learn.chatgpt.com/docs/config-file/config-reference)
### config.toml
@@ -230,11 +246,11 @@ goals = true
`OpenAI` 是自定义 Provider ID,大小写要与 `model_provider` 一致。合并配置时修改已有表,
不要重复添加 `[features]` 或 Provider 表。更换模型时也要检查其支持的推理强度。
密钥以明文保存,文件仅供本人读取,不要提交到 Git。
密钥以明文保存,文件仅供本人读取,不要提交到 Git
需要指定完整模型目录时,在账号的模型列表中导出所选 Codex 模型,并在 `config.toml` 顶层设置
`model_catalog_json = "/absolute/path/to/cpr-model-catalog.json"`。导出文件不含账号凭据;
它是一次目录快照,调整选择或上游模型能力变化后需重新导出。
它是一次目录快照,调整选择或上游模型能力变化后需重新导出
### auth.json
@@ -246,27 +262,27 @@ goals = true
使用 `auth.json` 读取密钥的客户端或 CCSwitch 可保留这份文件。上述 Provider 配置从
`experimental_bearer_token` 读取代理密钥,可与仅含 API Key 的 `auth.json` 共存。
真实 OpenAI OAuth 账号文件用于管理端账号导入,不要当作代理配置分发给客户端。
真实 OpenAI OAuth 账号文件用于管理端账号导入,不要当作代理配置分发给客户端
### 生图和 WebSocket
模板启用原生生图。Codex 可在任务需要图片时调用 `image_gen.imagegen`,
再通过代理的 Images 接口生成或编辑图片;也可以在需求中明确要求生成并使用图片。
需要支持该能力的客户端、支持图片输入的对话模型,以及有生图权限和额度的 OpenAI 账号。
代理不会增加上游权限,xAI 账号不能承接这些 Images 请求。
代理不会增加上游权限,xAI 账号不能承接这些 Images 请求
生图不要求开启 WebSocket。要启用客户端 WebSocket,把当前 Provider 的
`supports_websockets` 改为 `true`,并检查反向代理是否允许 Upgrade。
`supports_websockets` 改为 `true`,并检查反向代理是否允许 Upgrade
客户端到代理、代理到上游是两段独立连接。客户端关闭 WebSocket 后,
服务端仍可能用 WebSocket 访问上游;客户端开关不控制服务端连接池和 HTTP 回退策略。
OpenAI OAuth 账号的上游传输方式默认 WS;固定为 SSE 的账号不承接必须依赖 WS 的预热及连接内续接。
仅使用这类账号时,客户端保持 `supports_websockets = false`,避免先尝试 WS 再回退。
仅使用这类账号时,客户端保持 `supports_websockets = false`,避免先尝试 WS 再回退
### 客户端配置兼容
仅含代理密钥的 `auth.json` 配合 `requires_openai_auth = true` 仍可用于已有请求,
但 API Key 登录本身不会启用原生生图。需要生图时换用上述 Provider 配置。
但 API Key 登录本身不会启用原生生图。需要生图时换用上述 Provider 配置
使用 Codex 0.153.4 时,旧配置中的以下字段需删除或替换:
@@ -276,81 +292,64 @@ OpenAI OAuth 账号的上游传输方式默认 WS;固定为 SSE 的账号不
| 顶层 `network_access = "enabled"` | 无有效配置定义,删除;不是客户端 API 的联网开关 |
| `features.responses_websockets_v2` | 已标记移除,使用 Provider 的 `supports_websockets` |
`image_generation` 和 `goals` 仍有效。不要为了接入代理,顺带扩大命令沙箱的联网或文件权限。
`image_generation` 和 `goals` 仍有效。不要为了接入代理,顺带扩大命令沙箱的联网或文件权限
### 登录与生图排查
- 仍提示登录:确认实际读取的用户配置目录、选中的 Provider 和客户端版本,再完全退出重启。
新 Provider 使用 `requires_openai_auth = false`,不依赖本地 ChatGPT 登录。
新 Provider 使用 `requires_openai_auth = false`,不依赖本地 ChatGPT 登录
- 没有生图工具:检查配置是否被覆盖、Actor 标记是否保留、模型是否支持图片输入。
官方客户端还会检查缓存登录状态;已核验版本在本地账号为 Free 时会隐藏生图。
先备份并区分本地真实账号文件与代理密钥文件,不要直接删除全部登录状态。
- 已调用生图但失败:查看服务端账号的凭据、权限、额度和请求错误,不能只凭文本对话成功判断。
- `401`:检查代理密钥是否正确、是否启用;Actor 标记不能代替密钥。
先备份并区分本地真实账号文件与代理密钥文件,不要直接删除全部登录状态
- 已调用生图但失败:查看服务端账号的凭据、权限、额度和请求错误,不能只凭文本对话成功判断
- `401`:检查代理密钥是否正确、是否启用;Actor 标记不能代替密钥
- `426`:检查客户端版本及反向代理是否保留版本头,低于管理员设置的最低版本时需要更新客户端。
手机远程控制的版本识别范围见 [版本门禁规则](../docs/api.md#1-鉴权与公共约定)。
手机远程控制的版本识别范围见 [版本门禁规则](../docs/api.md#1-鉴权与公共约定)
- 地址包含 `5173/dev/v1`:这是开发代理地址,依赖 Vite 服务。日常使用改成后端或 HTTPS 地址;
验证 WebSocket 时直接连接后端,当前 Vite 代理未显式开启 WebSocket 转发。
验证 WebSocket 时直接连接后端,当前 Vite 代理未显式开启 WebSocket 转发
其他客户端使用 Responses API、`/v1` Base URL 和代理密钥即可,不需要 Actor 标记。
路由与请求格式见 [API 参考](../docs/api.md#3-openai-数据面与模型目录)。
路由与请求格式见 [API 参考](../docs/api.md#3-openai-数据面与模型目录)
## 优雅关停
收到停止信号后,应用先停止接收新连接并 drain 存量连接;整个 drain 共享一个从停止信号
起算的绝对截止点(`host.drain_timeout_seconds`,默认 30 秒),逾期放弃等待,存量连接随
进程退出终止。drain 结束后才关停后台 worker,预算为
`host.worker_shutdown_timeout_seconds`(默认 30 秒),两段预算按最坏情况串联。
`host.worker_shutdown_timeout_seconds`(默认 30 秒),两段预算按最坏情况串联
Compose 的 `stop_grace_period` 为 75 秒,覆盖默认 30 秒 HTTP drain、30 秒 worker 收尾和额外调度
余量。若调大任一应用超时,也必须把 `stop_grace_period` 调到大于两段超时之和;否则 Docker 会在
宽限期结束时 SIGKILL。
宽限期结束时 SIGKILL
## 本地开发
本地开发需要克隆源码仓库,以下命令从仓库根目录执行。PostgreSQL 和 Redis 可继续由 Compose 启动:
源码环境、前后端启动与组件库联调见 [开发与源码联调](../docs/development.md)
```bash
docker compose -f deploy/compose.yaml up -d postgres redis
cd backend
cargo run -p codex-proxy-rs
```
后端会从当前目录向上查找 `deploy/config.yaml`。`host.runtime_data_dir` 是运行数据的统一根目录,
相对数据和日志目录均以该配置文件所在目录解析;Compose 把监听地址和数据库、Redis 地址固定
覆盖为容器内部服务名,并把前端静态目录指向容器内构建产物。PG/Redis 集成测试所需的
`CPR_TEST_DATABASE_URL` / `CPR_TEST_REDIS_URL` 约定见
[迁移文档](../backend/migrations/README.md)。
管理端的依赖准备与 UI 源码联调见 [贡献与验证](../CONTRIBUTING.md#验证)。另开终端从仓库根目录启动前端:
```bash
pnpm --dir frontend dev
```
本机开发可使用 Compose 提供 PostgreSQL 和 Redis,集成测试另用[专用测试库](../backend/migrations/README.md#本地测试库)
## 持久化与备份
### 数据目录
Compose 使用以下绑定目录:
- `.runtime/data` → OpenAI 会话锚点密钥、更新状态、临时更新目录与备份暂存区
- `.runtime/logs` → 应用文件日志
- `.runtime/postgres` → PostgreSQL
- `.runtime/redis` → Redis AOF
| 目录 | 内容 |
| --- | --- |
| `.runtime/data` | OpenAI 会话锚点密钥、更新状态、临时更新目录与备份暂存区 |
| `.runtime/logs` | 应用文件日志、按需启用的 OAuth 恢复记录与请求转储 |
| `.runtime/postgres` | PostgreSQL 持久化数据 |
| `.runtime/redis` | Redis AOF |
普通 `docker compose down` 不会删除这些目录。删除 `.runtime` 会永久清除本地状态。
普通 `docker compose down` 不会删除这些目录。删除 `.runtime` 会永久清除本地状态
PostgreSQL 是账号、Client Key、运行设置、请求记录与审计的权威存储;账号 credential 当前按
PostgreSQL 是账号、Client Key、运行设置、请求记录与审计的权威存储;账号 credential 按
Provider schema 以明文 JSON 保存于 PostgreSQL。Redis 只保存可重建、可过期的协调状态,例如
会话亲和、lease、cooldown、OAuth pending flow 与套餐模型目录 cache。
会话亲和、lease、cooldown、OAuth pending flow 与套餐模型目录 cache
OpenAI 主动额度重置卡及其消费结果由上游持有,不写入 PostgreSQL/Redis,也不属于本地备份内容;
管理端只在当前浏览器会话中保留最近一次查询结果。
备份应覆盖哪些目录、如何生成一致性数据库归档,见 [备份与恢复](#备份与恢复)
数据库可用管理端的 S3/R2 逻辑备份,或停库后备份 `.runtime/postgres`。
不要在 PostgreSQL 写入期间直接复制数据目录作为一致性备份。要保留 OAuth 的 AT/RT 恢复记录,需保持
`host.logging.oauth_recovery: true` 并备份 `.runtime/logs`;恢复记录位于独立文件集,仍按普通日志的
`retention_days` 按完整日期保留。若希望保留短期 Redis 状态和会话锚点,也同时备份 `.runtime/redis` 与
`.runtime/data`。
### 日志与保留窗口
文件日志以时间完整性为清理依据,**没有文件数量淘汰上限**:
@@ -363,84 +362,91 @@ OpenAI 主动额度重置卡及其消费结果由上游持有,不写入 Postgr
`application`、`oauth-recovery`、`request-dump`。专用 tracing target 为 `oauth_recovery` 和
`request_dump`;普通日志保留各 Rust 模块的 target,便于按模块过滤。
程序只管理上述规范名称的日志,其他命名的文件由运维手动清理。
普通日志未配置 `retention_days` 时默认使用 7 天,显式配置优先。
普通日志未配置 `retention_days` 时默认使用 7 天,显式配置优先
按 UTC 日期整组保留:例如 9 月 8 日配置 1 天,会保留 9 月 7 日全天及 9 月 8 日的所有分片,
到 9 月 9 日才允许清理 9 月 7 日。这会略多保留,保证跨午夜及高流量时不留下半天日志。
配置 7 天同理,保留前 7 个完整 UTC 日期及当天;若旧日期分片近期又被写入,整组延后删除。
`max_file_size_mb` 仅决定分片大小(默认 20 MiB),不决定保存时长,单条大记录不会被截断。
关闭的分片压缩为 `.log.gz`;成功压缩、同步并发布归档后才删除原文件,保留原修改时间。
清理发生在启动和轮转时;空闲期间过期文件可能暂时多保留。检索时须同时读取 `.log` 与 `.log.gz`。
清理发生在启动和轮转时;空闲期间过期文件可能暂时多保留。检索时须同时读取 `.log` 与 `.log.gz`
报文开关为 `host.logging.request_dump`,默认关闭;开启时原始报文按块完整写入独立文件,
数据库请求诊断仍是有界摘要,不能用摘要事件数代替全量报文完整性。
数据库请求诊断仍是有界摘要,不能用摘要事件数代替全量报文完整性
OAuth 恢复开关为 `host.logging.oauth_recovery`,默认关闭,与普通文件日志开关分别控制:
- 开启后,OpenAI 每次成功取得 AT 和可用 RT 时,在资料补全及数据库写入前写入独立的 `oauth-recovery` 文件集
- 该通道不受普通日志级别过滤,关闭时也不会改写到普通日志或 stdout;有界队列满时等待写入
恢复记录和请求转储含原始凭据,`.runtime/logs` 须按敏感数据控制访问和备份
文件写入使用有界队列背压,正常退出会排空队列并同步文件。`file_logging` 健康探针在写入/同步失败后
报告 `Unhealthy`,本次进程内恢复写入也不会清除已有缺口;压缩或清理失败报告 `Degraded`。
断电、强杀或磁盘故障可能造成日志丢失;需要恢复已删除文件时,应使用独立备份。
容量不足时不会提前删除保留窗口内日志,必须根据完整日期的压缩后实际用量规划空间,并监控磁盘余量和
健康探针。Docker stdout 的独立轮转不承担应用文件日志的完整保留承诺。
健康探针。Docker stdout 的独立轮转不承担应用文件日志的完整保留承诺
完整运行时、Provider、revision 与恢复边界见 [架构文档](../docs/architecture.md)。
完整运行时、Provider、revision 与恢复边界见 [架构文档](../docs/architecture.md)
## 请求错误排查
1. 先记录故障时间和时区、网关版本/提交、客户端名称与版本,以及 HTTP/SSE/WebSocket 传输。
区分“上游返回”“网关实际响应”和“客户端终端展示”,不要只凭终端的统一文案推断根因。
区分“上游返回”“网关实际响应”和“客户端终端展示”,不要只凭终端的统一文案推断根因
2. 收集响应中的 `x-gateway-request-id`、`x-request-id` / `x-oai-request-id`;配置了
`api.request_id_header` 时也记录该入口头。WebSocket 合成错误的关联头位于本条错误的 `headers`。
在管理端错误列表按 ID 和时间搜索;检查平台条件并主动刷新,翻页不会推进查询时间。
只有入口 ID 时,改用入口日志和时间定位。
只有入口 ID 时,改用入口日志和时间定位
3. 打开错误详情,核对上游/客户端状态、发送状态、attempt、失败阶段及后续恢复关联。
下载默认诊断包作为反馈材料,先看 `availability`、`attemptsComplete` 和淘汰计数;
该包不含原始错误正文或完整 trace data。分享前仍应检查关联 ID 等内部信息。
该包不含原始错误正文或完整 trace data。分享前仍应检查关联 ID 等内部信息
4. 需要更细上下文时,在 `codex-proxy-rs-application.*.log` 及 `.log.gz` 中按网关/上游 ID
和时间检索,结合 `attempt.started`、`attempt.failed`、`request.finished` 与 transport 阶段判断。
**开启 `host.logging.file.enabled` 时,stdout 仅保留 `gateway_startup` 通道**;
`docker logs` 看不到业务错误不代表没有错误。关闭普通文件日志且开启 `host.logging.stdout`
时,普通日志才按级别输出到 stdout;专用 dump/OAuth 恢复通道不会混入。
时,普通日志才按级别输出到 stdout;专用 dump/OAuth 恢复通道不会混入
5. 查不到请求记录时,先用模型执行 ID 在“全部平台”下搜索:已进入执行会话的建流前失败也会记录,
但其 attempt 为零,尚未确认的 Provider/账号/传输为空,不会命中具体平台或账号筛选。
再检查观测队列丢弃/写入失败告警及 `file_logging` 健康状态。鉴权、解析、路由和准入等入口拒绝
仍不保证进入错误列表;应结合入口状态与日志,不能据此认定请求未发生。异步投影有延迟,
刷新后仍需核对缺口,而不是反复重放可能已发送的请求。
刷新后仍需核对缺口,而不是反复重放可能已发送的请求
6. 仅在上述信息不足且能够控制访问范围时临时开启 `host.logging.request_dump` 复现一次。
它会记录完整凭据与用户正文,应限定访问、摘取最小片段并人工脱敏;复现后关闭开关,
按配置的保留窗口及组织的数据处理要求管理已生成文件。不要为普通请求排查开启 OAuth 恢复记录,
也不要直接上传整个日志目录或完整转储。
也不要直接上传整个日志目录或完整转储
请求问题反馈使用 [接口问题反馈表单](../.github/ISSUE_TEMPLATE/api-bug-report.yml);错误诊断与查询合同见
[API 文档](../docs/api.md#10-dashboard用量与错误)。
[API 文档](../docs/api.md#10-dashboard用量与错误)
### 插件取文与 Fake-IP DNS
插件受管 HTTP 会检查域名解析出的全部地址,再使用通过校验的地址连接,默认拒绝内网、回环、
链路本地和保留地址。配置了代理也不会跳过目标地址校验,宿主不自动改用公共 DNS。
链路本地和保留地址。配置了代理也不会跳过目标地址校验,宿主不自动改用公共 DNS
若取文提示“目标地址被安全策略拦截”,先在**实际运行网关的主机或容器内**检查域名解析。
Clash / Mihomo 的 Fake-IP 模式可能把公网域名解析为 `198.18.0.0/15` 中的地址,此时应调整代理 DNS,
让需要访问的域名返回真实公网地址,不要关闭宿主检查或放行整个保留地址段。
让需要访问的域名返回真实公网地址,不要关闭宿主检查或放行整个保留地址段
例如 Mihomo 使用 `fake-ip-filter-mode: blacklist` 时,将 `github.com` 追加到现有
`dns.fake-ip-filter` 列表,保留原条目及其他 DNS、代理规则。Clash Verge Rev 开启 DNS 覆写时,
应在 DNS 设置中修改持久配置,而不是只编辑会被重新生成的运行配置。规则模式和白名单模式含义不同,
按所用版本的 [Mihomo DNS 文档](https://wiki.metacubex.one/config/dns/)配置。
重新加载后确认解析结果已改变,再验证实际取文;浏览器或普通 `curl` 可访问不能代替宿主校验。
重新加载后确认解析结果已改变,再验证实际取文;浏览器或普通 `curl` 可访问不能代替宿主校验
宿主日志中的“插件受管 HTTP 请求失败”只记录固定原因、错误类别和发送状态,不包含请求网址、
请求头、正文或凭据。域名解析失败、网络超时、连接失败和地址拒绝应分别处理,不需要开启请求转储。
请求头、正文或凭据。域名解析失败、网络超时、连接失败和地址拒绝应分别处理,不需要开启请求转储
## 密码语义
- `admin.default_password` 只在首次创建管理员时使用。
- 已有管理员在「系统设置 → 安全与访问 → 管理员密码」修改登录密码,需要验证当前密码;修改后所有管理员会话失效,使用新密码重新登录。
- PostgreSQL 官方镜像只在空数据目录初始化时使用 `database.password`。
- Redis 在每次容器创建时使用 `redis.password`。
- `admin.default_password` 只在首次创建管理员时使用
- 已有管理员在「系统设置 → 安全与访问 → 管理员密码」修改登录密码,需要验证当前密码;修改后所有管理员会话失效,使用新密码重新登录
- PostgreSQL 官方镜像只在空数据目录初始化时使用 `database.password`
- Redis 在每次容器创建时使用 `redis.password`
已有 PostgreSQL 数据目录后,直接修改 `database.password` 不会修改数据库用户密码,只会导致
应用无法连接。轮换时必须先在 PostgreSQL 中修改用户密码,再同步更新 `config.yaml`。Redis
密码变更后需要用新配置重新创建 Redis 和应用容器,不需要删除 Redis 数据目录。
安排维护窗口,避免应用和 Redis 在过渡期间使用不同密码。
安排维护窗口,避免应用和 Redis 在过渡期间使用不同密码
## 镜像升级与源码构建
@@ -449,7 +455,7 @@ Clash / Mihomo 的 Fake-IP 模式可能把公网域名解析为 `198.18.0.0/15`
使用二进制归档手动部署时,将模板中的 `api.asset_directory` 改为 `../web/dist`,指向归档内的静态资源。
在线更新默认使用同一目录;如显式设置 `host.system_update.web_dist_dir`,应确保它指向实际提供页面的目录。
升级时先阅读目标版本说明,下载同一 Release 的部署附件,对比模板并合并必要配置,保留已有凭据
和 Compose 自定义项。不要用模板覆盖 `config.yaml`,也不要从 `main` 下载模板搭配旧镜像。
和 Compose 自定义项。不要用模板覆盖 `config.yaml`,也不要从 `main` 下载模板搭配旧镜像
按现有配置和接入方式检查以下升级条件:
@@ -466,14 +472,14 @@ docker compose -f deploy/compose.yaml pull codex-proxy-rs
docker compose -f deploy/compose.yaml up -d --no-build --wait codex-proxy-rs
```
源码构建需要克隆源码仓库并准备配置与数据目录。UI 组件库从锁定的 GitHub 提交下载,并在前端依赖安装阶段自动构建,无需准备同级源码仓库或额外 build context。以下命令从仓库根目录执行:
源码构建需要克隆源码仓库并准备配置与数据目录,构建过程自动准备前端依赖,无需初始化子模块或准备额外 build context。以下命令从仓库根目录执行:
```bash
docker compose -f deploy/compose.yaml build codex-proxy-rs
docker compose -f deploy/compose.yaml up -d --no-build --wait
```
升级后通过管理端版本接口或容器 image digest 确认运行实例的版本和 revision。
升级后通过管理端版本接口或容器 image digest 确认运行实例的版本和 revision
构建元数据通过一次性进程环境传入:
@@ -488,7 +494,7 @@ docker compose -f deploy/compose.yaml build codex-proxy-rs
管理端「系统更新」可临时选择通道检查更新,不保存选择,不会自动下载或安装。
每次打开弹窗按当前运行版本匹配通道:正式版为 Stable,预发行按版本后缀匹配。
升级并重启后按新运行版本匹配;侧栏更新提示始终使用当前运行通道。预发行编号 `N` 从 1 开始递增。
升级并重启后按新运行版本匹配;侧栏更新提示始终使用当前运行通道。预发行编号 `N` 从 1 开始递增
| 通道 | 命名示例 | 接收的版本 |
| --- | --- | --- |
@@ -500,31 +506,31 @@ docker compose -f deploy/compose.yaml build codex-proxy-rs
普通通道在同一大版本内持续接收更高版本,包括后续小版本和补丁版本的预发行。
切回 Stable 不会降级:例如运行 `3.16.0-beta.2` 时,不能安装 `3.15.1`,需要等待 `3.16.0` 或更高正式版。
下载、安装或待重启期间不能切换通道。
下载、安装或待重启期间不能切换通道
同一 `X.Y.Z-exp.N` 系列只用于同一轮实验,不能混用不同实验分支;变更基线即进入另一实验线,需手动迁移。
exp 不进入正式版、alpha、beta 或 rc,普通实例也不能选择 Exp。
从 exp 切换正式版时需先备份,在目标版本的独立数据库中迁移业务数据,不能直接复用或整库还原实验数据库。
从 exp 切换正式版时需先备份,在目标版本的独立数据库中迁移业务数据,不能直接复用或整库还原实验数据库
版本大小遵循 [SemVer](https://semver.org/),构建元数据 `+...` 不参与比较。未知预发行标识不提供在线更新。
更新器先过滤不允许的目标,再选择版本最高的候选;其他通道或更高大版本领先,不影响当前发行线的更新。
只有当前官方构建满足在线更新条件且存在允许安装的新版本时,才返回“有可用更新”。否则不显示更新标记、
其他通道的版本及发布说明,手动检查显示“当前没有可用更新”。检查失败单独报告,不能当作没有更新。
其他通道的版本及发布说明,手动检查显示“当前没有可用更新”。检查失败单独报告,不能当作没有更新
alpha、beta、rc、exp 在 GitHub 标记为 Pre-release,不覆盖 GitHub Latest 或镜像 `latest`。
允许连续升级的发行线从首次发布起遵守[迁移冻结规则](../backend/migrations/README.md#冻结规则),
包括预发行到正式版的晋级;发版前验证对应升级路径,不能仅凭版本号认定数据库兼容。
包括预发行到正式版的晋级;发版前验证对应升级路径,不能仅凭版本号认定数据库兼容
### 管理端在线更新
官方构建按上述规则在线更新;源码等非官方构建返回不支持原因,不提供可用更新。
点击「下载并更新」后在原有更新日志中显示下载、校验与安装过程,完成后显示「待重启」及待生效版本,重启后才使用新程序。
「当前版本」始终表示正在运行的版本,「通道最新」表示所选通道的检查结果。
点击「下载并更新」后,操作日志显示下载、校验与安装过程,完成后显示「待重启」及待生效版本,重启后才使用新程序。
「当前版本」始终表示正在运行的版本,「通道最新」表示所选通道的检查结果
手动更换镜像或程序后,更新器会核对安装文件并校准历史状态,不以旧成功记录推断需要重启。
运行期间检测到外部文件变动或文件缺失时会报告异常,需要核对完整发行包并重启服务后再检查。
回滚入口只接受与记录指纹一致的完整备份;旧格式记录没有指纹,或备份被替换、丢失时,不再提供在线回滚,
但不会删除备份文件。此时需要按人工部署流程恢复已经核实的发行包。
回滚入口只接受与记录指纹一致的完整备份;缺少指纹,或备份被替换、丢失时,不提供在线回滚,
但不会删除备份文件。此时需要按人工部署流程恢复已经核实的发行包
直接运行二进制时,若未设置 `CPR_ENABLE_SELF_RESTART=true`,管理端自重启默认关闭。
在现有 `deploy/config.yaml` 的 `host` 下合并以下配置:
@@ -541,20 +547,20 @@ host:
显式配置时,将示例改为实际程序的绝对路径。`self_restart_enabled` 控制管理端的「立即重启」操作;
修改配置后需手动重启当前服务一次才会生效。
以上三个字段也可分别通过 `CPR_DEPLOYMENT_MODE`、`CPR_ENABLE_SELF_RESTART` 和 `CPR_UPDATE_EXE_PATH` 设置,
YAML 中显式配置的值优先于对应环境变量。
YAML 中显式配置的值优先于对应环境变量
Compose 提供以下在线更新运行参数:
- `CPR_UPDATE_REPOSITORY`:只接受 `owner/repository`;默认 `zyycn/codex-proxy-rs`。
- `CPR_GITHUB_API_BASE`:正式环境必须为 `https://api.github.com/repos`。
- `CPR_UPDATE_REPOSITORY`:只接受 `owner/repository`;默认 `zyycn/codex-proxy-rs`
- `CPR_GITHUB_API_BASE`:正式环境必须为 `https://api.github.com/repos`
- `CPR_UPDATE_EXE_PATH`、`CPR_WEB_DIST_DIR`:分别指向容器内二进制和前端静态目录;
`CPR_WEB_DIST_DIR` 同时供页面服务与更新器使用,相对路径以 `deploy/config.yaml` 所在目录为基准。
`CPR_WEB_DIST_DIR` 同时供页面服务与更新器使用,相对路径以 `deploy/config.yaml` 所在目录为基准
- 更新临时目录、状态文件和锁文件默认由 `host.runtime_data_dir` 派生;
`CPR_UPDATE_TEMP_DIR`、`CPR_UPDATE_STATE_FILE`、`CPR_UPDATE_LOCK_FILE` 仅用于显式覆盖。
`CPR_UPDATE_TEMP_DIR`、`CPR_UPDATE_STATE_FILE`、`CPR_UPDATE_LOCK_FILE` 仅用于显式覆盖
- `CPR_ENABLE_SELF_RESTART=true`:更新或回滚完成后允许管理端请求重启;Docker 进程退出后由
Compose 的 `restart: unless-stopped` 拉起新进程。
Compose 的 `restart: unless-stopped` 拉起新进程
旧配置中的 `CPR_UPDATE_CHANNEL` 不再参与版本选择,可移除。
`CPR_UPDATE_CHANNEL` 不参与版本选择,配置中存在该变量时可删除
Release 必须提供当前 OS/架构的 `codex-proxy-rs_<version>_<os>_<arch>.tar.gz` 与
`checksums.txt`。服务会在替换前再次查询远端最新版本,校验下载 host、声明大小、SHA-256 和
@@ -570,7 +576,7 @@ Release 必须提供当前 OS/架构的 `codex-proxy-rs_<version>_<os>_<arch>.ta
### 插件兼容与发行目录
教学示例在 `codex-proxy-plugins` 独立构建和发布,通过[插件管理](../docs/plugins.md)安装。
宿主发行物当前不内置插件,但仍包含用于兼容检查的封口清单。
宿主发行物当前不内置插件,但仍包含用于兼容检查的封口清单
| 文件或目录 | 用途 |
| --- | --- |
@@ -581,11 +587,27 @@ Release 必须提供当前 OS/架构的 `codex-proxy-rs_<version>_<os>_<arch>.ta
- 非空官方清单中的包经身份、平台和摘要校验后幂等导入;导入不接受访问域、不创建配置、不启用,也不删除旧包。管理员确认访问域后才执行默认配置流程;重复摘要保留首次安装出处
- `sealed` 是构建封口标记,不是密码学签名。普通上传、URL 或 GitHub 安装不能获得 `builtin` 身份,独立教学示例也不例外
首次从不支持官方目录的旧更新器升级时,须完整解压平台发行包或更新正式容器镜像;旧更新器不会复制新增目录。
后续在线更新会成套处理该目录。macOS arm64 提供构建产物;部署前仍需验证目标平台的插件运行与上游调用,不能只以打包成功判断可用。
使用不支持 `plugins/official` 目录的更新器时,须完整解压平台发行包或更新正式容器镜像,确保发行文件齐全。
macOS arm64 提供构建产物;部署前需验证目标平台的插件运行与上游调用,不能只以打包成功判断可用
## 备份与恢复
数据库可用管理端的 S3/R2 逻辑备份,或停止 PostgreSQL 后备份数据目录
不要在 PostgreSQL 写入期间直接复制数据目录作为一致性备份
| 需要保留的内容 | 备份方式 |
| --- | --- |
| 账号、Key、设置、请求、审计与插件数据 | 完整数据库归档,或停库后备份 `.runtime/postgres` |
| 部署配置与凭据 | 单独备份 `deploy/config.yaml` 及 Compose 自定义配置 |
| 应用日志与已启用的 OAuth 恢复记录 | 单独备份 `.runtime/logs`,不包含在数据库归档中 |
| 会话锚点与节点运行文件 | 按需备份 `.runtime/data` |
| 短期协调状态 | 按需备份 `.runtime/redis`,数据库冷恢复时使用空 Redis |
OpenAI 主动额度重置卡及消费结果由上游持有,不属于本地备份内容
数据库、配置、插件敏感设置和 OAuth 恢复日志均按凭据保护
### 备份内容与限制
- 插件包体、固定来源、下载凭据、制品接受事实、实例配置、版本配置快照、敏感配置、功能范围绑定和私有状态都保存在
@@ -594,24 +616,16 @@ Release 必须提供当前 OS/架构的 `codex-proxy-rs_<version>_<os>_<arch>.ta
不依赖进程代理环境,故障时不会自动回退直连。
包体单个上限为 32 MiB,需要计入数据库及备份容量;解压运行目录和 Redis 协调状态是可重建数据,
不作为插件安装或访问域事实。恢复时保留制品接受状态、实例配置、账号引用和状态代次的一致性;插件降级仍须
通过状态兼容校验,不通过手工替换缓存目录回滚。
通过状态兼容校验,不通过手工替换缓存目录回滚
- 官方运行镜像内置与 Compose 数据库服务版本一致的 `pg_dump` / `pg_restore`,无需额外安装。
自行替换 PostgreSQL 版本时,应同步核对备份工具版本。
自行替换 PostgreSQL 版本时,应同步核对备份工具版本
- 备份暂存目录为 `host.runtime_data_dir/backup-staging`;Compose 默认对应
`/app/.runtime/data/backup-staging`,由 `.runtime/data` 卷持久化,权限 `0700`(仅 `cpr`
用户可读写)。部署卷至少预留一个最大数据库归档的空间。
- OAuth 恢复记录通道使用 `oauth_recovery` 结构化日志事件;当前 OpenAI Provider 每次成功取得
AT(以及存在时的 RT)后,都会在账号资料补全、过期时间计算和数据库写入前写入独立的
`codex-proxy-rs-oauth-recovery.YYYY-MM-DD[.N].log` 文件集。事件含原始 AT/RT,并以 `provider`
字段标记来源,与普通日志一样按日、按大小分割,按 `retention_days` 保留完整日期。
`host.logging.oauth_recovery` 默认关闭,显式开启后才写独立文件,与普通文件日志开关分别控制。
开启时不会被普通日志级别筛掉;关闭时不会写入普通文件或 stdout,即使 `RUST_LOG` 提高此 target 的级别。
有界队列满时会等待写入,避免拥堵时静默丢记录。
`.runtime/logs` 因此属于敏感数据,必须按现有普通日志的访问控制和加密备份策略处理。
用户可读写)。部署卷至少预留一个最大数据库归档的空间
- S3/R2 存储、Cron 计划、保留策略与备份记录都保存在 PostgreSQL(`backup_settings` /
`backup_records`),备份记录行在删除成功后硬删除,操作历史进入 `admin_audit_events`。
`backup_records`),备份记录行在删除成功后硬删除,操作历史进入 `admin_audit_events`
- 手工备份的 `expiresInDays` 在创建时生成独立 `expires_at`;计划备份按当前 `retentionDays` 生成
`expires_at`,并同时受 `retentionCount` 清理规则约束。到期只进入删除流程,不构成在线恢复点。
`expires_at`,并同时受 `retentionCount` 清理规则约束。到期只进入删除流程,不构成在线恢复点
### 人工恢复数据库
@@ -623,18 +637,18 @@ pg_restore --no-owner --no-privileges --password \
--dbname='postgresql://restore_user@127.0.0.1:5432/restore_db' backup.dump
```
示例中的账号、空目标库和归档路径需按实际情况替换,密码由命令交互输入。
示例中的账号、空目标库和归档路径需按实际情况替换,密码由命令交互输入
恢复期间与恢复后的边界见 [架构文档](../docs/architecture.md#11-生命周期安全与恢复)。
当前没有在线恢复 API,也没有供部署者直接启用的“维护模式”开关:
1. 先备份现有数据库,并停止应用;在独立的空数据库中恢复归档,检查数据和迁移版本。
1. 先备份现有数据库,并停止应用;在独立的空数据库中恢复归档,检查数据和迁移版本
2. 应用保持停止,离线核对备份设置与记录。禁用快照中的旧计划,处理非终态记录
(`queued/dumping/uploading/deleting`)、旧调度游标和到期清理条件,再决定哪些记录保留。
(`queued/dumping/uploading/deleting`)、旧调度游标和到期清理条件,再决定哪些记录保留
3. 使用空 Redis 和空插件解包缓存做一次冷启动;逐一核对已启用实例的包摘要/平台、制品接受状态、配置与绑定、账号引用、
私有状态 schema/代次,并执行实际插件操作。缺少或损坏必需包体、引用不完整或状态不兼容时,恢复不能
记为成功,也不能用相同插件 ID 的其他包替代。
4. 确认不会误执行旧任务或删除恢复前的远端对象后,再接入业务流量;重新测试 S3 连接并设置计划。
记为成功,也不能用相同插件 ID 的其他包替代
4. 确认不会误执行旧任务或删除恢复前的远端对象后,再接入业务流量;重新测试 S3 连接并设置计划
只关闭计划备份不会停止 Worker 的任务恢复和到期删除。无法确认这些记录的影响时,
不要把恢复后的数据库直接接入运行中的应用。具体处理应根据目标库数据制定,不提供清空生产记录的通用命令。
不要把恢复后的数据库直接接入运行中的应用。具体处理应根据目标库数据制定,不提供清空生产记录的通用命令
+306 -296
View File
File diff suppressed because it is too large Load Diff
+199 -179
View File
@@ -2,7 +2,7 @@
本文说明模块职责、请求流程、状态存储和必须保持的约束。
具体 HTTP 字段见 [接口文档](api.md),部署参数见 [部署文档](../deploy/README.md);上游 URL、超时、
重试间隔和 UI 布局属于源码或配置,不在架构文档重复维护。
重试间隔和 UI 布局属于源码或配置,不在架构文档重复维护
| 阅读目标 | 章节 |
| --- | --- |
@@ -15,15 +15,15 @@
Codex Proxy RS 是单进程、单副本运行的多 Provider AI 网关,同时提供:
- 面向客户端的 OpenAI Responses、Images、standalone Search 和模型目录协议;
- 面向管理员的 `/api/admin/*` 控制面和 Vue 管理端;
- 面向 Key 持有者的 `/api/key-usage/*` 用量与客户端配置接口、独立 `/key-usage` 页面,以及复用数据面认证的 `/v1/usage` 额度查询;
- 固定的 OpenAI 与 xAI 两个编译期 Provider,以及插件提供的认证、中间件和管理扩展;
- PostgreSQL 持久化、Redis 协调状态以及 S3/R2 数据库备份。
- 面向客户端的 OpenAI Responses、Images、standalone Search 和模型目录协议
- 面向管理员的 `/api/admin/*` 控制面和 Vue 管理端
- 面向 Key 持有者的 `/api/key-usage/*` 用量与客户端配置接口、独立 `/key-usage` 页面,以及复用数据面认证的 `/v1/usage` 额度查询
- 固定的 OpenAI 与 xAI 两个编译期 Provider,以及插件提供的认证、中间件和管理扩展
- PostgreSQL 持久化、Redis 协调状态以及 S3/R2 数据库备份
系统不提供 `/v1/chat/completions`,不存在 Provider Instance 层,也不支持通过复制应用容器进行多副本
扩容。Client Key 限定账号分组,而不是绑定某个 Provider;一次请求的 Provider 候选由账号范围、模型
能力和运行时健康共同决定。
能力和运行时健康共同决定
## 2. 运行拓扑
@@ -49,7 +49,7 @@ flowchart TB
图中表示运行时协作,不是 crate 的直接依赖。`backend/apps/gateway` 是网关的唯一组合根,
按 Host → Store → Provider → Core → Admin → API → Worker 的顺序
初始化具体实现。其余 crate 只暴露自己的配置、端口和 Bundle,不自行定位别的实现。
初始化具体实现。其余 crate 只暴露自己的配置、端口和 Bundle,不自行定位别的实现
## 3. Workspace 边界
@@ -73,23 +73,23 @@ flowchart TB
依赖方向遵守四条规则:
1. Core 不依赖 HTTP、数据库、Redis 或具体 Provider。
2. Provider 之间不互相依赖,也不决定跨 Provider fallback。
3. API 只调用 Core/Admin 抽象;Store 只实现端口。
4. 具体实现只在组合根相遇。
1. Core 不依赖 HTTP、数据库、Redis 或具体 Provider
2. Provider 之间不互相依赖,也不决定跨 Provider fallback
3. API 只调用 Core/Admin 抽象;Store 只实现端口
4. 具体实现只在组合根相遇
`frontend/` 是独立的 Node 项目,自行管理依赖、pnpm 配置、锁文件和 ESLint;仓库根目录不建立前端 workspace。
管理端与独立示例仓库分别依赖 `@codex-proxy/ui` 的固定 GitHub 标签或提交,通过锁文件固定实际提交并校验源码归档完整性,不要求同级源码目录。安装与构建许可见 [贡献与审查](../CONTRIBUTING.md#验证)。
管理端与独立示例仓库分别依赖 `@codex-proxy/ui` 的固定 GitHub 标签或提交,通过锁文件固定实际提交并校验源码归档完整性,不要求同级源码目录。安装与构建许可见 [开发环境](development.md#环境与依赖)。
`modules/ui` 与 `modules/plugins` 是可选的 Git 子模块,分别指向两个独立仓库;不加入宿主的 Cargo 或 pnpm workspace,不参与宿主发行构建。源码联调和版本指针维护见 [开发指南](development.md)。
UI 包只公开组件、主题与样式入口;Pinia 持久化、登录、路由和管理请求仍由各自应用持有。
插件页面把所需 UI、Vue 和 Tailwind CSS 4 样式编译进包内静态资源,通过隔离页面与受限消息桥使用自己的管理接口,
不在运行时借用宿主 Vue 实例或内部模块。组件与主题扩展方式见 [管理端主题](theme.md)。
不在运行时借用宿主 Vue 实例或内部模块。组件与主题扩展方式见 [管理端主题](theme.md)
### 3.1 插件扩展
`gateway-plugin/` 只是目录分组,包含独立 SDK 和宿主 Runtime,不存在聚合 crate。
SDK 定义双方通信合同;Runtime 把插件能力接入既有 Core / Admin 端口,不另建请求引擎或持久化体系。
当前合同为 SDK `0.1.0`、清单 `manifestVersion: 1`、进程协议 `1`;宿主只准备通过当前合同与兼容范围校验的制品。
宿主只准备通过 [SDK 合同](../backend/crates/gateway-plugin/sdk/README.md)与兼容范围校验的制品
下图表示运行时协作,不表示 crate 直接依赖;具体实现由组合根注入:
@@ -118,63 +118,60 @@ flowchart LR
#### 发布与执行边界
- **模型目录与路由共用事实。** `model_catalog` 的直接别名保存在发布代次引用中,Core 校验原生模型、静态映射与插件 ID 冲突及目标存在性后发布。
公开列表、详情、原生目录和受管模型查询复用相同别名与账号访问范围,不另维护插件专用目录。
- **重试由 Core 裁决。** `retry_policy` 只能停止或继续 Core 已允许的恢复路径;绑定按确定顺序委托,故障回退宿主。
发送、交付、续接、外部副作用、预算和取消限制不进入插件控制面,策略返回后继续复核。
- **转换声明不替代正文事实。** middleware v2 在 request 阶段声明具体承担的功能和额外上游需求,原始需求保留用于诊断,实际正文与未承担的原始功能仍参与选路。
attempt 阶段拒绝需求声明。没有改写的正文、header 和响应帧在相同网关边界保留原字节与顺序。
- **模型目录与路由共用事实**:`model_catalog` 的直接别名保存在发布代次引用中,Core 校验原生模型、静态映射与插件 ID 冲突及目标存在性后发布。
公开列表、详情、原生目录和受管模型查询复用相同别名与账号访问范围,不另维护插件专用目录
- **重试由 Core 裁决**:`retry_policy` 只能停止或继续 Core 已允许的恢复路径;绑定按确定顺序委托,故障回退宿主。
发送、交付、续接、外部副作用、预算和取消限制不进入插件控制面,策略返回后继续复核
- **转换声明不替代正文事实**:middleware v2 在 request 阶段声明具体承担的功能和额外上游需求,原始需求保留用于诊断,实际正文与未承担的原始功能仍参与选路。
attempt 阶段拒绝需求声明。没有改写的正文、header 和响应帧在相同网关边界保留原字节与顺序
- **制品接受固定访问域。** 只读校验不持久化;上传、URL 和 GitHub 的确认安装接受精确摘要声明的访问域,
- **制品接受固定访问域**:只读校验不持久化;上传、URL 和 GitHub 的确认安装接受精确摘要声明的访问域,
官方发行导入仍须管理员确认。若插件尚无实例,Admin 以稳定 ID 合并 schema 默认值与适用贡献项 binding:
配置完整则启用,缺少必填项则停用待补;已有实例时不复制配置或切换版本。实例不能增删制品访问域。
- **一份发布快照。** 数据面和管理页面使用同一不可变扩展集合;CLI 复用准备与调用合同,按次读取持久化配置。
新配置准备成功后才提交发布,在途请求持有旧集合直到结束;管理目标和回调另按当前身份与版本复验。
- **插件故障按实例隔离。** 恢复时将启动失败的实例及其错误保留在发布集合中,正常实例和原生能力继续发布;
配置完整则启用,缺少必填项则停用待补;已有实例时不复制配置或切换版本。实例不能增删制品访问域
- **一份发布快照**:数据面和管理页面使用同一不可变扩展集合;CLI 复用准备与调用合同,按次读取持久化配置。
新配置准备成功后才提交发布,在途请求持有旧集合直到结束;管理目标和回调另按当前身份与版本复验
- **插件故障按实例隔离**:恢复时将启动失败的实例及其错误保留在发布集合中,正常实例和原生能力继续发布;
用户正在修改的实例仍须准备成功才能提交。集合区分进程就绪、可继续服务与需要后台重建,单个进程退出不撤销全部请求快照。
故障绑定保留原有作用范围和拒绝/委托策略,入口认证故障不降级为其他认证方式;宿主配置存储与 revision 无法确认时仍停止新请求。
- **宿主持有业务事实。** 插件返回能力结果,Core 继续决定账号资格、发送状态、标准用量与费用;账号变更由 Admin / Store
执行版本校验、事务和审计。Provider 由组合根静态注册为 OpenAI 与 xAI;插件停用不删除账号或历史记录,失败的观察回调不改变响应和账单。
- **能力开放不承接插件业务。** 宿主向插件提供独立、可组合的受控原生能力,负责授权、资源隔离、事务与审计。
插件自身的触发条件、跨资源业务关联、策略和流程编排归插件所有;宿主不能因某个插件的使用场景代为实现这些流程。
- **数据加工走中间件。** `request` 包裹逻辑请求,`attempt` 包裹每次已选号的执行;请求顺序进入、响应逆序返回。
故障绑定保留原有作用范围和拒绝/委托策略,入口认证故障不降级为其他认证方式;宿主配置存储与 revision 无法确认时仍停止新请求
- **宿主持有业务事实**:插件返回能力结果,Core 继续决定账号资格、发送状态、标准用量与费用;账号变更由 Admin / Store
执行版本校验、事务和审计。Provider 由组合根静态注册为 OpenAI 与 xAI;插件停用不删除账号或历史记录,失败的观察回调不改变响应和账单
- **能力开放不承接插件业务**:宿主向插件提供独立、可组合的受控原生能力,负责授权、资源隔离、事务与审计。
插件自身的触发条件、跨资源业务关联、策略和流程编排归插件所有;宿主不能因某个插件的使用场景代为实现这些流程
- **数据加工走中间件**:`request` 包裹逻辑请求,`attempt` 包裹每次已选号的执行;请求顺序进入、响应逆序返回。
正文按需读取,透传无需额外读取权限;转换和改写不能绕过 Core 的交付、取消与结算边界。
数据面插件通过受管 HTTP 已发送或无法证明未发送时,Runtime 把该事实并入 Core 的请求副作用水位,后续不能按
Provider 的 `not_sent` 结果透明重放。
- **访问域不跨调用。** `network`、`models`、`accounts`、`data`、`requests`、`public_endpoints`、`groups`、`keys`、`key_budgets`、`quota_observations` 只开放对应资源域;
账号、HTTP、模型、私有状态和日志回调仍绑定有效父调用与阶段。管理页和 CLI 不预绑定 Client Key,模型调用按次
Provider 的 `not_sent` 结果透明重放
- **访问域不跨调用**:账号、HTTP、模型、私有状态和日志回调绑定有效父调用与阶段。管理页和 CLI 不预绑定 Client Key,模型调用按次
选择当前 Key;页面 Responses 桥还复核精确实例目标和 `models` 域,并在等待与交付期间持续撤销检查。
`frontend_authentication` 必须显式配置 principal 到 Key 的映射;公开登录回调使用一次性票据且不继承宿主回调权限。
`data` 仅向管理、命令和维护阶段提供账号、Key 基础投影与已有额度观测,不提供凭据、预测或 SQL,不进入客户端请求链。
`key_budgets` 在这三个阶段管理所有 Client Key 的预算查询、日/周金额上限和用量重置,不授予其他 Key 配置、密钥或模型执行权限。
`quota_observations` 在这三个阶段读取及刷新账号额度观测,复用 Provider 管理路径;不暴露凭据或执行上游额度重置。
两个域分别作用于本地预算和上游观测,不建立账号到 Key 的自动同步关系;`data` 仍仅提供已有事实。
- **维护只作用于已发布实例。** `maintenance` 由 Host 监督的 Runtime worker 调用,启用、恢复、配置发布和周期补偿共用对账入口。
数据查询、本地预算和上游额度观测分别授权,不建立账号到 Key 的自动同步关系,不承接插件的预测或业务关联。
各域的字段、权限与开放阶段由 [SDK 访问域](../backend/crates/gateway-plugin/sdk/docs/capabilities.md#访问域)定义
- **维护只作用于已发布实例**:`maintenance` 由 Host 监督的 Runtime worker 调用,启用、恢复、配置发布和周期补偿共用对账入口。
发布通知只用于唤醒,事实始终来自当前快照与数据库;同实例串行,通知合并,停用与版本替换取消旧任务。
自有分组和 Key 按实例、资源类型与稳定资源键登记;Admin 组织用例,Store 在同一事务复核当前启用 revision、
制品授权与归属,并复用原生资源写入、审计和配置发布。无变化不递增 revision;插件不能接管管理员资源,
成员变更不覆盖账号的其他分组。停用保留资源;删除实例只解除归属。
- **版本切换保持数据一致。** 同一插件最多启用一个实例;每个实例与制品摘要保存最近启用时提交的配置、密钥和绑定,
成员变更不覆盖账号的其他分组。停用保留资源;删除实例只解除归属
- **版本切换保持数据一致**:同一插件最多启用一个实例;每个实例与制品摘要保存最近启用时提交的配置、密钥和绑定,
快照与实例修改共享事务,停用草稿不覆盖恢复点。版本切换优先恢复目标快照,否则合并缺失默认值并保留显式设置。
私有状态按实例、schema 与配置版本隔离,写入使用精确记录版本。
不兼容升级须停用、排空并完成迁移,再原子提交配置与状态;失败不能覆盖并发修改。回滚也须通过兼容检查。
不兼容升级须停用、排空并完成迁移,再原子提交配置与状态;失败不能覆盖并发修改。回滚也须通过兼容检查
#### 信任与资源边界
插件以与宿主相同的 OS 身份运行;接受制品访问域是管理员确认,不是操作系统沙箱。
进程、RPC、下载、缓存与回调均有容量和期限限制;Host 提供受管资源,Runtime 负责协议、访问域与调用上下文校验。
下载来源、凭据与代理按目标匹配,网络操作不占用数据库事务,提交前重新校验来源与代理版本。
下载来源、凭据与代理按目标匹配,网络操作不占用数据库事务,提交前重新校验来源与代理版本
管理页面使用无同源权限的 sandbox iframe,通过固定目标的宿主桥访问已声明路由;
UI、Vue 与样式随插件打包,不读取宿主内部模块。
官方插件身份仅来自受信宿主发行物内的封口清单,普通安装不能指定 `builtin`;封口标记不是密码学签名。
宿主更新与回滚必须通过已启用插件的兼容检查,不能自动接受制品或停用实例来绕过检查。
宿主更新与回滚必须通过已启用插件的兼容检查,不能自动接受制品或停用实例来绕过检查
安装到使用见 [插件使用](plugins.md),清单与能力合同见 [SDK](../backend/crates/gateway-plugin/sdk/README.md),
HTTP 字段见 [插件 API](api.md#12-插件管理),更新与数据恢复见 [部署说明](../deploy/README.md#插件兼容与发行目录)。
HTTP 字段见 [插件 API](api.md#12-插件管理),更新与数据恢复见 [部署说明](../deploy/README.md#插件兼容与发行目录)
组合根的 workspace architecture tests 冻结成员清单、依赖 DAG、公开模块面、源码纪律以及生产/测试模块
镜像关系;具体行为边界由各 crate 的集成测试维护。
镜像关系;具体行为边界由各 crate 的集成测试维护
### 3.2 Rust 模块组织约定
@@ -182,21 +179,21 @@ HTTP 字段见 [插件 API](api.md#12-插件管理),更新与数据恢复见 [
测试模块树:
1. crate root 作为顶层门面,声明模块并暴露该 crate 的配置、Bundle、端口或稳定领域合同;目录节点使用
`目录/mod.rs`,叶子模块使用 `名称.rs`,不混用 `名称.rs` 与 `名称/` 两套入口。
`目录/mod.rs`,叶子模块使用 `名称.rs`,不混用 `名称.rs` 与 `名称/` 两套入口
2. 生产源码不使用内联 `mod name { ... }`、`#[path]` 或 `include!` 隐藏文件归属。`lib.rs` 和各级
`mod.rs` 是模块门面,负责声明子模块、选择性 re-export 与少量同层协调。
`mod.rs` 是模块门面,负责声明子模块、选择性 re-export 与少量同层协调
3. 子模块默认私有;只有真实的跨 crate 生产合同才能使用 `pub`。adapter/provider 的公开根模块由
allowlist 冻结,变更必须同步完成边界审计,不能为测试或兼容路径创建第二 owner。
allowlist 冻结,变更必须同步完成边界审计,不能为测试或兼容路径创建第二 owner
4. owner 级依赖保持单向。两个模块互相引用时,应把共享值对象、端口或生命周期能力提升到共同 owner,
不能依靠同一 crate 内可见性掩盖循环边界。
不能依靠同一 crate 内可见性掩盖循环边界
5. 每个 crate 的 `src/` 与 `tests/` 平级,生产源码不承载测试。`tests/` 镜像 `src/` 的模块目录形态:例如
`src/foo/mod.rs` 对应 `tests/foo/mod.rs`,`src/foo/bar.rs` 对应 `tests/foo/bar.rs`。一个生产模块可以没有
测试;额外场景测试必须放在最近的生产 owner 目录下。根级 `support` 及其辅助模块和冻结的 crate/workspace 架构场景
是明确例外。
是明确例外
较大的模块门面只负责组合和 re-export:账号 Admin HTTP 边界按 `wire`、`credentials`、`handlers`、
`presenter` 划分;Provider 执行按 continuation、stream、failure、observation 和 worker 等职责拆分;xAI
请求转换按 response、tools 与 history 拆分。各子模块之间只使用 owner 内最小可见性。
请求转换按 response、tools 与 history 拆分。各子模块之间只使用 owner 内最小可见性
### 3.3 `gateway-core` 内部 owner
@@ -216,36 +213,36 @@ HTTP 字段见 [插件 API](api.md#12-插件管理),更新与数据恢复见 [
`event` 通过 `validation` / `upstream` 使用基础值,不依赖承载原始事件的执行错误;账号值对象和
选择策略通过 `identity` / `account::scope` 使用身份与范围,不依赖路由计划。`routing` 和 `error` 的
相关公开类型通过 re-export 引用上述定义,类型所有权和 Core 内部依赖归属定义模块。架构测试约束这些叶子依赖。
相关公开类型通过 re-export 引用上述定义,类型所有权和 Core 内部依赖归属定义模块。架构测试约束这些叶子依赖
Provider 模型能力、目录代次与 `ProviderCatalogPort` 由 `routing::catalog` 定义。快照编译和对账只消费
该只读合同,不反向依赖执行注册表;`ProviderRegistry` 实现目录端口,维护唯一的 Provider
注册集合。已知空目录与查询失败的未知目录保持不同语义,目录替身无需实现请求执行。
Provider 可为模型提供来源账号集合;Core 在公开目录查询时结合冻结账号范围与政策过滤来源,
配置发布复用已缓存的来源事实。来源集合只约束目录展示,不改变发现型目录的推理准入语义。
配置发布复用已缓存的来源事实。来源集合只约束目录展示,不改变发现型目录的推理准入语义
客户端原生模型目录同样由 Provider 拥有,通过 `ExecutionService` 和现有 Registry 的只读调用传递。
`routing::catalog` 的原生条目只包含模型 ID 与不透明协议正文,Core 负责冻结账号范围和整对象模型映射,
不解析上游字段;API 负责协议输出,不直接依赖具体 Provider。原生目录失败不会退回通用画像,只有不提供
该协议原生目录的 Provider(如 xAI 的 Codex 适配)才走画像转换。目录读取不创建计量请求或推理 attempt。
OpenAI 的账号/凭据 revision/客户端版本隔离、并发合并、TTL、容量与失效均归属 credential catalog
service;客户端原生对象保存在有界进程缓存中,与套餐 Redis 模型 ID 缓存分开管理。
service;客户端原生对象保存在有界进程缓存中,与套餐 Redis 模型 ID 缓存分开管理
全局请求位置及开关由运行设置持久化;快照仅在开关开启时生成全局覆盖,并随
`RuntimeSnapshot → RoutingPlan → AttemptContext` 冻结传递,关闭时保留客户端原有字段;
Provider 在选定账号后应用代理位置覆盖。请求期间不额外查询全局设置,配置发布不改变已开始请求的全局值。
Provider 在选定账号后应用代理位置覆盖。请求期间不额外查询全局设置,配置发布不改变已开始请求的全局值
Fast 限制由同一快照链路冻结:Client Key 所有绑定分组的开关取逻辑或;
禁用分组仍贡献限制,无分组 Key 不限制 Fast,不按所选账号的分组重新解释。
OpenAI Provider 在独立编码请求上、生成上游头与观测前统一应用顶层档位覆盖,HTTP、WS 与重试共用。
该策略不改变选号、会话亲和或共享原始请求;每个新 WS 请求重新取得当前策略。
WS 路由提示属于握手,连接复用时不重发;档位变化不重建连接或打断 `previous_response_id` 续写,
各帧正文及计费仍使用本次请求的最终档位。
各帧正文及计费仍使用本次请求的最终档位
`engine::observation` 统一维护单次响应的用量、费用、时间和响应 ID,并负责重试前清理;协调器继续
独占发送、提交、重试和终结顺序。Provider 上报费用优先于本地估算,丢弃的 attempt 不得污染最终计量。
Provider 本地估算按当前 attempt 实际发送的上游模型查价,响应声明的模型只作观测;费用明细复用同一口径。
Client Key 费用账本独立累计各次 attempt 的实际费用,不能因请求重试而清空已产生的费用或未知计费状态。
Client Key 费用账本独立累计各次 attempt 的实际费用,不能因请求重试而清空已产生的费用或未知计费状态
## 4. 数据面请求生命周期
@@ -272,7 +269,7 @@ sequenceDiagram
请求开始时冻结 `RuntimeSnapshot`、Client Key 的账号范围及账号模型政策、模型映射、Codex 客户端最低版本、Provider
候选顺序和调度策略。
运行中的请求始终使用该快照,不拼接不同版本的配置,也不在热路径查询分组关系。
运行中的请求始终使用该快照,不拼接不同版本的配置,也不在热路径查询分组关系
智能调度配置由 Core 定义和校验,Store 随运行设置及配置 revision 原子保存,快照编译器将其纳入
`AccountSelectionPolicy`。内置选号与诊断复用同一评分函数,近似最优容差随参与立即选号的五项系数总和缩放。
@@ -282,36 +279,36 @@ sequenceDiagram
排队系数为零时保留最短队列规则;已入队位置、队内 FIFO、取消回收、容量上限和请求共享等待预算保持原合同。
高权重回切仅影响智能调度的软亲和:先验证候选资格,再决定是否由更高权重层覆盖亲和;同层亲和保留。
Provider 限定的原生续写账号范围仍是硬约束。回切在请求选号时发生,不新增后台任务或主动历史重放。
插件显式选号沿用既有候选校验和实际亲和结果,委托内置选号时才应用智能调度配置。
插件显式选号沿用既有候选校验和实际亲和结果,委托内置选号时才应用智能调度配置
Client Key 鉴权完成后,API adapter 从有界请求头识别 Codex Desktop/CLI,Core 使用同一请求冻结的
`RuntimeSnapshot` 比较对应最低版本。Desktop 优先于其 User-Agent 内嵌的 CLI/Core 标记;未知客户端不
应用门禁。API 识别手机远程 UA 后缀;这类 Desktop 请求缺失应用版本时不应用门禁,不以 Core 或远程
客户端版本代替应用版本。其余低版本或已识别但版本不可用时,在进入 Provider 前返回稳定的 `426` 合同,
具体请求头规则见 [API 鉴权与公共约定](api.md#1-鉴权与公共约定)。
具体请求头规则见 [API 鉴权与公共约定](api.md#1-鉴权与公共约定)
核心不变量:
- 一个客户端请求对应一条 `model_requests`;attempt 是请求内事实,不建立第二张权威表。
- Provider 的一次 `execute` 只选择一个 credential 并返回一个冷流;换号、重试和 fallback 由 Core 决定。
- `not_sent`、`sent`、`ambiguous` 是单调的上游发送边界;结果不明确时不能假定上游未收到请求。
- downstream commit 是不可撤回的交付承诺。commit 后禁止换号、重试和 fallback。
- 一个客户端请求对应一条 `model_requests`;attempt 是请求内事实,不建立第二张权威表
- Provider 的一次 `execute` 只选择一个 credential 并返回一个冷流;换号、重试和 fallback 由 Core 决定
- `not_sent`、`sent`、`ambiguous` 是单调的上游发送边界;结果不明确时不能假定上游未收到请求
- downstream commit 是不可撤回的交付承诺。commit 后禁止换号、重试和 fallback
- API 在最终错误编码出口投影客户端恢复信号;该投影不修改 Provider 上游事实,也不改变 Core 的
重试与提交边界。具体错误合同见 [数据面接口](api.md#3-openai-数据面与模型目录)。
重试与提交边界。具体错误合同见 [数据面接口](api.md#3-openai-数据面与模型目录)
- Provider 可将明确容量拒绝标记为有界同账号退避,Core 在既有安全重放边界内执行,按账号维护请求内
预算,耗尽后复用普通换号路径。该退避消耗总路由预算,与 WS 传输恢复、OAuth 刷新及账号额度冷却分开。
- 跨 Provider 只在账号范围和能力都允许,且请求尚未到达上游或已被证明可安全重放时发生。
- 可恢复观测写入失败不能替换已经确定的客户端协议结果。
预算,耗尽后复用普通换号路径。该退避消耗总路由预算,与 WS 传输恢复、OAuth 刷新及账号额度冷却分开
- 跨 Provider 只在账号范围和能力都允许,且请求尚未到达上游或已被证明可安全重放时发生
- 可恢复观测写入失败不能替换已经确定的客户端协议结果
Responses 按模型目录编译候选;全局模型映射是精确映射,未命中时模型名原样交给候选 Provider。
Images 与 standalone Search 是 OpenAI Provider 自有端点:两者都不参与文本模型映射,只在 Client Key
的账号范围确实包含 OpenAI 账号时生成单一 OpenAI 候选。Images 不要求模型字段;Search body 中的模型
及其他字段保持原始 bytes 并由上游解释。
及其他字段保持原始 bytes 并由上游解释
## 5. Provider 与协议边界
Core 只理解 `Operation`、能力要求、Provider 候选、稳定错误和 canonical event,不读取 Provider SDK
类型。Provider 独占 credential schema、OAuth、账号选择、模型目录、额度投影和上游 transport。
类型。Provider 独占 credential schema、OAuth、账号选择、模型目录、额度投影和上游 transport
OpenAI 的 OAuth 与 API Key 共用现有账号和事务。API Key 的 Base URL、密钥和传输策略属于 Provider 凭据 JSON,
随 credential revision 更新;普通详情只投影非敏感连接设置。API Key 客户端目录按账号、凭据版本和客户端版本隔离,
@@ -320,14 +317,14 @@ credential state 调度,不以是否存在上游用户 ID 推断可用性;OA
状态恢复和未补齐身份的凭据轮换保留 `unknown`。
API Key 默认 HTTP/SSE,可选 WS 优先;选号先验证传输资格,WS pool 与 continuation 按凭据版本隔离。
OAuth 与 API Key 共用业务请求、响应和能力透传链路,差异限定在上游地址、认证、传输配置及明确的上游请求合同适配。
OpenAI 模型目录用于发现,不因目录缺项拒绝请求;管理员配置的模型权限仍由 Core 与选号链路执行。
OpenAI 模型目录用于发现,不因目录缺项拒绝请求;管理员配置的模型权限仍由 Core 与选号链路执行
- OpenAI 是透明边界。Responses 请求保留未知字段和字段顺序;SSE、WebSocket、Images 与 standalone
Search 的业务正文按原始字节转发,原生续写额度恢复遵循下述 continuation 例外。
canonical facts 从同一数据旁路提取,用于路由、恢复判断、观测和计费。
非流式 Responses 由 API 聚合 wire:终态省略或清空 `output` 时,使用同一响应的 `output_item.done`
按 `output_index` 还原完整输出;已有非空终态输出不改写。完成项缺失或冲突时在下游提交前拒绝,
不凭 canonical 增量补造内容,也不让 SSE/WS 转发额外保存整份输出。
不凭 canonical 增量补造内容,也不让 SSE/WS 转发额外保存整份输出
- Responses 的业务扩展头保留原始多值字节。API 负责剥离鉴权、账号身份和 HTTP 传输字段,
并提取会话语义;`gateway-protocol` 共享 HTTP 传输与网关链路字段分类。客户端兼容规则集中在
`providers/openai/src/transport/downstream/`:`headers.rs` 管理下游环境头和已提取语义的头部别名,
@@ -339,10 +336,10 @@ OpenAI 模型目录用于发现,不因目录缺项拒绝请求;管理员配
`transport/headers.rs` 负责上游身份保护和官方头组装。
会话别名只规范化请求头,不清除正文身份字段;未知业务扩展与响应诊断头不受影响,字段见
[Responses 合同](api.md#3-openai-数据面与模型目录)。Grok 专属请求标记和已知指令开场白的兼容只在
`downstream/grok.rs` 内判断,且仅用于 Codex/OAuth 上游;普通请求的提示词、工具及业务正文不做品牌清洗。
`downstream/grok.rs` 内判断,且仅用于 Codex/OAuth 上游;普通请求的提示词、工具及业务正文不做品牌清洗
- xAI 是翻译边界。Provider 把 Grok wire 转换为 Responses wire;上游结构化错误的 message/code/type
可以透出,但账号指纹会先脱敏。
- response ID 是不透明 UTF-8 bytes,不假设 UUID、固定长度或跨 Provider 可复用。
可以透出,但账号指纹会先脱敏
- response ID 是不透明 UTF-8 bytes,不假设 UUID、固定长度或跨 Provider 可复用
- OpenAI 与 xAI 用户身份选择由 PostgreSQL 保存,Core 在 `FrozenAccountScope` 中按 Key 整体覆盖通用选择,
以 Provider-owned 不透明对象沿路由计划传递;执行会话复用首次解析结果,使重试不受发布更新影响。
各 Provider 唯一负责默认值、校验、版本来源及 UA 生成,Admin 提供管理和生效预览。
@@ -354,11 +351,11 @@ OpenAI 模型目录用于发现,不因目录缺项拒绝请求;管理员配
普通连接按已有身份键匹配,精确续写保留原连接。
后台账号和 Desktop 专属操作使用独立官方 Desktop 画像,不接受 Key 覆盖。
xAI 以内置画像为版本检查基线,在进程内更新 Grok CLI 发布资料;模型与压缩请求使用已保存的用户选择,
OAuth、后台目录和额度查询使用内置官方画像。两者均不回写 `config.yaml`。
OAuth、后台目录和额度查询使用内置官方画像。两者均不回写 `config.yaml`
xAI Provider 负责 Codex custom 工具与 Grok function 工具的双向转换,保持工具类型、item ID 与
`call_id` 配对;超限或转换失败终止流。默认 `store: false` 的续接由现有会话 owner 重放完整历史;
原生续接按上游约束处理 `instructions` 与 `previous_response_id`,不把协议差异交给 Core。
原生续接按上游约束处理 `instructions` 与 `previous_response_id`,不把协议差异交给 Core
### 后台账号导入
@@ -368,19 +365,19 @@ OpenAI / xAI 导入用例。并发槽位由单个 Worker 统一管理,条目
停止只跳过待执行输入;Host 关闭时停止接收新任务并在关闭预算内等待已开始条目完成。
完成或跳过即释放原始输入,终态记录有时限和数量上限,不写 PostgreSQL 或 Redis。
前端通过当前管理员的任务列表恢复视图,轮询与任务执行互不拥有生命周期。接口、限额与重启语义见
[后台导入任务](api.md#后台导入任务)。
[后台导入任务](api.md#后台导入任务)
### 账号出站代理
出站代理属于账号配置,由各 Provider 在推理、OAuth 服务端交换/刷新、额度、目录和资料等请求中统一使用。
指定代理不可用时请求失败,不退回直连;未配置时直连,不继承进程的全局代理环境变量,TLS 仍验证证书。
用户浏览器访问第三方授权页时的出口不受网关设置控制。
用户浏览器访问第三方授权页时的出口不受网关设置控制
连接池按出口隔离。修改代理推进配置 revision,使后续请求使用新出口;已执行请求可沿原连接完成。
代理变更不推进 `credential_revision`,不会使进行中的令牌刷新因凭据版本冲突而丢失结果。
xAI 的推理连接池继续按账号绑定隔离;OAuth、目录/计费辅助请求在各自 transport 内复用有界出口
client,OIDC 的 JWKS 缓存与单飞归属对应出口状态。自动刷新提交凭据后的目录预热重新读取当前账号
出口,不从 token-only 路径绕过代理;JWKS 过期或获取失败仍不使用 stale fallback。
出口,不从 token-only 路径绕过代理;JWKS 过期或获取失败仍不使用 stale fallback
代理独立保存和测试,通过 `outboundProxyId` 绑定账号;账号保留解析后的 URL 供 Provider 使用。
出口测试结果属于诊断信息,不作为选择、授权或导入绑定的准入条件。
@@ -393,26 +390,26 @@ client,OIDC 的 JWKS 缓存与单飞归属对应出口状态。自动刷新提
代理修改、删除和测试结果写入通过同一资源的事务级独占锁检查,导入期间直接返回冲突。
导入保护连接数量有界,不占用提交所需的连接池槽位,也不持有空闲事务。
请求取消或进程退出时关闭会话并释放锁。探测器由组合根注入 OpenAI 的 HTTP 客户端构建函数,
复用其自定义 CA 加载与证书校验规则,Host 不依赖具体 Provider 包。
复用其自定义 CA 加载与证书校验规则,Host 不依赖具体 Provider 包
导入器在认证交换前解析并校验账号出口。sub2api 的代理引用按完整 URL 登记为共享代理并绑定账号;
其 SOCKS5 配置按远端 DNS 语义转换为 SOCKS5H。账号导入只迁移账号及出口,不迁移下游 Key、余额或历史用量。
代理认证信息仅通过敏感账号导出返回,列表、详情、Debug 和普通审计不得暴露;数据库及备份按凭据保护。
输入字段、协议支持与导入校验见 [账号 API](api.md#5-账号)。
输入字段、协议支持与导入校验见 [账号 API](api.md#5-账号)
### Codex 原生生图与认证
管理端导出的 Codex 配置使用代理 Bearer 密钥,并声明服务端托管账号认证。
客户端据此开放原生生图工具,图片生成和编辑由 Images 路由处理。
这只解决客户端能力识别;模型能力、客户端限制和上游账号的实际生图权限仍分别检查。
这只解决客户端能力识别;模型能力、客户端限制和上游账号的实际生图权限仍分别检查
`X-OpenAI-Actor-Authorization` 是客户端能力标记,不是账号凭据。API 解码和 OpenAI Provider
都过滤该 header,网关继续校验 Client Key,上游认证由服务端账号产生。
客户端配置示例维护在 [部署文档](../deploy/README.md#客户端配置)。
客户端配置示例维护在 [部署文档](../deploy/README.md#客户端配置)
客户端到代理与代理到上游的传输选择相互独立。客户端的 `supports_websockets = false`
不禁止 Provider 使用上游 WebSocket。响应终态前的 Close 1000 仍视为失败,
后续恢复请求成功也不改写原失败请求的结果。
后续恢复请求成功也不改写原失败请求的结果
### 错误与诊断三层边界
@@ -420,32 +417,32 @@ client,OIDC 的 JWKS 缓存与单飞归属对应出口状态。自动刷新提
1. **数据面协议错误**:`/v1/*` 继续遵守 OpenAI/xAI wire 合同。可交付原始上游响应时保留其状态、headers、
content type 和 body;原生续写额度恢复由 Provider 单独投影客户端响应,真实上游事实仍用于诊断和记账。
本地 fallback 使用数据面稳定机器码与安全英文,不受管理端中文化影响。
本地 fallback 使用数据面稳定机器码与安全英文,不受管理端中文化影响
2. **控制面展示错误**:`/api/admin/*` 由 API 统一 HTTP 状态与数值业务码,由 Admin/API owner 提供安全中文
文案。extractor rejection、namespace 404 和 method 405 也使用同一 JSON 信封;任意 Store、Serde、
Provider `Display` 不得直接跨越 HTTP 边界。
Provider `Display` 不得直接跨越 HTTP 边界
3. **原始上游诊断**:只在连接测试、运维错误详情等明确诊断界面展示已经捕获的原始字段,不翻译、不补造,
也不塞入普通 Admin 错误信封。原始 body 不进入 Debug、普通日志或持久化错误消息。
也不塞入普通 Admin 错误信封。原始 body 不进入 Debug、普通日志或持久化错误消息
Provider 管理适配在仍持有结构化失败事实时选择静态 `public_message`;Admin 将其转为可安全展示的
`AdminError.message`,API 保留其中的 502/503 具体原因,无公开消息时才使用通用回退。认证与未知
内部异常仍保持固定文案,不能把任意 Provider/Store 的 message 直接放行。
管理提示与 Worker 的处置分类是不同职责:例如 OpenAI 刷新已收到 401 时,可以提示已解析的令牌
拒绝原因,但不因此改变现有有界恢复退避和账号终态判定。刷新繁忙、已知上游失败与结果未知分别映射为
409、50201、50202;结果未知不能触发自动重放一次性凭据,Vue 也不重新解析上游错误码或正文。
409、50201、50202;结果未知不能触发自动重放一次性凭据,Vue 也不重新解析上游错误码或正文
连接测试的 `gateway` / `provider` / `upstream` 来源以及 `not_sent` / `sent` / `ambiguous` 发送状态由 Core 在
仍持有完整执行错误时一次判定;Vue 只能根据稳定字段生成摘要,不能匹配英文错误句子反推来源。
仍持有完整执行错误时一次判定;Vue 只能根据稳定字段生成摘要,不能匹配英文错误句子反推来源
Vue 普通管理请求的错误提示由 `api/request.ts` 响应拦截器统一负责:优先展示安全信封的 `message`,
缺失或空白时才使用请求层的 HTTP、网络或超时兜底;成功业务码为 `200`,HTTP 成功但业务码失败也会拒绝。
规范化异常保留 `status`、`code`、`requestId` 与 `kind`。页面和 `useAsyncAction` 不重复弹出接口错误;
查询可以保留失败状态与重试入口,本地校验、文件操作、SSE 诊断和成功响应中的业务结果仍归各自 owner。
查询可以保留失败状态与重试入口,本地校验、文件操作、SSE 诊断和成功响应中的业务结果仍归各自 owner
取消或已被新查询取代的请求不弹提示;后台轮询、重启探测等显式使用 `silent`,它只关闭提示,
不吞异常,也不跳过会话失效处理。
批量操作的部分成功汇总、不可逆操作的结果未知等必要业务处理先将对应请求静默,再由业务 owner 提供
一次有上下文的反馈;不得为普通失败重新维护一套消息或业务码映射。
一次有上下文的反馈;不得为普通失败重新维护一套消息或业务码映射
### 登录与查询身份
@@ -462,16 +459,16 @@ ObservabilityStore 的范围查询;数据面额度查询不执行推理准入
API 只输出各入口所需的字段白名单,不复用管理员的宽响应。
客户端配置通过 ClientKeyStore 显式读取当前会话绑定 Key 的明文,不进入用量响应。
前端 `/key-usage` 独立于管理布局,不挂载管理员菜单或请求管理接口;配置弹窗和 Codex / CCSwitch
配置生成逻辑与管理端共用,明文仅在打开弹窗时获取,关闭后清除,不持久化到浏览器。
配置生成逻辑与管理端共用,明文仅在打开弹窗时获取,关闭后清除,不持久化到浏览器
## 6. 路由、账号范围与 continuation
Client Key 与账号分组形成授权范围:
- 没有分组关联表示 `AllAccounts`;
- 有关联时只允许已启用分组成员的并集;
- 已绑定分组为空或全部禁用时得到空池,绝不回退为全部账号;
- 分组可以包含多个 Provider,账号也可以属于多个分组。
- 没有分组关联表示 `AllAccounts`
- 有关联时只允许已启用分组成员的并集
- 已绑定分组为空或全部禁用时得到空池,绝不回退为全部账号
- 分组可以包含多个 Provider,账号也可以属于多个分组
账号选择综合启停状态、credential/quota 事实、Redis cooldown、并发上限、权重、请求间隔和会话亲和。
默认账号并发上限为 `0` 时表示无限;账号独立正数上限仍优先,未设置则继承默认值。
@@ -479,36 +476,36 @@ Client Key 与账号分组形成授权范围:
`account::AccountModelAccess` 拥有管理员模型政策的校验与精确匹配语义,存入账号行的 `model_access_json`,
由 `RuntimeAccountDirectory` / `FrozenAccountScope` 随配置快照冻结。Provider 在额度、亲和与租约之前
按映射后的上游模型筛选账号;重试和 fallback 使用同一冻结政策。上游目录和凭据不承载或改写该政策。
客户端目录的政策过滤判断范围内整个候选池,原生模型对象仍由 Provider 独占解释。
客户端目录的政策过滤判断范围内整个候选池,原生模型对象仍由 Provider 独占解释
账号编辑在一个事务中更新调度事实,批量更新只应用显式提供的字段。导入与首次 OAuth 可携带统一账号设置,由 Admin 传递给 Store,
与凭据在同一事务中提交;Provider 仍独占凭据解析。未附带设置的导入、重新授权和后台刷新保留已有分组、
权重、并发与模型政策。模型政策变更推进配置 revision,不推进 credential revision。管理端导入和账号编辑共用设置表单,凭据输入独立于设置。
权重、并发与模型政策。模型政策变更推进配置 revision,不推进 credential revision。管理端导入和账号编辑共用设置表单,凭据输入独立于设置
Continuation 仍受原请求的 Client Key、账号范围、Provider 和发送/交付边界约束:
- native continuation 固定创建它的 Provider 与账号;
- native continuation 固定创建它的 Provider 与账号
- OpenAI Provider 的 OAuth 大包传输预检在发送前执行,新链可选择 HTTP;大 connection-local
续接和 HTTP `store=false` 后不可原生续写的 WS 增量返回 `ClientReplayRequired`,由客户端重发
完整历史。体积测量复用 WS 编码结构,Provider session state 仍只携带元数据,不保存 transcript;
完整历史。体积测量复用 WS 编码结构,Provider session state 仍只携带元数据,不保存 transcript
- OpenAI 在交付前收到可安全重放的明确额度拒绝时,先隔离账号,再投影 `ClientReplayRequired`;
丢弃未交付的原错误帧,由客户端提交完整历史开启新链,不把原增量输入交给其他账号。
真实错误分类、状态码、发送状态和上游诊断保持不变,客户端合同见 [Responses API](api.md#3-openai-数据面与模型目录);
- OpenAI 按 native → replay owner → replay any 推进,并保留官方 `previous_response_id` 语义;
- xAI 使用客户端提交的完整历史作为重放输入;
- scope 外账号、跨 Key 复用或不明确发送结果均 fail closed。
真实错误分类、状态码、发送状态和上游诊断保持不变,客户端合同见 [Responses API](api.md#3-openai-数据面与模型目录)
- OpenAI 按 native → replay owner → replay any 推进,并保留官方 `previous_response_id` 语义
- xAI 使用客户端提交的完整历史作为重放输入
- scope 外账号、跨 Key 复用或不明确发送结果均 fail closed
会话亲和是优先选择提示,不是硬账号绑定;native continuation 才携带不可跨越的 owner 约束。
OpenAI 启用账号排队时,可等待的已有亲和绑定优先保留;子线程首绑前继承的根偏好仍允许分流。
快照满载、原子租约争用与已有等待者均进入原账号队列,等待重查仍执行完整资格校验。
显式策略选号和内置高权重回切保留各自裁决;亲和等待不将暂忙升级成失效,也不覆盖原生续写约束。
显式策略选号和内置高权重回切保留各自裁决;亲和等待不将暂忙升级成失效,也不覆盖原生续写约束
### 并发等待
Core 的 `concurrency` 拥有中立的有界等待位置与 FIFO 唤醒,不依赖账号、路由或执行会话;
账号策略仅引用其中的等待配置值。执行准入与各 Provider 选择器分别持有等待队列,按 Key/账号隔离。
运行并发仍由 Redis 原子准入与账号租约裁决,等待队列只保存当前进程中的等待位置,不复制运行计数,
也不持久化正文或在重启后重放请求。每个等待 owner 最多容纳 1,024 个等待者作为资源兜底。
也不持久化正文或在重启后重放请求。每个等待 owner 最多容纳 1,024 个等待者作为资源兜底
新请求不能抢占已有等待队列;队首短周期重读容量,取消/完成等待通过 Drop 回收位置并唤醒后继。
Provider 等待重试仍在同一次未发送准备阶段重新读取账号资格、容量、目录和冷却,不增加 attempt;
@@ -516,7 +513,7 @@ Key 的 RPM 在成功准入时才计数,金额限制在入队前及成功准
账号并发与本地请求间隔可有限等待,权限、额度和上游冷却不能作为可等待容量。
密钥准入、账号选择与后续重试共享请求级等待预算,从首次入队开始计时,同时受请求整体截止时刻约束。
切换等待层、账号或 Provider 不重置等待时限;没有发生排队时不启动该计时,也不据此中断已开始的上游生成。
队列满/超时属于本地容量拒绝,不作为上游限流反馈或 Provider 故障熔断证据。
队列满/超时属于本地容量拒绝,不作为上游限流反馈或 Provider 故障熔断证据
### Client Key 限额与结算
@@ -527,46 +524,46 @@ Admin 的手动重置复用同一账本与 Key 行锁,在一个事务中清零
插件预算回调通过 `PluginClientKeyAccess` 进入同一 `ClientKeyService` 和 `ClientKeyStore`,存储事务先复用插件写入授权,
再执行原生预算操作;不建立另一份预算状态。查询复用账本投影,不触发准入。局部更新金额上限不改用量或窗口,
仅在值变化时提交配置 revision、审计并发布;重置仍不推进配置版本。重复调用及结果未知的合同见
[SDK 预算接口](../backend/crates/gateway-plugin/sdk/docs/capabilities.md#client-key-预算)。
[SDK 预算接口](../backend/crates/gateway-plugin/sdk/docs/capabilities.md#client-key-预算)
- 日窗口按北京时间零点划分;七天窗口从首次准入当天零点开始,到期后由下一次使用重新开启,不固定为周一。
- 日窗口按北京时间零点划分;七天窗口从首次准入当天零点开始,到期后由下一次使用重新开启,不固定为周一
- 金额优先使用 Provider 上报的 USD,否则按现有模型价格估算;订阅账号的估算费用不代表上游订阅账单。
费用归属请求完成时间,跨日请求计入完成日,延迟写入仍保留原完成时间。
- 准入检查已结算金额,不预占未来费用。达到任一金额阈值后拒绝新请求,已准入请求仍可完成并使总额超过阈值。
费用归属请求完成时间,跨日请求计入完成日,延迟写入仍保留原完成时间
- 准入检查已结算金额,不预占未来费用。达到任一金额阈值后拒绝新请求,已准入请求仍可完成并使总额超过阈值
- SSE 持有并发名额直到终态;WebSocket 每个 `response.create` 独立准入,空闲连接不占名额。
Core 在每次请求开始时重新鉴权并冻结当前策略,既有连接也应用已发布的限额与授权范围变更。
换号和内部重试复用同一名额,Key 与账号并发上限同时生效;预算拒绝或启动失败释放名额。
换号和内部重试复用同一名额,Key 与账号并发上限同时生效;预算拒绝或启动失败释放名额
- 响应中断入口由 Core 的根执行持有,API 只控制当前连接正在交付的执行。Provider 在收到上游
`response.created` 后注册活动响应 owner,在终态、失败或断连时释放;控制句柄本身不延长 owner
生命周期。中断信号交给占用原账号、原 socket 的 exchange,不经过全局响应查询、新选号或独立 attempt。
API 暂存的下一轮请求和连接泵接收队列共用容量名额,控制帧处理不取消中间件正在读取响应体的 future。
API 暂存的下一轮请求和连接泵接收队列共用容量名额,控制帧处理不取消中间件正在读取响应体的 future
完成、失败、取消和断连使用同一结算路径,在结束时以网关请求 ID 幂等累计已取得费用。
执行会话持有结算与准入释放的同一个清理 future;协议等待被取消后,后续驱动或宿主 detached finalizer
继续原清理,不重复结算。只有执行终态、结算与释放均已返回,协议层才可视为 finalized 并放弃清理责任。
缺少用量或价格、无法取得 USD 费用的尝试按零累计,不创建待核账记录,也不会阻断 Key;
内部重试中已经取得的费用仍须累计。发送状态只决定重放是否安全,不能据此推断费用。
请求日志继续保留真实错误、用量与费用来源,账本按零累计不表示上游实际免费。
请求日志继续保留真实错误、用量与费用来源,账本按零累计不表示上游实际免费
结算写入失败时,进程内保留精确费用,在同一 Key 下次请求前重试;进程退出后无法恢复的费用不会形成欠账。
PostgreSQL 不可用时拒绝所有新的计费请求,Redis 继续管理并发/RPM 租约。
账本独立于可丢弃的请求观测日志,日志清理不重置金额;费用事件保留至删除 Key,已有日志不会回填为账本费用。
字段与错误合同见 [Client Key API](api.md#7-client-key)。
字段与错误合同见 [Client Key API](api.md#7-client-key)
### 模型价格与费用快照
Provider 是内置价目、服务档位、模态和工具费规则的唯一 owner;Core metering 定义中立价格覆盖与
精确金额运算。Admin 管理人工覆盖与手动同步,Host `pricing` 适配固定的 models.dev HTTPS 来源,
由组合根注入 Admin 的 `PricingSource` 端口;Store 负责来源层、人工层与审计的事务持久化。
同步不能覆盖人工项,更新仅修改选中的模型。HTTP 合同与价格边界见 [模型定价 API](api.md#模型定价)。
同步不能覆盖人工项,更新仅修改选中的模型。HTTP 合同与价格边界见 [模型定价 API](api.md#模型定价)
编译 RuntimeSnapshot 时合并同步与人工配置,Provider 缺省项仍由其内置表解释。不可变价格集合经
Arc 随 RoutingPlan 冻结并进入所有 attempt,不在推理请求中读取数据库、Redis 或外部价目,也不复制
整份价目。Provider 按实际发送模型选择配置;自定义倍率只作用于本地计算费用,上游报告金额不变。
整份价目。Provider 按实际发送模型选择配置;自定义倍率只作用于本地计算费用,上游报告金额不变
本地计算费用携带当次拆分、有效单价和倍率,终态观测将其写入版本化费用快照。查询优先还原快照,
不使用新配置解释历史;无快照的旧记录保留总额核对后补充拆分的路径。Client Key 账本使用同一费用
结果沿既有幂等结算流程累计,不依赖请求明细写入成功。
结果沿既有幂等结算流程累计,不依赖请求明细写入成功
## 7. 控制面与 revision
@@ -585,15 +582,15 @@ HTTP validation
会改变路由快照或安全配置的 mutation 在同一 PostgreSQL 事务中提交业务事实、推进内部
`config_revision` 并写入脱敏审计。`configRevision` 不作为客户端写入的乐观并发前置条件;
少数账号/分组响应返回它用于标识已提交的配置。插件配置等资源另有 `revision` / `expectedRevision` 检查,
不能与全局配置版本混用。
不能与全局配置版本混用
额度、cooldown、目录 generation、请求统计和自动 credential refresh 属于运行时观测,不推进全局
revision;credential 轮换只推进账号自己的 `credential_revision`。Redis 通知用于缩短收敛延迟,
PostgreSQL 周期对账才是正确性基础。
PostgreSQL 周期对账才是正确性基础
同一 publisher 的管理提交、订阅通知与周期对账共享编译到发布/暂停的临界区,避免旧结果覆盖新授权或
晚到的失败暂停新快照;请求读取不等待刷新锁。对账仍允许持久 revision 回退,不能只比较版本大小。
配置不可确认时暂停新请求;仅目录 generation 变化且编译失败时继续使用旧快照,留待下次对账。
配置不可确认时暂停新请求;仅目录 generation 变化且编译失败时继续使用旧快照,留待下次对账
## 8. 状态所有权
@@ -611,46 +608,48 @@ PostgreSQL 周期对账才是正确性基础。
账号对外状态不是独立列,而是 PostgreSQL credential/quota 事实与 Redis cooldown 的统一投影:
`normal`、`quota_exhausted`、`rate_limited`、`disabled`、`error`。只有明确上游证据才能恢复或终态化账号,
本地时钟和不确定响应不能伪造事实。
本地时钟和不确定响应不能伪造事实
容量冻结与普通上游限流共享账号冷却投影,但恢复条件不同:需要探测的冻结在截止时间后仍阻止调度,
普通推理成功不能解除。Admin 恢复任务按冻结代次原子提交探测结果,避免旧结果覆盖手动恢复或新冻结;
自动降低并发通过现有管理事务与快照发布链路,仅按数据库最新设置更新并发上限。
自动降低并发通过现有管理事务与快照发布链路,仅按数据库最新设置更新并发上限
PostgreSQL schema 由迁移目录按编号管理。已应用迁移按字节冻结,后续 schema 变化只能新增编号迁移,
详见 [迁移规则](../backend/migrations/README.md)。
详见 [迁移规则](../backend/migrations/README.md)
## 9. Credential、额度与主动重置
credential 与 quota 是两组独立事实:credential refresh 不等于 quota refresh,额度接口的 401/403
也不能单独证明 refresh token 永久失效。
也不能单独证明 refresh token 永久失效
- OpenAI 支持 OAuth、AT/RT、PAT 和上游 API Key。OAuth 身份来自官方 JWT claims,PAT 经官方身份接口验证,
不信任导入文档顶层身份字段。RT-only 导入先换取 AT;AT-only、PAT 与 API Key 不参加 OAuth 自动续期。
输入形态和适用操作见 [账号能力与导入](api.md#账号能力导入与-oauth)。
输入形态和适用操作见 [账号能力与导入](api.md#账号能力导入与-oauth)
- xAI 使用 OAuth session;API Key 不是受支持的账号 credential。刷新额度时同步查询官方实时订阅,
只把套餐事实写入现有 quota JSON。明确无付费订阅的个人账号显示 Free;查询失败、缺失字段或
团队身份不推断为 Free,订阅查询失败不影响额度观测。
团队身份不推断为 Free,订阅查询失败不影响额度观测
- 账号导入和 OAuth complete(包括重新授权)在 credential 提交、Provider 事实失效及快照发布后,
由 Admin 共用流程后台读取一次额度;不等待观测完成才返回管理请求,失败记录告警但不回滚账号事务。
手工和后台 credential refresh 仍不隐式刷新 quota。
手工和后台 credential refresh 仍不隐式刷新 quota
- quota refresh、正常推理返回的 rate-limit headers 和后台健康任务汇入同一额度事实;套餐只用于展示与
目录 cache 隔离,不创建套餐专属状态机。
OpenAI Provider 将其中明确的套餐变更与额度原子提交,共用凭据版本和观察时间保护;
空值及 `unknown` 不覆盖套餐,同族泛化值保留具体子类型。Token 刷新保留提交时的账号资料。
空值及 `unknown` 不覆盖套餐,同族泛化值保留具体子类型。Token 刷新保留提交时的账号资料
OpenAI 订阅周期属于按需个人信息,不是额度事实。Admin 账号用例通过现有 Provider 管理端口并发读取
个人资料统计与订阅,汇聚为一次只读响应;Provider 继续拥有各自的认证、出站代理与上游协议处理。
订阅不由 quota、导入或后台任务触发,不持久化。汇聚结果返回前核对账号身份与 credential revision,
避免重新授权期间展示混合身份信息;单项失败不丢弃另一项可用结果。前端只在打开「个人信息」或手动
刷新信息时请求,关闭后取消等待,不维护两套请求状态。
刷新信息时请求,关闭后取消等待,不维护两套请求状态
主动额度重置是 OpenAI Provider 的不可逆上游操作:列表查询和消费都直接使用当前 Desktop 请求画像;
卡片不写 PostgreSQL/Redis。消费请求携带调用方生成的 UUIDv4 幂等键,同一账号的消费在进程内串行;
发送结果不明确时必须复用原键。确认成功后管理端再显式刷新卡片与 quota,不能直接改本地重置时间。
发送结果不明确时必须复用原键。确认成功后管理端再显式刷新卡片与 quota,不能直接改本地重置时间
## 10. 观测与后台任务
### 额度预测
账号容量预测属于 Admin 的只读派生规则,不参与 quota 权威状态、调度或金额结算。Store 通过专用采样端口
在同一 SQL 快照内返回截至观测时间的累计数值及有界历史 Provider 文档;原生 Provider 复用协议解析器解释
文档,插件由 Runtime 本地解释 SDK 声明的版本化额度观测,不在历史查询中调用插件进程。观测通过 Core 原有
@@ -659,28 +658,38 @@ OpenAI 订阅周期属于按需个人信息,不是额度事实。Admin 账号
周期以额度重置为边界,重置后累计与样本重新开始。该采样不改变全站完整交付用量口径,不创建第二份
持久化额度状态。部分 Token/美元费用缺失仅提示精度限制,继续按已记录数值估算;对应数值完全不可用时
才不返回该项预测,不按请求数量补齐未知消耗。历史请求完成时间只是额度时间的近似,不承诺严格扣额归因
或预测准确率。公开字段与采样门槛见 [周/月额度预测](api.md#周月额度预测)。
或预测准确率。公开字段与采样门槛见 [周/月额度预测](api.md#周月额度预测)
采样规则:
- 同一额度窗口内,每累计至少 5 个百分点形成一段,使用最近 3 个完整段及尾部计算合并比值,不平均逐请求的小分母比值
- 重复读数不增加段数,大于 1 个百分点的额度回落、累计计数倒退或窗口身份变化会中断采样
- OpenAI 的窗口匹配复用限流解析器,核对桶、槽位、时长和明确套餐,只容许最多 2 秒的重置时间抖动
- 重置后从新段重新累计,无法精确归属的用量不补算;只有账号在窗口开始前加入且无已知断点时,才允许以窗口累计量替代增量采样
- 历史点抽样不缩减区间累计用量,同一完成时间共用同一累计值
### 请求记录与交付
请求观测通过有界进程内队列异步投影到 PostgreSQL。队列满、Store 暂不可用或进程退出超时时,允许丢失
可恢复观测并累计指标,但不允许改变客户端响应。Usage 详情中的 attempt 因此是 best-effort,并通过
`attemptsComplete: false` 明示不完整性。
`attemptsComplete: false` 明示不完整性
模型执行以 `model_requests.id` 标识,上游身份由 `upstream_request_id` 保存。
API 输出模型执行与上游请求的关联信息,具体响应头规则见 [数据面接口](api.md#3-openai-数据面与模型目录)。
入口 middleware 的 ID 用于入口日志,不作为模型请求的主键或检索映射。
失败终态及中间失败的上游请求 ID 优先取对应 `ProviderError`,缺失时再取该 attempt 的响应
observation、调用 metadata;最终失败的关联头不与 opening 身份混合。
observation、调用 metadata;最终失败的关联头不与 opening 身份混合
正常路径的请求行仍在首次合法 ProviderStream 建立后随 attempt 合并创建。已进入 Core 执行会话、
但在首次合法冷流建立前发生的无可用账号、准备失败、超时或取消,复用同一请求表补写失败终态与
诊断快照;没有实际 attempt 时计数为零,账号与传输保持空值。Provider 的准备过程不得发送本次请求的
上游握手或业务载荷,也不得启动独立发送任务;已合法登记冷流后,即使没有首事件也保留真实 attempt。
不能因首写失败或缺少 metadata 而伪造零尝试、账号、传输或 `not_sent`。
不能因首写失败或缺少 metadata 而伪造零尝试、账号、传输或 `not_sent`
终态观测写入由会话持有;事件等待被取消后,后续 poll 或 detached finalizer 继续同一个 future,
保留原结果及完成时间,不重复创建请求。观测写入失败仍为 best-effort,不阻断费用结算与准入释放。
鉴权、解析、路由或准入等尚未进入执行会话的入口拒绝不纳入此请求记录边界;异步投影也仍可能延迟或
丢弃。排障需结合入口日志,不能将“无记录”解释成没有发生错误。
丢弃。排障需结合入口日志,不能将“无记录”解释成没有发生错误
只有完整交付客户端的成功推理响应进入 Token、延迟和成本聚合。OpenAI Provider 根据请求的
`generate: false` 将连接与上下文准备归类为 `prewarm`,不信任客户端单独声明的同名 metadata。
@@ -688,15 +697,17 @@ Store 的共享用量口径排除这些预热记录,账号用量与额度预
观测和费用事实仍保留,不将未知费用改写为零,也不影响 Client Key 结算账本。
OpenAI Responses 的统计档位与本地费用估算统一使用 Provider 最终发给上游的请求 `service_tier`,
不由响应回显覆盖;未发送档位时保留缺失值,展示与估算按标准档处理。上游响应档位独立保留在
Provider metadata 的 `upstreamServiceTier`,不改写客户端收到的响应,也不据此断言实际加速效果。
Provider metadata 的 `upstreamServiceTier`,不改写客户端收到的响应,也不据此断言实际加速效果
### Worker 与账号冻结
Worker 由各 Bundle 贡献、由 Host 统一监督:
- Store:过期请求恢复、历史保留和 PostgreSQL/Redis 观测队列;
- Core:`runtime` owner 的 RuntimeSnapshot 周期对账和 Redis change 订阅;
- Store:过期请求恢复、历史保留和 PostgreSQL/Redis 观测队列
- Core:`runtime` owner 的 RuntimeSnapshot 周期对账和 Redis change 订阅
- Admin:S3/R2 备份 daemon,负责调度、执行、删除收敛与保留清理;
以及账号冻结恢复 worker(容量熔断的自适应并发下调与到期探测解冻);
- Provider:credential refresh、quota/catalog 健康和官方版本/etag 检查。
以及账号冻结恢复 worker(容量熔断的自适应并发下调与到期探测解冻)
- Provider:credential refresh、quota/catalog 健康和官方版本/etag 检查
账号容量熔断默认关闭。启用后,仅普通请求收到的明确容量拒绝(`server_is_overloaded`、`slow_down`
或结构化错误中的明确过载提示)按滑动窗口计数,并把当时观测到的在途并发并入峰值证据;
@@ -707,72 +718,81 @@ Worker 由各 Bundle 贡献、由 Host 统一监督:
worker 复用连接测试探针执行真实上游调用,并按冻结代次处理结果:成功清除,失败按配置时长顺延,
过期探测结果不得覆盖更新的冻结或管理员操作。关闭自动冻结或探测后,已有冻结在冷却到期后恢复。
自适应并发下调按观测峰值的 80%(下限 2)原子更新最新有效账号上限,只降不升,不覆盖其他账号
设置,审计标注为系统变更;该上限持久保存,恢复调度后不自动调高。
设置,审计标注为系统变更;该上限持久保存,恢复调度后不自动调高
周期任务的 Redis lease 只保证单周期互斥,不构成多副本 leader 选举。备份 daemon 依赖单副本部署边界,
自更新也只替换处理请求的当前进程,因此整个应用必须保持单副本。
自更新也只替换处理请求的当前进程,因此整个应用必须保持单副本
## 11. 生命周期、安全与恢复
### 启动与关闭
启动只有在配置、PostgreSQL、Redis、Provider、Core、Admin、API 和 Worker 全部初始化成功后才进入服务。
健康检查综合 Core、Store 与 Worker 状态,但不会把单个 Provider 的业务降级等同于整个进程失活。
客户端下载解析由 Host 的 `ClientDistributionResolver` 实现,组合根注入 Admin;只在管理员请求时访问外部来源,
不阻塞启动。失败时使用官方稳定地址,临时直链不持久化或代理下载,字段与来源规则见 [客户端下载 API](api.md#windows-客户端下载)。
不阻塞启动。失败时使用官方稳定地址,临时直链不持久化或代理下载,字段与来源规则见 [客户端下载 API](api.md#windows-客户端下载)
关闭分为两段:先停止接收新连接并 drain HTTP/WS,再取消并等待 Worker;两段各有独立预算,Compose 的
`stop_grace_period` 必须覆盖二者之和。超时后只丢弃仍未落盘的可恢复观测,不执行隐式业务重放。
`stop_grace_period` 必须覆盖二者之和。超时后只丢弃仍未落盘的可恢复观测,不执行隐式业务重放
安全边界:
### 凭据、日志与诊断
- Provider credential 以 Provider schema 的明文 JSON 保存在 PostgreSQL;数据库和备份必须按敏感数据保护。
- Provider credential 以 Provider schema 的明文 JSON 保存在 PostgreSQL;数据库和备份必须按敏感数据保护
- `host.logging.oauth_recovery` 默认关闭,开启后将 OAuth 原始 AT/RT 写入独立文件;
与普通文件日志开关分别控制,不输出到普通日志或 stdout。`.runtime/logs` 同样属于敏感数据。
与普通文件日志开关分别控制,不输出到普通日志或 stdout。`.runtime/logs` 同样属于敏感数据
- `host.logging.request_dump` 默认关闭;开启后独立请求转储包含原始请求头和正文,
可能包含密钥及用户内容,只能在明确的排障范围内使用,不能作为普通日志公开。
可能包含密钥及用户内容,只能在明确的排障范围内使用,不能作为普通日志公开
- Host 拥有文件日志写入、轮转、保留和关闭流程,以时间窗口完整性为约束,队列满时背压,正常关闭时排空并同步。
写入缺口必须通过健康状态持续报告,压缩或清理失败不能伪装成保留完成。
配置、保留窗口和容量规划见 [日志与持久化](../deploy/README.md#持久化与备份)。
配置、保留窗口和容量规划见 [日志与持久化](../deploy/README.md#持久化与备份)
- Provider 将安全诊断的阶段、原因码与消息独立于错误大类和发送状态传入 Core;
`attempt.failed` 保存这些字段,最终错误记录优先持久化诊断消息。重试包装不能覆盖底层诊断,
也不能因为错误更详细而改变已有重试安全边界。
也不能因为错误更详细而改变已有重试安全边界
- 默认诊断区分 transport 采集的真实头部与用户 JSON;未知 map 的键和值均不原样保留,
嵌套的同名 header 不获得头部白名单权限。管理端反馈包只导出明确允许的关联、分类、阶段和计时,
不复制 trace 的事件 data,也不因历史 `sanitized` 标记而信任旧正文。
不复制 trace 的事件 data,也不因历史 `sanitized` 标记而信任旧正文
- 真实 secret 不进入普通日志、Debug、fixture 或 audit details;明文只能通过账号导出、Key reveal、
备份设置等明确的敏感 Admin 合同返回。
- OAuth pending flow 使用有期限、带 owner 的一次性 claim;事务成功后才消费,失败释放 claim。
- 在线更新校验 Release host、大小、SHA-256 和归档路径,并只允许同一大版本内更新;更新与回滚的目标发行清单
还要通过全部启用插件的静态兼容检查,文件交换前后复核同一插件配置版本,失败或取消时恢复二进制、Web 与
官方插件目录。
Host 在受理时持久化任务并转交后台执行,任务持有操作锁与终态写入责任,不依赖 HTTP 请求的生命周期。
状态文件记录操作结果,SSE 终态在状态落盘后发送;Host 关闭与任务析构都必须收敛状态。
安装状态由 Host 对照启动时的发行文件指纹、磁盘文件与安装记录校准,历史成功记录本身不构成待重启事实。
二进制、Web 资源、官方插件目录及回滚备份须整体匹配,无法核实的备份不参与回滚。
通道是单次查询或安装的参数,不持久化偏好;默认按运行版本推导,检查和执行共用候选策略,
缓存隔离通道并拒绝过期写回,安装在受理时冻结确认的通道与目标;
Admin/API 只转发策略事实,前端分别展示运行版本、通道候选与待生效版本。
备份设置等明确的敏感 Admin 合同返回
- OAuth pending flow 使用有期限、带 owner 的一次性 claim;事务成功后才消费,失败释放 claim
### 在线更新与回滚
在线更新校验 Release host、大小、SHA-256 和归档路径,并只允许同一大版本内更新;更新与回滚的目标发行清单
还要通过全部启用插件的静态兼容检查,文件交换前后复核同一插件配置版本,失败或取消时恢复二进制、Web 与官方插件目录
Host 在受理时持久化任务并转交后台执行,任务持有操作锁与终态写入责任,不依赖 HTTP 请求的生命周期。
状态文件记录操作结果,SSE 终态在状态落盘后发送,Host 关闭与任务析构都必须收敛状态
安装状态由 Host 对照启动时的发行文件指纹、磁盘文件与安装记录校准,历史成功记录本身不构成待重启事实。
二进制、Web 资源、官方插件目录及回滚备份须整体匹配,无法核实的备份不参与回滚
通道是单次查询或安装的参数,不持久化偏好,默认按运行版本推导。
检查和执行共用候选策略,缓存隔离通道并拒绝过期写回,安装在受理时冻结确认的通道与目标。
Admin/API 只转发策略事实,前端分别展示运行版本、通道候选与待生效版本
### 数据库恢复
PostgreSQL 备份恢复属于人工维护操作。当前没有部署级维护模式开关,需要先停止应用,
离线处理快照中的非终态任务、计划游标和到期清理条件,再重新验证对象存储并恢复计划。
关闭计划开关只阻止新计划任务,不会停止 Worker 的任务恢复和删除流程。
未经核对就启动旧快照,可能触发远端对象删除;操作步骤见 [部署文档](../deploy/README.md#人工恢复数据库)。
未经核对就启动旧快照,可能触发远端对象删除;操作步骤见 [部署文档](../deploy/README.md#人工恢复数据库)
## 12. 修改与验收
变更应落在拥有该事实的边界:协议适配进 API,执行策略进 Core,Provider 差异进对应 Provider,持久化
实现进 Store,生命周期进 Host,管理规则进 Admin。不要用兼容 shim、第二套状态机或跨层旁路绕开 owner。
实现进 Store,生命周期进 Host,管理规则进 Admin。不要用兼容 shim、第二套状态机或跨层旁路绕开 owner
### 后端自审
提交前沿受影响的完整调用链复核最终差异,不能只读新增函数或等待 CI、PR 审查者发现问题。按改动涉及的职责检查:
- **职责归属**:说明业务决定由哪个模块负责、哪些层只传递合同或投影结果,对照 [Workspace 边界](#3-workspace-边界) 与现有同类路径,避免调用方重新解释被调用方已拥有的规则。
- **事实与状态**:同一解析、校验、错误分类和业务规则复用所属模块的实现;检查新增字段、缓存、标志或状态机能否由已有状态推导,避免多个可写来源。必要缓存明确更新、失效与并发约束。
- **失败与资源**:从成功、错误、取消和超时出口核对事务、锁、连接、任务与租约的释放;涉及异步清理时确认返回后的下一次操作不会被尚未完成的清理误挡。
- **并发与副作用**:按实际改动核对 revision、幂等、重试、部分成功与过期结果,明确谁提交状态、谁发布变化、谁负责重试,不在不同层重复补偿或放宽既有隔离合同。
- **必要性与验证**:删除本次引入的重复分支、无用转换、死代码和无明确职责的转发包装;保留特殊分支需说明真实场景。测试覆盖可观察结果与失败条件,不靠增加防御代码或改弱断言让检查通过。
- **职责归属**:说明业务决定由哪个模块负责、哪些层只传递合同或投影结果,对照 [Workspace 边界](#3-workspace-边界) 与现有同类路径,避免调用方重新解释被调用方已拥有的规则
- **事实与状态**:同一解析、校验、错误分类和业务规则复用所属模块的实现;检查新增字段、缓存、标志或状态机能否由已有状态推导,避免多个可写来源。必要缓存明确更新、失效与并发约束
- **失败与资源**:从成功、错误、取消和超时出口核对事务、锁、连接、任务与租约的释放;涉及异步清理时确认返回后的下一次操作不会被尚未完成的清理误挡
- **并发与副作用**:按实际改动核对 revision、幂等、重试、部分成功与过期结果,明确谁提交状态、谁发布变化、谁负责重试,不在不同层重复补偿或放宽既有隔离合同
- **必要性与验证**:删除本次引入的重复分支、无用转换、死代码和无明确职责的转发包装;保留特殊分支需说明真实场景。测试覆盖可观察结果与失败条件,不靠增加防御代码或改弱断言让检查通过
在 PR 中简述与本次变更相关的归属、复用判断和验证结果,标明仍未覆盖的边界;无需复制整张检查清单,也不借自审扩大为无关重构。
在 PR 中简述与本次变更相关的归属、复用判断和验证结果,标明仍未覆盖的边界;无需复制整张检查清单,也不借自审扩大为无关重构
### 验证命令
@@ -786,16 +806,16 @@ RUST_MIN_STACK=16777216 cargo +1.97.0 test --manifest-path backend/Cargo.toml --
线程栈设置与当前 CI 一致。PostgreSQL/Redis 集成测试需按
[迁移文档](../backend/migrations/README.md#本地测试库) 配置专用测试库;未设置环境变量时,本地相关测试会跳过。
其他检查与界面验证按 [贡献与审查](../CONTRIBUTING.md#验证) 执行。
其他检查与界面验证按 [贡献与审查](../CONTRIBUTING.md#验证) 执行
插件 Runtime 的真实子进程与持久化测试使用 `CPR_PLUGIN_TEST_DATABASE_URL` 和
`CPR_PLUGIN_TEST_REDIS_URL` 指向专用实例,密码及隔离要求与上述 Store 测试一致。
设置 `CPR_PLUGIN_TEST_LIVE_HTTP=1` 会额外请求 GitHub 公共 HTTPS API,验证受管出站链路;这不代表模型推理验收。
若测试环境使用 Fake-IP 或私网 DNS,需通过 `CPR_PLUGIN_TEST_LIVE_NETWORK_RANGES` 显式提供逗号分隔的
CIDR 授权。该选项只用于真实网络测试,默认为空,不改变生产网络策略或其他测试的授权。
CIDR 授权。该选项只用于真实网络测试,默认为空,不改变生产网络策略或其他测试的授权
独立插件包的构建、安装与功能验证说明位于 `codex-proxy-plugins` 仓库的 `examples/workbench/README.md`。
功能测试与性能、隔离和平台实测分别记录,不相互替代。
功能测试与性能、隔离和平台实测分别记录,不相互替代
改动使现有说明失真或缺少必要信息时,修订所属文档:用户入口写入根 README,HTTP 合同写入 `docs/api.md`,
部署操作写入 `deploy/README.md`,架构不变量保留在本文。
部署操作写入 `deploy/README.md`,架构不变量保留在本文
+71 -16
View File
@@ -1,4 +1,60 @@
# 源码联调
# 开发与源码联调
本文说明开发环境、服务启动与跨仓库联调,命令均从宿主仓库根目录执行
协作与检查要求见 [贡献与审查](../CONTRIBUTING.md),部署实例的配置与数据维护见 [部署与运维](../deploy/README.md)
| 任务 | 入口 |
| --- | --- |
| 修改宿主前后端 | [环境与依赖](#环境与依赖) → [启动宿主](#启动宿主) |
| 同时修改 UI 或插件 | [源码联调](#源码联调) → [启动与检查](#启动与检查) |
| 验证本地 SDK | [本地 SDK 验证](#本地-sdk-验证) |
| 提交跨仓库改动 | [编辑与提交](#编辑与提交) |
## 环境与依赖
需要 Node.js 24、pnpm、[Rust 工具链](architecture.md#验证命令)、PostgreSQL 和 Redis
各前端的 `packageManager` 固定 pnpm 版本,宿主 CI 和 Docker 从 `frontend/package.json` 读取
```bash
git clone https://github.com/zyycn/codex-proxy-rs.git
cd codex-proxy-rs
pnpm --dir frontend install --frozen-lockfile
```
前端依赖、锁文件与 ESLint 配置由 `frontend/` 管理,根目录不维护 Node 包
- **版本固定**:`@codex-proxy/ui` 使用固定 GitHub 标签或提交,锁文件记录实际提交、源码归档地址及完整性摘要
- **安装与构建**:pnpm 按 UI 自身锁文件准备依赖,执行 `prepack` 生成 JS、CSS 和类型声明,普通 `dev`、`build`、发行 CI 和 Docker 使用编译产物,无需初始化子模块
- **构建许可**:`frontend/pnpm-workspace.yaml` 的 `allowBuilds` 按仓库放行自有 UI 包脚本,升级时核对源码并更新锁文件,无需逐次修改许可,不开放全部依赖脚本
## 启动宿主
使用源码仓库自带的 Compose 与配置模板,按[手动安装](../deploy/README.md#手动安装)中的配置与权限要求准备 `deploy/config.yaml` 和 `.runtime/` 目录
数据库及 Redis 可由 Compose 启动:
```bash
docker compose -f deploy/compose.yaml up -d postgres redis
cargo run --manifest-path backend/Cargo.toml -p codex-proxy-rs
```
后端从当前目录向上查找 `deploy/config.yaml`,本机运行时数据库与 Redis 地址应指向可访问的本机端口
`host.runtime_data_dir` 和日志的相对路径以该配置文件所在目录解析
另开终端启动管理端:
```bash
pnpm --dir frontend dev
```
后端代理由 `frontend/vite.config.ts` 配置,验证 WebSocket 时直接连接后端
前后端检查见 [贡献与审查](../CONTRIBUTING.md#验证),数据库集成测试使用[专用测试库](../backend/migrations/README.md#本地测试库)
## 源码联调
宿主、组件库和官方插件保留独立仓库,通过 Git 子模块在同一目录开发:
@@ -8,9 +64,7 @@
| `modules/ui` | `codex-proxy-ui`,共用组件与主题 |
| `modules/plugins` | `codex-proxy-plugins`,官方插件示例 |
需要 Node.js 24、pnpm,以及 [宿主约定的 Rust 工具链](architecture.md#12-修改与验收)。各前端的 `packageManager` 分别固定 pnpm 版本。
## 检出与安装
### 检出与安装
首次检出可同时下载子模块:
@@ -19,7 +73,7 @@ git clone --recurse-submodules https://github.com/zyycn/codex-proxy-rs.git
cd codex-proxy-rs
```
已有仓库运行 `git submodule update --init --recursive`。子模块固定到具体提交;拉取宿主更新后,同样用此命令对齐。执行前先保存子模块中的改动,避免切换正在开发的分支。
已有仓库运行 `git submodule update --init --recursive`。子模块固定到具体提交;拉取宿主更新后,同样用此命令对齐。执行前先保存子模块中的改动,避免切换正在开发的分支
在宿主根目录安装各项目依赖:
@@ -29,7 +83,7 @@ pnpm --dir frontend install --frozen-lockfile
pnpm --dir modules/plugins/examples/workbench/frontend install --frozen-lockfile
```
## 启动与检查
### 启动与检查
在各自终端启动所需服务:
@@ -39,16 +93,17 @@ pnpm --dir modules/plugins/examples/workbench/frontend dev:source # 插件页
pnpm --dir modules/ui dev # 组件库交互示例
```
管理端沿用 `frontend/vite.config.ts` 的后端代理,需按原有方式启动网关。插件独立预览的宿主桥使用模拟数据;已安装插件读取包内静态资源,修改后仍需重新构建、打包和安装。
管理端需要[已启动的网关](#启动宿主)。插件独立预览的宿主桥使用模拟数据;已安装插件读取包内静态资源,修改后需重新构建、打包和安装
`dev:source` 使用 Vite 的 `source` 模式,由 `@codex-proxy/ui/vite` 的 `CodexProxyUI` 适配器将公开入口解析到 `modules/ui`,并统一处理共享依赖、源码预构建排除与开发服务的文件访问范围。入口映射以 UI 包 `exports` 中的 `codex-proxy-source` 条件为准,由 Vite 热更新。插件单独检出时仍用普通 `dev`;`source` 模式需要上述子模块目录结构。
`dev:source` 使用 Vite 的 `source` 模式,由 `@codex-proxy/ui/vite` 的 `CodexProxyUI` 适配器将公开入口解析到 `modules/ui`,并统一处理共享依赖、源码预构建排除与开发服务的文件访问范围。入口映射以 UI 包 `exports` 中的 `codex-proxy-source` 条件为准,由 Vite 热更新。插件单独检出时仍用普通 `dev`;`source` 模式需要上述子模块目录结构
```bash
pnpm --dir frontend build:source
pnpm --dir modules/plugins/examples/workbench/frontend build:source
```
`build:source` 用各项目的 `tsconfig.source.json` 检查源码类型,管理端构建到 `frontend/node_modules/.vite/source-dist`,示例构建到其前端项目的 `.vite/source-dist`,不改写正式依赖、锁文件或 `dist`。正常 `dev`、`build`、宿主发行 CI 和 Docker 仍消费锁定的 UI 包,无需初始化子模块。CI 另有源码联调检查,显式检出子模块、核对 UI 提交锁定并验证这两个命令。
`build:source` 用各项目的 `tsconfig.source.json` 检查源码类型,管理端构建到 `frontend/node_modules/.vite/source-dist`,示例构建到其前端项目的 `.vite/source-dist`,不改写正式依赖、锁文件或 `dist`。
源码联调 CI 显式检出子模块、核对 UI 提交锁定并验证这两个命令
### 本地 SDK 验证
@@ -71,12 +126,12 @@ pnpm --dir modules/plugins/examples/workbench/frontend build:source
## 编辑与提交
直接打开宿主目录即可开发,分别在对应仓库目录操作 Git。
直接打开宿主目录即可开发,分别在对应仓库目录操作 Git
1. 在要修改的子模块内先创建分支,例如 `git -C modules/ui switch -c feat/select`。初次初始化的子模块通常处于 detached HEAD。
2. 在子模块运行自身检查,并在宿主根目录运行所需联调检查。
3. 分别提交、推送并合并组件库或插件 PR。宿主只记录子模块提交号,不能替代子仓库的提交与推送。
4. UI 更新后,将管理端和插件页面的 UI 依赖同步到同一固定标签或提交,重新生成各自锁文件,再更新宿主的子模块指针;CI 会核对三处是否一致。
5. 提交宿主改动及 `modules/ui`、`modules/plugins` 指针。指针必须指向远程可取得的提交,不能只存在于开发机。
1. 在要修改的子模块内先创建分支,例如 `git -C modules/ui switch -c feat/select`。初次初始化的子模块通常处于 detached HEAD
2. 在子模块运行自身检查,并在宿主根目录运行所需联调检查
3. 分别提交、推送并合并组件库或插件 PR。宿主只记录子模块提交号,不能替代子仓库的提交与推送
4. UI 更新后,将管理端和插件页面的 UI 依赖同步到同一固定标签或提交,重新生成各自锁文件,再更新宿主的子模块指针;CI 会核对三处是否一致
5. 提交宿主改动及 `modules/ui`、`modules/plugins` 指针。指针必须指向远程可取得的提交,不能只存在于开发机
更新子模块指针不会自动升级消费方依赖。正式依赖不能写成开发机路径或浮动分支;SDK 仍由插件仓库固定到包含所需合同的宿主提交。
更新子模块指针不会自动升级消费方依赖。正式依赖不能写成开发机路径或浮动分支;SDK 仍由插件仓库固定到包含所需合同的宿主提交
+43 -30
View File
@@ -1,7 +1,9 @@
# 插件使用
插件“制品”是按 SHA-256 固定的代码、版本和访问域;“配置”是运行实例,保存普通设置、敏感设置和功能范围。
日常使用只维护一个当前配置,同一插件最多启用一个实例。访问域随制品接受,不能在设置中另行增删。
日常使用只维护一个当前配置,同一插件最多启用一个实例。访问域随制品接受,不能在设置中另行增删
[安装](#安装) · [访问域](#访问域) · [配置与启用](#配置与启用) · [使用](#使用) · [更新与卸载](#更新回退与卸载)
```mermaid
flowchart LR
@@ -12,9 +14,9 @@ flowchart LR
D -->|缺少必填项| F[补充配置后启用]
```
宿主支持 SDK `0.1.0`、`manifestVersion: 1` 和进程协议 `1`,只接受当前合同的插件包。
正文按块传输,插件通信不额外限制正文总量。宿主、SDK 和打包 CLI 必须使用同一套合同;
现有开发插件需要重新构建并打包,不能只修改包内版本字段。包必须同时满足当前平台和宿主兼容范围。
安装包必须满足宿主支持的 [SDK 合同](../backend/crates/gateway-plugin/sdk/README.md)、目标平台和宿主版本范围
合同不匹配时,需由作者使用匹配的 SDK 与 CLI 重新构建打包,不能只修改包内版本字段
## 安装
@@ -25,11 +27,11 @@ flowchart LR
- **GitHub**:填写仓库地址或 `owner/repo`;标签留空查询最新稳定版,预发行版须指定标签
公开来源无需认证。私有来源可选择或创建独立下载认证;认证按来源和用途限制,不会交给插件进程。
下载代理独立选择,默认直连,代理失败不会回退直连。SHA-256 用于固定内容,不证明发布者身份。
下载代理独立选择,默认直连,代理失败不会回退直连。SHA-256 用于固定内容,不证明发布者身份
“下一步”只解析和校验包,不保存制品。检查名称、发布者、版本、平台、摘要和访问域后确认安装;上传、URL
和 GitHub 安装会接受该摘要声明的访问域。随宿主发行物导入的官方制品会显示为“待接受”,也必须先查看访问域并确认,
未接受的制品不能创建或编辑配置。相同摘要的确认可安全重试。
未接受的制品不能创建或编辑配置。相同摘要的确认可安全重试
确认安装后,若该插件还没有任何配置,宿主会:
@@ -38,11 +40,11 @@ flowchart LR
3. 配置完整时直接启用并发布;仅缺少必填项时保存为停用并打开配置页
类型错误、非法 schema 或把敏感值混入普通配置会直接拒绝,不会伪装成“待配置”。更新已有插件时不会创建另一套配置,
管理端在包安装完成后提示确认切换,检查通过后应用目标版本,不兼容时保留原版本并打开待调整的设置。
管理端在包安装完成后提示确认切换,检查通过后应用目标版本,不兼容时保留原版本并打开待调整的设置
### 访问域
安装确认只使用以下稳定访问域;具体调用仍受贡献项、当前调用上下文和宿主策略限制。
安装确认只使用以下稳定访问域;具体调用仍受贡献项、当前调用上下文和宿主策略限制
| 标识 | 安装摘要中的含义 |
| --- | --- |
@@ -56,30 +58,32 @@ flowchart LR
| `keys` | 创建绑定插件自有分组的 Client Key,不读取密钥明文 |
访问域不是操作列表或资源白名单。实例配置不能扩大、缩小或伪造制品声明;切换版本后使用目标制品已经接受的精确声明。
插件进程与网关使用相同的系统身份,接受访问域不是操作系统沙箱,因此只安装可信来源的包。
插件进程与网关使用相同的系统身份,接受访问域不是操作系统沙箱,因此只安装可信来源的包
## 配置与启用
在插件详情中直接启用或停用,设置会保留,无需重新填写。插件没有可设置的参数或认证映射时不显示设置入口。
在插件详情中直接启用或停用,设置会保留,无需重新填写。插件没有可设置的参数或认证映射时不显示设置入口
“设置”按当前包的 schema 显示参数,敏感字段单独保存且不回显。首次补齐必填项后“保存并启用”;其他设置修改保持
原有启停状态。请求处理能力随包自动注册,无需在安装时逐项勾选;有特殊需求时,可在“高级设置”中调整请求作用范围。
客户端 Key、账号分组、Provider 和模型等范围同时满足才生效,同一范围内任一值匹配即可。
客户端 Key、账号分组、Provider 和模型等范围同时满足才生效,同一范围内任一值匹配即可
范围数组为空表示不限制该维度。自动创建的默认绑定不预先绑定 Client Key;普通网关请求仍以实际请求的 Key 和冻结策略
执行。管理页面和命令行不保存功能 binding,页面调用模型时由页面每次明确选择一个 Client Key ID,宿主按当前 Key 状态、
范围、预算、账本和普通请求链执行,插件看不到 Key 明文。
范围、预算、账本和普通请求链执行,插件看不到 Key 明文
`frontend_authentication` 不会随安装自动接管入口。启用它时必须明确配置外部 principal 到既有 Client Key 的映射;
已停用或删除的 Key 不会因此恢复访问。已有多套配置保留在“历史配置”中,选择使用时确认替换当前配置;不再提供新增配置入口,
也不会自动删除历史配置、密钥或私有数据。升级前已经启用多套的插件会提示整理,宿主不会擅自选择停用对象,后续启用或更新
必须收敛到一套配置。
已停用或删除的 Key 不会因此恢复访问
存在多套配置时,可在“历史配置”中选择并确认替换当前配置,管理端只维护一个当前配置,不提供新增配置入口
若同一插件已有多个启用实例,需先整理为一套才能继续启用或更新,宿主不会自动停用实例或删除配置、密钥与私有数据
声明自动维护的插件可自行准备已授权的分组和 Key,无需提供管理员密码。停用会停止维护,但保留这些资源及其原生启用状态;
删除插件配置也不自动删除资源。需要撤销 Key 访问时,在 Key 管理中停用或删除相应 Key。
删除插件配置也不自动删除资源。需要撤销 Key 访问时,在 Key 管理中停用或删除相应 Key
“已启用”表示配置已经发布,不是外部服务的健康证明。“等待生效”表示候选仍在准备;“需要处理”会显示发布或运行失败原因。
并发编辑冲突时应重新加载当前 revision 后确认,不能覆盖另一窗口的修改。
并发编辑冲突时应重新加载当前 revision 后确认,不能覆盖另一窗口的修改
## 使用
@@ -95,25 +99,34 @@ flowchart LR
| 命令行 | 在宿主环境运行 `codex-proxy-rs plugin <配置 ID> --help` |
页面模型桥要求管理会话、当前实例/制品/revision 和 `models` 访问域同时有效。停用实例、切换版本或 revision、
撤销相应访问资格后,宿主会在等待首帧和交付响应期间持续复核并取消请求;它不会把管理 Cookie、Key 明文或认证头交给插件。
撤销相应访问资格后,宿主会在等待首帧和交付响应期间持续复核并取消请求;它不会把管理 Cookie、Key 明文或认证头交给插件
## 更新、回退与卸载
- **检查更新**:GitHub 按保存的策略查询发布,URL 下载并解析当前地址的包;本地上传不支持查询。检查本身不会安装,GitHub 标签也不等于包内版本。
- **更新插件**:选择新包并确认安装,再确认切换到目标版本。保留已填写的值,补充新版本缺失的默认值;字段改名、类型改变或删除等不兼容情况由用户调整,不猜测或静默清空参数与密钥。
- **切换版本**:在版本列表中选择已安装版本,确认当前版本与目标版本后执行。若该版本已有恢复设置,使用它上次启用时提交的参数、密钥与功能范围;否则从当前设置生成草稿。切换保持启停状态,目标访问域以已接受的包为准。停用的历史配置引用旧版本时显示“历史配置”,不表示该版本正在运行。
- **回退版本**:只列出同一插件已安装、已接受且保留恢复设置的较早版本,恢复对应设置后重新执行兼容检查。
### 版本与设置
每个精确插件包保留最近一次启用时提交的配置快照,停用期间未完成的草稿不会覆盖恢复点。升级宿主时会为已有启用配置建立
恢复点,无法补回此前已经丢失的旧版本设置。修改下载来源会先单独保存,取消后续安装也会保留已保存来源。当前浏览器标签页
记住未提交的安装参数和凭据引用,不保存认证明文或上传文件。
- **检查更新**:GitHub 按保存的策略查询发布,URL 下载并解析当前地址的包;本地上传不支持查询。检查本身不会安装,GitHub 标签也不等于包内版本
- **更新插件**:选择新包并确认安装,再确认切换到目标版本。保留已填写的值,补充新版本缺失的默认值;字段改名、类型改变或删除等不兼容情况由用户调整,不猜测或静默清空参数与密钥
- **切换版本**:在版本列表中选择已安装版本,确认当前版本与目标版本后执行。若该版本已有恢复设置,使用它上次启用时提交的参数、密钥与功能范围;否则从当前设置生成草稿。切换保持启停状态,目标访问域以已接受的包为准。停用的历史配置引用旧版本时显示“历史配置”,不表示该版本正在运行
- **回退版本**:只列出同一插件已安装、已接受且保留恢复设置的较早版本,恢复对应设置后重新执行兼容检查
每个精确插件包保留最近一次启用时提交的配置快照,停用期间未完成的草稿不会覆盖恢复点
宿主升级仅能为当前启用配置建立恢复点,无法还原未保存或已丢失的其他版本设置
修改下载来源会先单独保存,取消后续安装也会保留已保存来源。当前浏览器标签页
记住未提交的安装参数和凭据引用,不保存认证明文或上传文件
设置恢复不等于私有数据恢复。私有数据迁移仍由插件声明的迁移能力处理,不兼容且无迁移路径时不能切换;迁移失败会尝试
恢复原版本,若恢复会覆盖并发修改则保持停用并提示处理。配置检查或候选准备失败不会覆盖当前设置。
恢复原版本,若恢复会覆盖并发修改则保持停用并提示处理。配置检查或候选准备失败不会覆盖当前设置
### 故障处理
宿主重启时逐个恢复已启用插件。不兼容、启动失败的实例显示异常并保留设置,管理员可重新启动、停用或切换版本;
其他插件、原生转发和管理端继续运行。运行中的插件进程退出时,也不会撤销整个宿主的运行配置。
命中故障插件绑定的请求仍遵循既有拒绝或委托策略;入口认证插件失败不会绕过认证。
命中故障插件绑定的请求仍遵循既有拒绝或委托策略;入口认证插件失败不会绕过认证
### 停用与删除
| 操作 | 删除什么 | 保留什么 |
| --- | --- | --- |
@@ -123,8 +136,8 @@ flowchart LR
| 卸载插件 | 确认范围内的配置、数据、版本、来源规则及已无引用的下载认证 | 其他插件仍使用的下载认证、审计记录 |
卸载不是跨多个请求的事务;失败时会显示已完成结果,可重试剩余步骤,不会扩大到确认后新增的配置。只想暂时停用时不要卸载。
完整卸载后重新安装可选择新的来源,不再沿用旧来源限制。独立创建但未被删除版本使用的下载认证不会被顺带删除。
需要保留数据时,删除前按[部署、备份与恢复](../deploy/README.md)保存备份。
完整卸载后重新安装可重新选择来源。独立创建但未被删除版本使用的下载认证不会被顺带删除。
需要保留数据时,删除前按[部署、备份与恢复](../deploy/README.md)保存备份
开发插件见 [SDK](../backend/crates/gateway-plugin/sdk/README.md) 与 [插件 CLI](../backend/apps/plugin-cli/README.md);
完整示例在独立的 `codex-proxy-plugins` 仓库维护。HTTP 字段与限制见 [API 参考](api.md#12-插件管理)。
完整示例在独立的 `codex-proxy-plugins` 仓库维护。HTTP 字段与限制见 [API 参考](api.md#12-插件管理)
+92 -84
View File
@@ -5,36 +5,36 @@
# 管理端主题系统
浅色、深色、预置配色与自定义主题的配置和实现说明。
浅色、深色、预置配色与自定义主题的配置和实现说明
[设计原则](#设计原则) · [Token 模型](#token-模型) · [运行时架构](#运行时架构) · [主题编辑器](#主题编辑器) · [扩展指南](#扩展指南)
[界面约定](#界面文案与信息层级) · [Token 模型](#token-模型) · [运行时架构](#运行时架构) · [主题编辑器](#主题编辑器) · [扩展指南](#扩展指南)
</div>
在管理端「主题」中调整颜色、字号、圆角和组件样式,保存后应用到当前浏览器。
设置保存在浏览器本地,不会同步到服务器或其他设备。只想换配色时,选择预置主题即可。
设置保存在浏览器本地,不会同步到服务器或其他设备。只想换配色时,选择预置主题即可
下文说明实现和扩展方式。颜色使用 Ant Design 十阶色板与项目的明暗角色规则,
中性表面从背景和文字 Seed 派生;Vue 组件通过 CSS Variables 与 Tailwind CSS 4 使用这些值。
项目不依赖 Ant Design 组件库或 CSS-in-JS。
项目不依赖 Ant Design 组件库或 CSS-in-JS
> [!IMPORTANT]
> 主题的目标是调整颜色、密度、圆角与层级,不改变现有页面结构和产品语义。默认主题保留既有表面层级,并满足正常文字的可读性约束;
> 成功、警告、错误和业务数据色不会因为品牌色变化而失去原有含义。
> 成功、警告、错误和业务数据色不会因为品牌色变化而失去原有含义
## 设计原则
- **Seed 与 Map 可追踪**:主色和功能色均按 Ant Design 从 Seed 派生 P1-P10;浅色 P6 等于 Seed,深色 P6
经过暗色色板适配。全局 `colorPrimary` 保留原始 Seed;功能色 Base 保留 P6。文字和按钮组件可在派生层做对比度校正,Seed 不随之改写。
- **主色与表面分离**:品牌色负责交互和强调,页面、容器、浮层与文字由独立的背景和文本 Seed 派生。
经过暗色色板适配。全局 `colorPrimary` 保留原始 Seed;功能色 Base 保留 P6。文字和按钮组件可在派生层做对比度校正,Seed 不随之改写
- **主色与表面分离**:品牌色负责交互和强调,页面、容器、浮层与文字由独立的背景和文本 Seed 派生
- **语义独立**:`success`、`warning`、`error`、`info` 不随品牌色隐式改变;未显式配置 `link`
时跟随 `primary`,显式配置后独立派生。
- **无边设计**:默认依靠表面色差、间距和轻阴影表达层级;边框只用于焦点、错误和必要分隔。
- **运行时可定制**:用户输入在浏览器中实时派生,因此使用 CSS Variables,不使用构建时 SCSS 变量。
- **单一事实源**:Store 只保存最小配置,所有 Map、Alias 与未覆盖的 Component Token 均由纯函数生成。
时跟随 `primary`,显式配置后独立派生
- **无边设计**:默认依靠表面色差、间距和轻阴影表达层级;边框只用于焦点、错误和必要分隔
- **运行时可定制**:用户输入在浏览器中实时派生,因此使用 CSS Variables,不使用构建时 SCSS 变量
- **单一事实源**:Store 只保存最小配置,所有 Map、Alias 与未覆盖的 Component Token 均由纯函数生成
- **行为透明**:任意自定义 Seed 都进入统一算法,页面只选择角色。功能色 `text` 与 `on-container` 分别依据中性
Surface 和自身 Container 三态做对比度保护,不使用固定色相的文字锚点覆盖自定义 Seed;交互仍尊重
`prefers-reduced-motion`。
`prefers-reduced-motion`
## 界面文案与信息层级
@@ -93,11 +93,13 @@ flowchart LR
独立仓库 `codex-proxy-ui` 是管理端和官方插件页面的共享组件源码;组件不得反向依赖管理端 Store、路由或 API。管理端通过 `@codex-proxy/ui` 公开入口消费组件,插件页面把组件和样式编译进自身静态资源,不在运行时借用宿主模块。
插件页面的标题与副标题由宿主呈现,内容区只渲染业务;主题变化通过宿主桥同步,接入方式见
[SDK 页面与宿主桥](../backend/crates/gateway-plugin/sdk/docs/capabilities.md#页面与宿主桥)。
[SDK 页面与宿主桥](../backend/crates/gateway-plugin/sdk/docs/capabilities.md#页面宿主桥-v2)。
`theme/` 根目录只保留公开入口 `index.ts` 和唯一类型文件 `types.ts`;内部实现按 `core/`、`derive/`、`runtime/` 分层,不增加嵌套 barrel。
纯派生模块不访问 DOM,`theme/runtime/browser.ts` 不包含派生规则,Theme Store 不复制算法。
普通 Map 字段按 camelCase → kebab-case 统一生成 `--cp-*`;Semantic 与 Preset Color 仅维护各自的短角色表。
`ThemeTokenName` 在 `types.ts` 中由 Map 契约推导,无需手写重复的联合类型和对象映射。
`ThemeTokenName` 在 `types.ts` 中由 Map 契约推导,无需手写重复的联合类型和对象映射
下文未带宿主路径的 `theme/`、`styles/` 均指 `codex-proxy-ui/src/` 下的目录,联调入口见[开发文档](development.md#源码联调)
## Token 模型
@@ -132,10 +134,12 @@ interface ThemeCustomization {
| 组件尺寸 | `tableRowHeight`、`cardBorderRadius` |
持久化键为 `codex-proxy-rs-theme`。Store 不保存完整色板或 CSS Variables,损坏的模式、颜色与自定义值会在
初始化时规范化并回退到默认配置。
初始化时规范化并回退到默认配置
### Map Token
#### 品牌与功能色
品牌色和功能色使用 `@ant-design/colors` 从一个 Seed 生成十阶色板。本文 P1-P10 指生成器返回数组的第 1-10 项,
不是 Ant Design 组件库再次重映射后的角色编号。项目的映射集中在 `theme/derive/roles.ts`:
@@ -153,14 +157,18 @@ interface ThemeCustomization {
Container 保证 3:1。功能色 Hover/Active 相对中性 Container 保证 3:1,Base 不做该校正。`text` 依据页面、
容器、浮层和交互填充等中性 Surface 校正;`on-container` 单独依据语义 Container 的默认、Hover、Active 三态
校正到至少 4.5:1,避免为了彩色底对比度而削弱中性表面上的颜色辨识度。
对比度校正由 UI 库的主题算法统一执行;互相矛盾的自定义前景/背景组合不保证全部达标。
对比度校正由 UI 库的主题算法统一执行;互相矛盾的自定义前景/背景组合不保证全部达标
分类、图表与数据强调继续使用 Ant Design Preset Color 的角色结构。Blue、Green、Orange、Red 分别复用
#### 分类与图表色
分类、图表与数据强调使用 Ant Design Preset Color 的角色结构。Blue、Green、Orange、Red 分别复用
`colorInfo`、`colorSuccess`、`colorWarning`、`colorError`,保证通用彩色与可编辑语义 Seed 同源;没有语义对应的
Cyan、Purple 从 `@ant-design/colors` 的 `presetPrimaryColors` 取得 Seed。
Preset 的实心色使用 P6,普通 Container、Strong Container 与边界按 recipe 权重混合;浅色文字取 P7,深色取 P8
并保留 HSL 最低明度 0.7。`text` 相对中性 Container 校正,`on-container` 同时相对普通和 Strong Container
校正到 4.5:1;彩色容器里的文字与图标应使用 `on-container`,普通数值与标签不直接使用 `solid`。
校正到 4.5:1;彩色容器里的文字与图标应使用 `on-container`,普通数值与标签不直接使用 `solid`
#### 中性表面与文字
背景与文本 Seed 进入独立的 Surface Map,生成:
@@ -173,13 +181,13 @@ Preset 的实心色使用 P6,普通 Container、Strong Container 与边界按
画像,自定义主色则只向通用中性基线注入少量色温。默认浅色采用中性锚点,默认深色使用 HSL 色调派生;带色温
主题按外观距离平滑过渡到背景和文字 Seed 的 RGB 混色结果。稳定锚点用于 Surface 与 Shadow,Component 从角色派生,
所有预置、自定义 Seed 和用户覆盖仍进入同一条算法,不在页面或组件中追加 HEX 特判。
Input、阴影与其他 Component Token 从 Fill、Surface、Primary 和 Semantic 派生,不在常量文件维护整套颜色表。
Input、阴影与其他 Component Token 从 Fill、Surface、Primary 和 Semantic 派生,不在常量文件维护整套颜色表
正常文字同时检查 Layout、Container、Elevated、文字交互背景、三级 Fill,以及控件透明填充在各宿主上的合成色
和浅色的选中 / 选中 Hover 背景。正文、标题和 Secondary 至少 7:1,
Tertiary 至少 5.5:1,Quaternary 至少 4.5:1;这些是相对全部上述表面的最低目标,对 Container 的实测比值通常更高。
Disabled 保留独立的弱化颜色,不承担正常信息。控件填充由文字 Seed 和透明度派生,placeholder 继续消费
Quaternary;主按钮白字与功能色文字遵循各自的容器配对规则。
Quaternary;主按钮白字与功能色文字遵循各自的容器配对规则
### Alias Token
@@ -199,11 +207,11 @@ Alias 按视觉角色命名,使用 `--cp-` 命名空间;整体分层对齐 A
| 预设彩色 | `--cp-color-{blue,cyan,green,orange,purple,red}-{container,container-strong,border,solid,text,on-container}` | 分类标签与数据强调 |
Alias 不在页面中追加修色;中性与彩色文字在派生层执行各自的背景配对约束。Component
Token 直接覆盖时不会自动重算同组件的其他状态;需要保持梯度关系时应修改 Seed,而不是逐个覆盖 Map Token。
Token 直接覆盖时不会自动重算同组件的其他状态;需要保持梯度关系时应修改 Seed,而不是逐个覆盖 Map Token
### Component Token
当组件需要独立演进时,使用 `component[-part][-variant][-state]-property` 命名,不创造含义重复的全局别名。
当组件需要独立演进时,使用 `component[-part][-variant][-state]-property` 命名,不创造含义重复的全局别名
| 组件 | 代表 Token |
| --- | --- |
@@ -229,14 +237,14 @@ Token 直接覆盖时不会自动重算同组件的其他状态;需要保持
| 错误与焦点 | Hover / Focus 不覆盖错误反馈;焦点外圈不受装饰性阴影强度影响 |
| 浮层头部 | 浅色取 Secondary Fill,深色取 Tertiary Fill;箭头与头部使用同一 Token,不借用表格背景 |
明暗模式分别派生,但共用组件合同。表格背景过渡覆盖普通行、选中行和固定列,尊重减少动态效果偏好。
明暗模式分别派生,但共用组件合同。表格背景过渡覆盖普通行、选中行和固定列,尊重减少动态效果偏好
Theme Editor 只开放真正由对应组件消费的 Component Token。全局 Alias 不放进组件目录,避免一次覆盖同时改变
多个无关组件。
多个无关组件
应用内品牌图标使用 [`AppBrandMark.vue`](../frontend/src/components/AppBrandMark.vue):浅色模式取
`colorBgSpotlight`,深色模式取 `colorBgElevated`,保持中性暗面与白色图形;浏览器 favicon 继续使用固定黑白
图标,不随主题改变,由 `frontend/public/favicon.svg` 提供,页面通过 `/favicon.svg` 引用。
图标,不随主题改变,由 `frontend/public/favicon.svg` 提供,页面通过 `/favicon.svg` 引用
### 通用颜色消费
@@ -244,7 +252,7 @@ Theme Editor 只开放真正由对应组件消费的 Component Token。全局 Al
Purple Strong Container 或 Purple Solid;同一组颜色仍由运行时色板统一派生。`styles/tokens.css` 只保留白色、透明色与
作用域 `color-scheme`,不保存可换肤值或业务标识色。账户活动热力图以 Success Container 为起点、Success Solid
为终点生成中间密度;浅色与暗色使用同一规则。
图表数据系列也只引用通用 Preset Color Token。
图表数据系列也只引用通用 Preset Color Token
## 预置主题
@@ -256,7 +264,7 @@ Purple Strong Container 或 Purple Solid;同一组颜色仍由运行时色板
| `graphite` | 石墨 | `#525B66` | 近单色的灰色强调 |
预置主题改变品牌主色,并可提供与该品牌匹配的浅色 / 深色背景和文字 Seed;不改变字号、密度、圆角或布局。
所有预置与自定义主题走同一条 Surface、Map、Alias 与 Component 派生链路,不存在页面级特判。
所有预置与自定义主题走同一条 Surface、Map、Alias 与 Component 派生链路,不存在页面级特判
## 运行时架构
@@ -273,7 +281,7 @@ createApp
→ app.mount('#app')
```
Theme Store 统一读取持久化配置并初始化主题,首个 Vue 组件渲染时即可使用主题变量。
Theme Store 统一读取持久化配置并初始化主题,首个 Vue 组件渲染时即可使用主题变量
### CSS Variables 提交
@@ -290,24 +298,24 @@ Theme Store 统一读取持久化配置并初始化主题,首个 Vue 组件渲
```
根元素只保留 `data-theme` 与 `data-theme-color` 状态,Token 统一写入运行时样式表。
全局弹窗通过 Teleport 挂到 `body` 后仍继承根变量。
全局弹窗通过 Teleport 挂到 `body` 后仍继承根变量
> [!NOTE]
> Theme Editor 预览是例外:草稿 Token 以内联变量写在影子环境的局部根节点上,仅影响预览,不污染已保存主题。
> Theme Editor 预览是例外:草稿 Token 以内联变量写在影子环境的局部根节点上,仅影响预览,不污染已保存主题
### 切换与图表
- 用户主动切换时,以点击位置为圆心运行 View Transition;不支持时使用 180ms 颜色过渡。
- 初始化、系统主题变化或减少动态效果时直接提交,不播放扩散动画。
- 每次有效提交只增加一次 `themeRevision`,相同签名不会重复刷新。
- `useThemeColor()` 在正式页面读取根 CSS Variables,在影子预览中优先读取注入的局部 Token。
- 图表 Option 在主题 revision 或预览 Token 改变后重算,`BaseChart` 通过 `setOption` 更新现有 Canvas。
- 用户主动切换时,以点击位置为圆心运行 View Transition;不支持时使用 180ms 颜色过渡
- 初始化、系统主题变化或减少动态效果时直接提交,不播放扩散动画
- 每次有效提交只增加一次 `themeRevision`,相同签名不会重复刷新
- `useThemeColor()` 在正式页面读取根 CSS Variables,在影子预览中优先读取注入的局部 Token
- 图表 Option 在主题 revision 或预览 Token 改变后重算,`BaseChart` 通过 `setOption` 更新现有 Canvas
## 主题编辑器
主题编辑器位于一级路由 `/theme`,采用一屏工作台:左侧编辑,右侧预览,顶部保留全局操作。桌面端高度固定为
`100dvh - 3rem`,切换编辑层级或预览类型时不改变外框高度。窄屏头部操作允许自然换行;手机端隐藏实时预览,
只保留 Token 编辑,避免在有限宽度内渲染不可操作的缩放画板。
只保留 Token 编辑,避免在有限宽度内渲染不可操作的缩放画板
### 编辑能力
@@ -319,41 +327,41 @@ Theme Store 统一读取持久化配置并初始化主题,首个 Vue 组件渲
| 组件 | Action、Form、Surface、Data Display、Navigation、Layout |
| 工作流 | 搜索、单项恢复、撤销草稿、恢复默认、保存并应用 |
Component Token 只开放白名单字段,且不允许在组件目录覆盖全局 Alias。未覆盖项始终继续使用全局 Seed 与
Component Token 只开放白名单字段,且不允许在组件目录覆盖全局 Alias。未覆盖项使用全局 Seed 与
Alias 算法,避免主题配置逐渐退化成一份无法维护的完整 CSS 快照。
Input、Button Secondary 与 Icon Button Secondary 的三个背景 Token 支持 HEX Alpha 编辑和保存;Seed、容器与表格背景仍只接受实色。
透明填充先合成再参与默认文字对比度计算,用户显式覆盖 Component Token 时仍由用户负责整组状态的搭配。
透明填充先合成再参与默认文字对比度计算,用户显式覆盖 Component Token 时仍由用户负责整组状态的搭配
### 草稿与保存
编辑器维护 `saved` 与 `draft` 两份状态:
1. 输入只更新草稿和局部预览。
2. 修改计数按模式、主题色、Seed、组件值和 Token override 分项统计。
3. “撤销草稿”恢复到最近一次已保存配置。
4. “保存并应用”先规范化草稿,再原子更新 Theme Store。
1. 输入只更新草稿和局部预览
2. 修改计数按模式、主题色、Seed、组件值和 Token override 分项统计
3. “撤销草稿”恢复到最近一次已保存配置
4. “保存并应用”先规范化草稿,再原子更新 Theme Store
### 隔离预览
[`ThemePreviewScope.vue`](../frontend/src/views/theme/components/ThemePreviewScope.vue) 创建开放 Shadow Root,复制应用
样式,并把预览内容 Teleport 到影子根中。预览可独立切换浅色和深色,不受外层主题影响。
样式,并把预览内容 Teleport 到影子根中。预览可独立切换浅色和深色,不受外层主题影响
- **首页画板**复用真实 `DashboardContent` 和固定 fixture,不请求接口,也不启动自动刷新。
- **组件概览**展示基础组件、表格、空状态、骨架、浮层和菜单等关键状态。
- 画板固定为 `1600 × 1808`,使用 CSS `zoom` 重排,不使用 `transform: scale()` 长期缩放文字。
- 空白区域可拖拽,滚轮以指针为锚点缩放,并提供缩小、100%、放大和适应画板操作。
- 编辑面板和组件概览统一使用 `BaseScrollbar`,滚动条空闲时自动隐藏。
- **首页画板**复用真实 `DashboardContent` 和固定 fixture,不请求接口,也不启动自动刷新
- **组件概览**展示基础组件、表格、空状态、骨架、浮层和菜单等关键状态
- 画板固定为 `1600 × 1808`,使用 CSS `zoom` 重排,不使用 `transform: scale()` 长期缩放文字
- 空白区域可拖拽,滚轮以指针为锚点缩放,并提供缩小、100%、放大和适应画板操作
- 编辑面板和组件概览统一使用 `BaseScrollbar`,滚动条空闲时自动隐藏
## 样式与命名约定
### CSS Token
- 全局 Token:`--cp-color-bg-container`、`--cp-font-size`。
- Component Token:`--cp-table-row-hover-bg`、`--cp-input-active-shadow`。
- Preset Color Token:`--cp-color-purple-container-strong`、`--cp-color-cyan-on-container`。
- 主题层禁止业务域命名;套餐、模型或页面只能消费通用 Alias、Preset 或 Component Token。
- Map 与 Component 字段由 `theme/core/tokens.ts` 统一生成 CSS Token;禁止在解析器中再写平行的逐项映射表。
- 禁止继续引入 `accent`、`soft`、`current`、`subtle` 等与现有角色重叠的平行词汇。
- 全局 Token:`--cp-color-bg-container`、`--cp-font-size`
- Component Token:`--cp-table-row-hover-bg`、`--cp-input-active-shadow`
- Preset Color Token:`--cp-color-purple-container-strong`、`--cp-color-cyan-on-container`
- 主题层禁止业务域命名;套餐、模型或页面只能消费通用 Alias、Preset 或 Component Token
- Map 与 Component 字段由 `theme/core/tokens.ts` 统一生成 CSS Token;禁止在解析器中再写平行的逐项映射表
- 禁止继续引入 `accent`、`soft`、`current`、`subtle` 等与现有角色重叠的平行词汇
### Vue 与 Tailwind CSS 4
@@ -366,55 +374,55 @@ Input、Button Secondary 与 Icon Button Secondary 的三个背景 Token 支持
```
仅当值需要在 CSS 函数、SVG 或局部派生中参与计算时,直接读取 `var(--cp-*)`。不在页面组件中重新实现色阶、
对比度或明暗算法。
对比度或明暗算法
共享包的 `styles/tailwind.css` 是唯一 `@theme inline` 注册表,只注册 Tailwind 名称,不保存主题值。注册表按基础、排版、颜色、圆角、阴影、间距与尺寸排序;
颜色再按基元、表面、主色与链接、语义、预设、数据、组件分组。
Preset 家族按字母排序,每个家族固定使用 `container → container-strong → border → solid → text → on-container`。主题值由
`initializeTheme()` 在 Vue 挂载前动态生成并提交,不增加 `theme:generate`、`theme:check` 或构建期快照。
`initializeTheme()` 在 Vue 挂载前动态生成并提交,不增加 `theme:generate`、`theme:check` 或构建期快照
全局元素基线统一放进共享包 `styles/base.css` 的 `@layer base`,确保组件 utility 可以按 Tailwind 层级正常覆盖;可复用的
原生滚动条声明使用 Tailwind CSS 4 `@utility`。组件内能等价表达的简单 SVG、渐变、原生外观与伪元素优先使用
utility / arbitrary variant;Vue Transition、跨浏览器 Range、动态富文本 `:deep()`、复杂纹理与关键帧继续保留局部
`<style scoped>`,不为追求原子化牺牲可读性。
`<style scoped>`,不为追求原子化牺牲可读性
### 视觉状态
- 静态填充、Hover、Active 与 Selected 必须使用不同角色,不能复用一个变量制造所有层级。
- 输入控件常态无边;明暗模式的 Hover 与 Focus 均保留同色、同宽外圈反馈。Hover 仅在未聚焦时生效,错误与禁用状态优先。
- 表格斑马纹使用 `table-row-stripe-bg`;选中行悬停使用 `table-row-selected-hover-bg`,不被普通 Hover 覆盖。
- 结构化浮层头部使用 `popover-header-bg`,明暗模式分别检查它与表格背景的区分。
- Card 和选项默认不增加装饰性边框;键盘焦点必须保留可见反馈。
- 阴影保持中性,`shadowStrength` 只调节层级强弱,不给阴影染品牌色。
- 静态填充、Hover、Active 与 Selected 必须使用不同角色,不能复用一个变量制造所有层级
- 输入控件常态无边;明暗模式的 Hover 与 Focus 均保留同色、同宽外圈反馈。Hover 仅在未聚焦时生效,错误与禁用状态优先
- 表格斑马纹使用 `table-row-stripe-bg`;选中行悬停使用 `table-row-selected-hover-bg`,不被普通 Hover 覆盖
- 结构化浮层头部使用 `popover-header-bg`,明暗模式分别检查它与表格背景的区分
- Card 和选项默认不增加装饰性边框;键盘焦点必须保留可见反馈
- 阴影保持中性,`shadowStrength` 只调节层级强弱,不给阴影染品牌色
## 扩展指南
### 增加预置主题
1. 在 `ThemeColorPresetId` 增加稳定 ID。
2. 在 `THEME_COLOR_PRESETS` 声明名称、描述与主色 Seed。
3. 不新增页面特判,确认预置可通过同一 `resolveTheme()` 派生。
4. 在浅色、深色和系统模式下检查文本、浮层、Input、表格与图表。
1. 在 `ThemeColorPresetId` 增加稳定 ID
2. 在 `THEME_COLOR_PRESETS` 声明名称、描述与主色 Seed
3. 不新增页面特判,确认预置可通过同一 `resolveTheme()` 派生
4. 在浅色、深色和系统模式下检查文本、浮层、Input、表格与图表
### 增加全局 Seed 或 Alias
1. 在唯一的 `theme/types.ts` 中声明 Seed、Map 或 Alias 字段。
2. 在 `theme/core/normalize.ts` 定义合法输入边界。
3. 在对应派生模块集中生成字段;普通 Map 字段会自动进入 `ThemeTokens` 完整输出。
4. 如需 Tailwind utility,在 `codex-proxy-ui/src/styles/tailwind.css` 的 `@theme inline` 中映射。
5. 只在确实需要用户控制时加入 Theme Editor;派生细节默认只读。
1. 在唯一的 `theme/types.ts` 中声明 Seed、Map 或 Alias 字段
2. 在 `theme/core/normalize.ts` 定义合法输入边界
3. 在对应派生模块集中生成字段;普通 Map 字段会自动进入 `ThemeTokens` 完整输出
4. 如需 Tailwind utility,在 `codex-proxy-ui/src/styles/tailwind.css` 的 `@theme inline` 中映射
5. 只在确实需要用户控制时加入 Theme Editor;派生细节默认只读
### 增加 Component Token
1. 先确认全局 Alias 无法准确表达组件职责。
2. 使用 `component[-part][-variant][-state]-property` 命名。
3. 在 `ThemeComponentMap` 和 `deriveThemeComponentMap()` 中提供默认值;Token 编译器自动生成 CSS 变量。
4. 需要开放编辑时,加入对应组件目录和可编辑白名单。
5. 基础组件消费 Token,页面不得再写第二套局部常量。
1. 先确认全局 Alias 无法准确表达组件职责
2. 使用 `component[-part][-variant][-state]-property` 命名
3. 在 `ThemeComponentMap` 和 `deriveThemeComponentMap()` 中提供默认值;Token 编译器自动生成 CSS 变量
4. 需要开放编辑时,加入对应组件目录和可编辑白名单
5. 基础组件消费 Token,页面不得再写第二套局部常量
> [!WARNING]
> 不要把任意 HEX、阴影、尺寸或业务标识色加入 `styles/tokens.css` 作为“临时修复”。可换肤值必须进入派生链;
> 静态样式只保留主题无关的颜色基元和首帧安全 fallback。
> 静态样式只保留主题无关的颜色基元和首帧安全 fallback
## 验证
@@ -422,13 +430,13 @@ utility / arbitrary variant;Vue Transition、跨浏览器 Range、动态富文
对照前后截图复核是否改变了无关区域、增加重复说明或不必要的空白,帮助内容收进浮层后检查入口可发现性与展开状态。
修改主题派生、主题编辑器或共用组件的视觉行为时,按影响范围补充以下检查:
- 四个预置、自定义 HEX、浅色、深色和跟随系统模式。
- 页面、容器、浮层、输入框、表格、分页、主按钮、选中态、焦点和品牌图标。
- 草稿隔离、保存刷新恢复与 Teleport 弹窗。
- 首页画板缩放清晰度、组件概览滚动、ECharts 网格线和 Skeleton 动效。
- 键盘操作、颜色之外的选中反馈和 `prefers-reduced-motion`。
- 四个预置、自定义 HEX、浅色、深色和跟随系统模式
- 页面、容器、浮层、输入框、表格、分页、主按钮、选中态、焦点和品牌图标
- 草稿隔离、保存刷新恢复与 Teleport 弹窗
- 首页画板缩放清晰度、组件概览滚动、ECharts 网格线和 Skeleton 动效
- 键盘操作、颜色之外的选中反馈和 `prefers-reduced-motion`
局部页面改动只验证受影响的页面与状态,无需执行整套主题编辑器验收。
局部页面改动只验证受影响的页面与状态,无需执行整套主题编辑器验收
## 上游参考