# R2.20 任务报告

## 结论

- PK01 当前运行的 Sub2API `v0.1.156` 已原生识别：
  - `Selected model is at capacity. Please try a different model.`
- 该错误满足自动切号条件：
  - HTTP 400 同步或流式请求在响应正文提交前返回该文案时，转换为 `UpstreamFailoverError`；
  - HTTP 200 SSE 在尚未向客户提交正文前收到包含该文案的 `response.failed` 时，同样转换为 `UpstreamFailoverError`；
  - UniDesk 禁用 `pool_mode`，因此不会在同一账号内部重试，而是排除当前账号并选择其他账号。
- 不需要修改 Sub2API 源码或版本即可实现当前请求的自动切号。
- 最近两小时所有分组原生策略数据表明自动切号链路正常：
  - `unidesk-codex-pool` 触发 13 次，恢复 13 次，失败 0 次，选号失败 0 次；
  - `自用` 触发 18 次，恢复 17 次，失败 1 次，选号失败 0 次。
- 近期客户可见错误摘要中未发现该 capacity 文案，说明它更可能已被内部切号吸收，或仅记录在上游错误详情中。

## 官方源码证据

- `backend/internal/service/openai_gateway_upstream_errors.go`：
  - `isOpenAITransientProcessingError` 对 HTTP 400 精确匹配 `selected model is at capacity`；
  - `shouldFailoverOpenAIUpstreamResponse` 在排除上下文窗口错误后，将该错误判定为可切号。
- `backend/internal/service/openai_gateway_forward.go`：
  - 判定命中后先记录 `failover`，执行账号错误副作用，再返回 `UpstreamFailoverError`；
  - 响应未写入客户连接，因此上层仍可重新选号。
- `backend/internal/service/openai_gateway_passthrough.go`：
  - `openAIStreamFailedEventShouldFailover` 对流内 capacity 文案返回可切号。
- 官方测试：
  - `TestOpenAIGatewayService_Forward_ModelCapacityErrorTriggersFailoverAndSameAccountRetry` 覆盖 HTTP 400；
  - `TestOpenAIStreamingResponseFailedBeforeOutputCapacityErrorReturnsFailover` 覆盖 HTTP 200 SSE 的提交前失败。

## 配置调优候选

- 当前 YAML 通用模板 `codex-upstream-failover` 的 HTTP 400 关键词只有 `input must be a list`，尚未包含 capacity 文案。
- 后续若用户明确授权，可通过官方 runtime/YAML 配置对非 auth 上游账号增加：
  - 状态码：`400`；
  - 精确关键词：`selected model is at capacity`；
  - 临时不可调度：`1` 分钟。
- 该规则的作用是让失败账号在数据库和内存调度缓存中短时降温，避免后续请求立即再次选中；它不是当前请求自动切号的必要条件。
- 例外账号必须通过精准 selector 排除，不得硬编码账号名称。

## 无法无感切号的边界

- 一旦上游已经向客户输出真实正文，再发生 `response.failed` 或断流，Sub2API 无法安全重放请求：
  - 换号会造成重复或相互矛盾的输出；
  - 只能记录为 degraded 或客户可见流式错误。
- 因此 capacity 优化应聚焦提交前 failover 和短时账号冷却，不应把已提交流强行重放。

## 本次边界

- 本次仅做官方源码与原生运维数据只读分析。
- 未修改 PK01 runtime、YAML、Sub2API 源码、版本、外部哨兵或生产运行面。
