Files
pikasTech-unidesk/.agents/skills/unidesk-sub2api/SKILL.md
T
2026-06-15 05:25:59 +00:00

41 KiB
Raw Blame History

name, description
name description
unidesk-sub2api UniDesk Sub2API 平台运维技能。用户提到 Sub2API、sub2api、platform-infra sub2api、Codex pool、统一 API key、Sub2API FRP 暴露、Sub2API 管理 UI、配置 master ~/.codex 走 Sub2API、添加/删除 Codex 上游账号、校验 Sub2API /v1/models 时使用。

UniDesk Sub2API

UniDesk 在 k3s platform-infra namespace 运维 Sub2API。D601 是当前默认 active runtime,使用外置 PostgreSQL 和目标级 public exposureG14 由同一 YAML/CLI 控制为 standby predeploy,默认缩容且不运行 sentinel、FRP 或 HTTPS egress proxy。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源或手工调用 Sub2API 管理 API。

固定入口: cd /root/unidesk && bun scripts/cli.ts platform-infra sub2api ...

先看报表

查 Codex pool 哨兵状态、账号冻结/恢复、marker 命中、下一次 probe、最近 CronJob run、token/cost 账本时,优先使用这个低噪声报表入口,不要先翻 ConfigMap、CronJob 日志或 Sub2API UI

bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report

需要机器处理或完整字段时再加 --raw;需要更多最近运行记录时加 --events N

追溯某个 Codex/Sub2API request id 的中断、上游账号、切号、临时不可调度、账号选择失败和同窗口账号池信号时,优先使用低噪声 trace 报表,不要先手写 kubectl logs | grep

bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>

默认输出类似 k8s/ps 的短表;机器处理用 --raw 读取 .data.trace.*;需要审计原始匹配日志时加 --show-lines;需要扩大搜索范围时使用 --since 24h --tail 50000。该命令只读:读取 Sub2API 日志、账号快照和 admin API 元数据,不改 schedulable、不清 runtime backoff、不中断请求。

先读边界

  • 仓库长期开发边界见 docs/reference/platform-infra.md,本 skill 承担日常操作手册。
  • 配置真相是 YAMLconfig/platform-infra/sub2api.yamlconfig/platform-infra/sub2api-codex-pool.yaml
  • 业务策略和具体数值只以 YAML 为准。已有字段的数值调整只改 YAML 并跑 plan / sync --confirm / validate;不要自动补代码硬编码、schema 硬范围、合同测试、单元测试或长期参考文档。配置校验只校验格式、类型、必填和可渲染性,不判断数值策略是否“合理”。
  • 本 skill 目录下若存在 agents/*.yaml,只作为 skill/agent 展示与调用元数据,不是 Sub2API 或 Codex pool 运行配置;不要在 skill 目录维护第二份账号、capacity、priority、endpoint 或 Secret 配置。
  • Runtime target 由 config/platform-infra/sub2api.yaml 声明;默认 D601:k3s 是 active targetG14:k3s 是 standby target。master server 只是控制端和消费者,不部署 Sub2API/PostgreSQL/Redis。
  • Standby target 不部署本地 PostgreSQL,不运行 sentinel、FRP 管理入口或 HTTPS egress proxy;只能预部署 namespace、NetworkPolicy、Service,以及 replicas=0 的 Sub2API/Redis Deployment。Redis 激活后也只允许 ephemeral cache。External-active target 仍不部署本地 PostgreSQL,必须直连 YAML 声明的外置 DB,使用本地 ephemeral Redis,并且只有在 YAML 启用时才运行 frpc、egress proxy 和目标级 sentinel。
  • Secret、~/.codex/config.toml*~/.codex/auth.json* 是运行时输入或本地状态,不提交。
  • 默认 ~/.codex/config.toml~/.codex/auth.json 只作为统一 Sub2API consumer 使用;config.toml 必须指向 YAML-selected active target 的 consumer URL,当前由 codex-pool configure-local --confirm 写入 D601 target-level public URLauth.json 必须使用统一 pool API key。新增上游账号不得覆盖这两个默认文件,只能新增 config.toml.<profile> / auth.json.<profile> 并在 YAML 里声明。
  • 输出只能包含 Secret 路径、长度、preview/fingerprint;禁止打印完整 API key、admin password、JWT secret、TOTP key。

部署与状态

bun scripts/cli.ts platform-infra sub2api plan
bun scripts/cli.ts platform-infra sub2api plan --target G14
bun scripts/cli.ts platform-infra sub2api apply --dry-run
bun scripts/cli.ts platform-infra sub2api apply --target G14 --dry-run
bun scripts/cli.ts platform-infra sub2api apply --confirm
bun scripts/cli.ts platform-infra sub2api apply --target G14 --confirm
bun scripts/cli.ts platform-infra sub2api status
bun scripts/cli.ts platform-infra sub2api status --target G14
bun scripts/cli.ts platform-infra sub2api validate
bun scripts/cli.ts platform-infra sub2api validate --target G14
  • plan 读取 config/platform-infra/sub2api.yaml,渲染 src/components/platform-infra/sub2api/sub2api.k8s.yaml,检查 no Ingress/NodePort/LoadBalancer/hostPort/hostNetwork/resource limits,并要求 NetworkPolicy/allow-all 随 manifest 受控创建。
  • apply --confirm 默认创建异步 job;按返回的 job status 命令轮询,再跑 statusvalidate
  • status --full|--raw 只在需要展开远端 stdout/stderr 或原始 JSON 时使用。
  • validate 是按需验收,不是连续可用性探针。对 standby targetvalidate --target <id> 验证预部署形态,不要求外置 DB 当前可连接;对 external-active target,必须验证外置 DB、ephemeral Redis、Sub2API service、YAML egress proxy 和目标级 public exposure。

镜像升级

  1. 修改 config/platform-infra/sub2api.yamlimage.repositoryimage.tagpullPolicy
  2. 执行 sub2api plan,确认策略检查通过。
  3. 执行 sub2api apply --confirm,轮询 job 完成。
  4. 执行 sub2api status,确认运行镜像等于 YAML 声明。
  5. 执行 sub2api validatecodex-pool validate 做入口验收。

不要把镜像版本写进脚本常量、JSON 或 manifest 模板。

Codex Pool

bun scripts/cli.ts platform-infra sub2api codex-pool plan
bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool validate
bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --target D601 --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --account unidesk-codex-hy --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --target D601 --account unidesk-codex-hy --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D601 --confirm

config/platform-infra/sub2api-codex-pool.yaml 控制:

  • pool.groupName: Sub2API group 名称。
  • pool.apiKeySecretName / pool.apiKeySecretKey: 统一消费 API key 的 k3s Secret 位置,默认 platform-infra/sub2api-codex-pool-api-key.API_KEY
  • pool.minOwnerBalanceUsd: pool key owner 最低余额,sync/validate 会补齐。
  • pool.minOwnerConcurrency: 可选统一消费 API key owner 最低并发;省略时 CLI 自动使用所有已解析账号 capacity 的总和,sync/validate 会补齐。显式 YAML 值只作为 override,仍必须不小于账号 capacity 总和;未显式写 profiles.entries[].capacity 的账号会使用 pool.defaultAccountCapacity 参与求和,不要用提高某个 provider capacity 来掩盖用户并发层 WS 1013。
  • pool.defaultTempUnschedulable: Sub2API 内置请求路径临时不可调度开关和 YAML 规则列表。当前要求是按 YAML 开启通用规则;sync 把 temp_unschedulable_enabled / temp_unschedulable_rules 渲染到 managed accounts,让匹配的 400/5xx/超时/模型路由/加密内容错误短暂冷却当前账号并触发同组 failover。
  • pool.defaultTempUnschedulable 与外部 sentinel.* 分开配置、互不驱动。内置规则负责 near-real-time request-path cooling/failover;哨兵负责 marker health、账号级隔离/恢复和 probe 退避。
  • 外部 sentinel 的写入面只允许通过 Sub2API admin schedulable 接口冻结/恢复账号;不能写入、清理或间接清理 temp_unschedulable_until / temp_unschedulable_reason、rate-limit、overload、model-rate-limit 等 Sub2API 请求路径 runtime 状态,也不能调用 recover-state 作为恢复动作。看到 UI 里的“触发时间/解除时间/规则序号/匹配关键词”临时不可调度状态时,默认先归因到 Sub2API 内置 request-path temp-unschedulable,而不是 sentinel。
  • YAML 只选择和配置 Codex 上游,不声明 schedulable 长期字段;codex-pool sync --confirm 不负责把既有账号恢复为 schedulable=true。既有账号的 schedulable=false 必须由哨兵先同步 Sub2API runtime 状态,再在 marker probe 命中后恢复。
  • profiles.entries: 从 master ~/.codex/ 选择上游 profile 并映射到 Sub2API account。
  • profiles.entries[].capacity: 可选 per-account concurrency override;不写则使用 pool.defaultAccountCapacity。具体数值只以 config/platform-infra/sub2api-codex-pool.yaml 为准,skill 和长期参考只描述规则,不重复写当前值。
  • profiles.entries[].loadFactor: 可选 per-account Sub2API load_factor override;不写则使用 pool.defaultAccountLoadFactor。具体数值只以 config/platform-infra/sub2api-codex-pool.yaml 为准,修改后必须 codex-pool sync --confirmcodex-pool validate
  • profiles.entries[].trustUpstream: 可选账号级哨兵信任标记;默认 false。可信账号使用 sentinel.cadence.trustedSuccessMaxIntervalMinutes 作为连续成功后的最大探测退避,不可信账号使用 sentinel.cadence.untrustedSuccessMaxIntervalMinutes。它只影响哨兵探测频率和状态可见性,不改变 Sub2API account priority/capacity/loadFactor。
  • pool.defaultSentinelProtect: 账号级哨兵保护默认策略;是否启用、连续确认次数、初始延迟、最大延迟和退避倍率都只以 YAML 为准。marker probe 或 gateway failure 触发冻结前会先按该策略做连续 marker 确认,只有全部失败才进入冻结状态机。
  • profiles.entries[].sentinelProtect: 可选账号级哨兵保护覆盖;只用于明确偏离 pool 默认策略。它只影响哨兵冻结判定和 sentinel-report 可见性,不改变 Sub2API account priority/capacity/loadFactor。
  • 除非用户明确要求修改配置,不要仅凭推断改账号 membership、priority、capacity、loadFactor、WebSocket mode 或其他调度策略;先保留 YAML,完成 provenance/runtime evidence 溯源,并把结论写回相关 issue 或 runbook 后再提出变更。
  • Sub2API 是 UniDesk 可读源码和可观测运行面的受控组件;排查 Sub2API 调度、failover、错误传播、临时不可调度或 account selection 时,默认先读当前 Sub2API 源码实现,再用真实 request id、Sub2API 日志和原入口流量验证。不要用 mock upstream、临时 probe account 或测试桩作为默认结论来源;这类探针最多是显式 debug 辅助,不能替代源码链路和真实运行证据。
  • profiles.entries[].tempUnschedulable: 可选 per-account Sub2API 内置临时不可调度覆盖;只用于明确偏离 pool 默认规则,不用它给某个账号特殊优先级或临时绕过通用 failover。
  • profiles.entries[].openaiResponsesWebSocketsV2Mode: 需要 Responses WebSocket v2 的上游才设置,值为 offctx_poolpassthrough
  • profiles.entries[].upstreamUserAgent: 少数要求 Codex CLI User-Agent 的上游才设置,不能含换行。
  • manualAccounts.protected: 已在 Sub2API 手动创建/维护、且必须排除在 UniDesk-managed Codex pool credentials 和 sentinel 控制之外的账号。默认不得改 credentials/status/schedulable/priority/capacity/loadFactor;只有显式声明 proxyBinding 时,sync --confirm 才允许把该账号的 proxy_id 对齐到 YAML 目标的 egress proxy;只有显式声明 groupBinding.source: pool-group 时,才允许把该账号加入统一消费 API key 使用的 pool group。
  • sentinel.monitor.enabled: 账号级 marker 哨兵监控开关;开启后 codex-pool sync --confirm 会在 platform-infra 创建/更新 k8s CronJob、ConfigMap、Secret、ServiceAccount、Role 和 RoleBinding。CronJob 直打 YAML-managed 上游账号的 OpenAI Responses gpt-5.5,用确定 marker 作为唯一健康标准,并在独立 state ConfigMap 中记录 token/cost 账本。
  • sentinel.actions.enabled: 账号级哨兵冻结/恢复动作开关;当前 marker-only guard 要求开启。动作关闭时只记录 would-freeze,不会调用 Sub2API admin API 改 schedulable。动作开启后,只要不满足 marker match,不论是 HTTP 200 私货、4xx/5xx、非 JSON、连接错误还是空输出,都进入同一个冻结/恢复状态机。
  • sentinel.sdk.openaiPythonVersion: 哨兵容器使用的 OpenAI Python SDK 固定版本;模型请求必须通过标准 SDK responses.create,不要手工拼 /v1/responses 请求体或手写响应解析。后续升级 SDK 只改 YAML 并 sync --confirm
  • sentinel.probe.maxOutputTokens: 哨兵本地流式 delta 收集上限,必须保持小值;它不作为上游 max_output_tokens 字段发送,以保持与 Sub2API WebUI 默认账号连接测试的 Responses SSE 请求形态一致。哨兵不限制并发和每轮账号数,所有到期账号会在同一轮并发探测。
  • sentinel.probe.userAgent: 哨兵 direct upstream probe 的默认 User-Agent,通过 OpenAI SDK extra_headers 传递;默认贴近 Sub2API net/http 账号连接测试形态,个别账号仍可用 profiles.entries[].upstreamUserAgent 覆盖。
  • sentinel.cadence: 成功信任指数退避配置。当前口径是从 1 分钟开始,连续成功后按账号 trustUpstream 选择可信/不可信最大退避;任意非 marker match 清零成功信任并进入冻结退避。可信/不可信最大退避数值只写 YAML。
  • sentinel.freeze: 失败冻结 TTL 指数退避配置。当前口径是初始 1 分钟,失败后 1m -> 2m -> 4m -> 8m -> 10m,最大 10 分钟;失败 probe 基本不消耗有效输出 token,因此冻结窗口保持短周期。冻结到期后只做恢复 probe,通过才自动恢复,不能仅靠 TTL 到期解封。
  • sentinel.pricing: 直打上游时哨兵自己的 token/cost 估算价格。因为 direct upstream probe 不经过 Sub2API 普通用量账本,哨兵必须自己记录全局与 per-account token/cost;这些账本只用于观察,不作为跳过探测的预算门禁。

sync --confirm 会登录 Sub2API admin、创建/更新 group、创建/更新 YAML 中的 unidesk-codex-* accounts、创建/复用统一 API key Secret,并部署/更新哨兵资源;它不把既有 managed account 直接恢复为 schedulable=true。恢复只由哨兵在读取 Sub2API runtime schedulable=false 后触发 recovery probe,并在 marker 命中时执行。sync 默认不删除 YAML 中缺席的 managed account。只有明确退役上游时才使用 sync --confirm --prune-removed 删除缺席且 extra.unidesk_managed=trueunidesk-codex-* account。对 manualAccounts.protectedsync 只执行 YAML 显式允许的窄同步;当前允许项是从目标 egressProxy 创建/更新 Sub2API internal proxy 记录并绑定 proxy_id,以及把受保护手动账号加入当前 pool.groupName。它仍不接管该账号凭据、status、schedulable、priority/capacity/loadFactor 或哨兵状态。

sentinel-image status|build 管理哨兵 Python 运行环境镜像。镜像由 YAML 的 sentinel.image 基础镜像和 sentinel.sdk.openaiPythonVersion 派生,发布到目标 runtime 的本地 registrybuild --confirm 会先检查 registry tag,存在则快速复用,不存在才在目标 host 构建并 push。CronJob 启动时只校验 SDK 版本,不在运行时 pip install

sync --confirm 同时会按 YAML 渲染账号级哨兵资源,并在 monitor 开启时先确保可复用哨兵镜像存在。当前目标是 sentinel.monitor.enabled=true + sentinel.actions.enabled=true 的 marker-only 自动冻结/恢复;不要手工 patch CronJob、Secret 或 Sub2API account。若 YAML 新增账号或修改 profile/base URL/API key fingerprint/upstream User-Agent/Responses WebSocket modesync 会从变更前 runtime state 写入 pending probe 记录并立即安排 sentinel probe,但不会把既有账号直接恢复为可调度;只有 sentinel 读取到 Sub2API runtime schedulable=false 后执行 recovery probe,且 marker 命中,才恢复 schedulable=true。sentinel 冻结/恢复只改 schedulable=false|true,不得顺手调用 Sub2API recover-state 清除请求路径临时不可调度或其他 runtime backoff。无关账号的既有成功/失败退避不能被重置。若 YAML 下调失败冻结最大窗口,sync 会把仍 active 的旧冻结状态迁移到当前最大窗口内并立即安排 recovery probe,但不会直接解冻。若怀疑某个账号被误判,先用 codex-pool sentinel-probe --account <accountName> --confirm 立即触发该账号测量;该命令从现有 CronJob 模板派生一次性 Job,复用同一份 Secret、ConfigMap、OpenAI SDK probe、token/cost 账本和冻结/恢复状态机。

trace --request-id <requestId> 是只读 request 追溯报表,不触发 probe、不修改账号。默认输出请求开始/最终状态、failover、account_select_failed、窗口内 account_temp_unschedulable、admin schedulable 写入计数和当前账号快照;reason=failover-attempted-no-candidate 表示 Sub2API 已进入自动切号,但排除当前失败账号后没有可用候选。需要机器处理时使用 --raw,需要原始匹配行时加 --show-lines

sentinel-report 是只读低噪声报表,不触发 probe、不修改账号。默认输出类似 ps 的文本表,展示每个账号的探测次数、Sub2API runtime schedulable、最近 marker/HTTP/动作、冻结 TTL、成功退避、下一次 probe 和最近 run 事件;SCH 展示 Sub2API runtime schedulablePROT 展示账号级保护阈值,P_FAIL 展示最近一次保护确认中的失败次数/阈值;需要机器处理时使用 sentinel-report --raw

sync --confirmvalidate 可能超过单次 SSH/runtime 短连接窗口。必须继续使用 bun scripts/cli.ts platform-infra sub2api codex-pool ...,由 CLI 在 G14 远端提交作业并短轮询状态;不要改用裸 trans G14:k3s sh 等一个长连接等待完整结果。若看到 UNIDESK_SSH_RUNTIME_TIMEOUT,先按 docs/reference/platform-infra.md 的规则处理为控制面可见性问题,修 CLI/job/poll 或重跑受控命令,不要手工 patch Sub2API credentials 或源码。

不要给 UniDesk-managed Codex accounts 开 Sub2API pool_mode。UniDesk 期望的 failover 是把失败账号临时标记为 unschedulable,让同组其他账号接手;pool_mode 会重试同一个 account path。

WebSocket v2 是账号能力集合,不是调度 pin。openaiResponsesWebSocketsV2Mode 只声明该账号可承担 Codex Responses WSv2 链路;只有 localCodex.supportsWebSockets=true / localCodex.responsesWebSocketsV2=true 时,codex-pool validate 才必须看到至少一个 webSocketsV2.schedulableEnabled 账号。真实可用性仍以 direct Codex WSv2 probe、Sub2API 日志和原入口 Codex smoke 为准。

Codex 启动时反复出现 WebSocket reconnect、HTTPS fallback、websocket closed by server before response.completed,或 Sub2API 日志出现 openai.websocket_proxy_failed / openai.websocket_account_select_failed / 上游 WS handshake 4xx/5xx 时,先按运行证据定位具体 account 和 transport。若账号的 WSv2 握手失败,优先只在 YAML 中把该账号的 openaiResponsesWebSocketsV2Mode 收敛为 off;若没有任何 direct Codex WSv2 probe 通过,则同时把 localCodex.supportsWebSocketslocalCodex.responsesWebSocketsV2 收敛为 false,再 codex-pool sync --confirm。不要顺手改 membership、priority、capacity、Secret 或代码 fallback。

受保护手动账号代理与分组绑定

Sub2API 管理 UI 的账号连接测试使用账号级 ProxyID / proxy URL 配置上游 HTTP transport;账号没有绑定 proxy 时会直接出站,即使 Sub2API Pod 已经有 HTTP_PROXY / HTTPS_PROXY 环境变量。看到 WebUI 账号测试连 chatgpt.com 超时、但 Pod 内显式走目标 proxy 可通时,先检查该账号是否属于 manualAccounts.protected 并声明了 proxyBinding

WebUI 账号连接测试也不经过统一消费 API key 的 pool group 选择器;账号测试正常不代表 PC Codex 客户端能选中该账号。看到 WebUI 账号测试正常、但 /responses/v1/responsesaccount-select-failed / no available accounts 返回 503 时,先检查该手动账号是否声明了 groupBinding.source: pool-group,并通过 sync --confirm 加入当前 pool.groupName

受保护手动账号仍由人工在 Sub2API UI 维护 credentials/status 等字段;UniDesk 只允许通过 YAML 做代理和分组窄绑定:

bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601

sync 输出应显示 manualAccounts.ok=trueproxySync.ok=truegroupSync.ok=true,且该账号的 proxy/group bindingAligned=truesentinel-probe --account <manual-account> --confirm 对受保护手动账号必须继续拒绝,通常返回 account-protected-manual;不要为了测试而把该账号移入 profiles.entries 或取消保护。需要证明 WebUI 同款账号测试恢复时,用 Sub2API admin account test 原入口测最小 hi / gpt-5.5,并只记录 account id、proxy id、event types、HTTP status 和短 output preview,不记录 OAuth token 或 Secret 明文。

添加上游

  1. 在 master ~/.codex/ 准备带后缀的上游 profile 文件,例如 config.toml.<profile>auth.json.<profile>;禁止覆盖默认 config.toml / auth.json
  2. config/platform-infra/sub2api-codex-pool.yaml 添加 profiles.entries 项,指定 profileaccountNameconfigFileauthFile
  3. 如需要,给该项加 prioritycapacityloadFactortrustUpstreamsentinelProtectopenaiResponsesWebSocketsV2ModeupstreamUserAgentcapacity/loadFactor/信任退避/保护阈值的具体数值只写在 YAML。只有显式恢复 Sub2API 内置临时不可调度时才添加 per-account tempUnschedulable
  4. 如果新增账号会提高声明 capacity 总和,默认让省略的 pool.minOwnerConcurrency 继续按 capacity 总和自动解析;只有 YAML 已经显式写了该 override 时,才同步提高到不低于总 capacity,或删除 override 回到自动解析。
  5. codex-pool plan,确认 profile 可读、base_url 和 API key 来源有效,且 stdout 未泄露完整 key。
  6. codex-pool sync --confirm
  7. codex-pool validate

普通新增上游是 YAML 操作,不走 CI/CD,不改代码。只有需要渲染或校验上游 Sub2API 已经存在的可复用能力时才修改 scripts/src/platform-infra-sub2api-codex.ts;Sub2API 本身不支持的能力不在 UniDesk 侧魔改实现。

删除上游

删除上游只用于明确退役、凭据所有权变更或用户明确要求移除 provider;不能作为上游 5xx、compact 失败、限流、模型路由失败或哨兵隔离/恢复问题的处理手段。

  1. config/platform-infra/sub2api-codex-pool.yaml 删除对应 profiles.entries 项。
  2. codex-pool plan 检查 desired 列表。
  3. codex-pool sync --confirm --prune-removed
  4. 确认输出 accounts.pruned 只包含期望删除项。
  5. codex-pool validate

CLI 默认保留缺席账号,避免把可用性问题误处理成删除;只有显式 --prune-removed 才会 prune nameunidesk-codex- 开头且 extra.unidesk_managed=true 的缺席账号。

FRP 暴露

bun scripts/cli.ts platform-infra sub2api codex-pool expose
bun scripts/cli.ts platform-infra sub2api codex-pool expose --confirm
  • 由 YAML publicExposure 控制。Codex pool 默认公共端是 publicBaseUrlmaster 本地消费端是 masterBaseUrlD601 external-active 的目标级 public exposure 在 config/platform-infra/sub2api.yaml 中声明。
  • expose --confirm 只为 YAML 指定的 remotePort 补 master frps allow port,并在 G14 创建/更新 sub2api-frpc
  • master Caddy site 也由 publicExposure.masterCaddy 渲染;responseHeaderTimeoutSeconds 必须足够覆盖 Codex /responses/compact 长请求,避免 Caddy 先返回 504 而 Sub2API 后台实际稍后成功。具体数值只改 config/platform-infra/sub2api-codex-pool.yaml,修改后跑 codex-pool expose --confirm,再核对 Caddyfile 中渲染出的 response_header_timeout
  • master Caddy 的短窗口边缘重试由 publicExposure.masterCaddy.edgeRetry 渲染;用于吸收 FRP remotePort 短暂关闭、connect: connection refused、EOF 或 connection reset 这类请求尚未稳定到达 Sub2API 的 502。具体 retry 时长、间隔和 retryMatch 范围只写 YAML,修改后跑 codex-pool expose --confirm,再核对 Caddyfile 中渲染出的 lb_try_durationlb_try_intervallb_retry_match。不要手工 patch /etc/caddy/Caddyfile
  • PK01 /etc/caddy/Caddyfile 是 Sub2API、LangBot、n8n、HWLAB 等多 YAML 来源共享的 edge artifact。Sub2API apply/expose 只能更新自己的 # BEGIN unidesk managed sub2api 块并保留其他 managed blocks;若 apply 输出显示 managed block 数异常,先停止 closeout,检查 PK01 Caddy 合并与 validation 结果,不要手工整文件覆盖。
  • 非幂等 POST 的 round-trip retry 必须收窄到 YAML retryMatch 声明的安全路径;普通 /responses 上游账号错误仍归 Sub2API failover / temp-unschedulable / sentinel 处理,不用 Caddy 重放整段推理请求来掩盖账号池问题。
  • 同一个 FRP TCP 入口同时暴露 OpenAI-compatible API 和 Sub2API 管理 UI /login。不要另开第二个管理端口,除非 YAML 明确声明新的暴露决策。
  • Sub2API Kubernetes Service 继续保持 ClusterIP。
  • D601 external-active 的公开路径是 client -> PK01 Caddy -> PK01 frps remotePort -> D601 frpc -> Sub2API,不经过 pikanode,也不经过 master server 反代。PK01 Caddy 下载必须使用 YAML publicExposure.pk01.caddyDownloadProxyUrl 指定的 proxy;如果 Caddy 下载慢,先确认 apply 输出里是 downloadProxy.mode=curl-proxyapi.pikapython.com 必须先解析到 YAML 声明的 PK01 公网地址,HTTPS 才能作为最终验证。

配置 master Codex 消费端

bun scripts/cli.ts platform-infra sub2api codex-pool configure-local
bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm

configure-local --confirm 会:

  • platform-infra/<apiKeySecretName>.<apiKeySecretKey> 读取统一 API key。
  • 把当前 ~/.codex/config.toml~/.codex/auth.json 备份为 .<backupSuffix>,默认 .pre-sub2api
  • 重写默认 ~/.codex 消费端,固定指向 https://sub2api.74-48-78-17.nip.io/provider 名称和 wire API 来自 localCodex
  • localCodex.modelContextWindow / localCodex.modelAutoCompactTokenLimit 写入 model_context_window / model_auto_compact_token_limit,用于统一控制 Codex auto compact 触发窗口,避免 GPT-5.5 消费端生成过大的 /responses/compact 长请求。
  • localCodex 写入 Codex transport 标记:supports_websockets[features] responses_websockets_v2 必须同开同关。只有至少一个上游通过 direct Codex WSv2 probe 时才启用;否则保持 HTTP Responses,避免每次原入口先经历无效 WS reconnect。
  • 用统一 key 做一次 gateway 验证。

防递归规则:默认 config.toml / auth.json 是 Sub2API consumer,不得作为上游账号导回 pool;上游账号必须使用带后缀 profile 文件,并通过 config/platform-infra/sub2api-codex-pool.yamlprofiles.entries 增删。

验收口径

部署 closeout 至少包含:

  • sub2api statusDeployment/StatefulSet/Service/Secret/NetworkPolicy 可见,运行镜像与 YAML 一致,NetworkPolicy/allow-all 符合 podSelector: {}、Ingress/Egress 全放行。
  • sub2api validateapp、PostgreSQL、Redis、service proxy、NetworkPolicy/allow-all 和临时跨 Pod PostgreSQL/Redis 连通性检查通过。
  • codex-pool validate:统一 key 的 GET /v1/models 成功,并用 localCodex.responsesSmokeModel 跑一次小的 POST /v1/responses smokeowner balance / owner concurrency 已满足 YAML 最小值,capacity、WebSocket v2、Sub2API 内置 temporary-unschedulable 开关/规则和 sentinel runtime 状态与 YAML 对齐;validation.gatewayResponsesRecent 汇总最近 6 小时普通 /responses/v1/responses 的 failover、forward failure、最终 4xx/5xx、慢 final error 与 context canceled 证据,validation.gatewayCompactRecent 单独汇总 /responses/compact 证据。若当前 Responses smoke ok=true 但 recent 字段 degraded=true,先区分是历史窗口残留还是新的 request id 正在失败;长期判定见 docs/reference/platform-infra.md
  • publicExposure.enabled=true,确认 FRP path 可用;expose --confirm 会用未带 key 的 public /v1/models 401 作为网关可达性探针。
  • 若目标声明了 egressProxy.enabled=true,确认 proxy Deployment/Service readySub2API 和 sentinel env 与 YAML 对齐,并通过 YAML 声明的 health URL 完成代理出站探针。

如果要证明真实模型请求可用,使用最小 /v1/responses 或等价 Codex smoke。不要把 group-level /v1/models 成功解释成每个上游 account 都健康。

排障

  • Codex pool 哨兵、账号冻结/恢复、marker-only 判断或 probe 周期看不清:第一步跑 bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report。这个报表是主观察面;只有报表缺字段或需要底层证据时,才继续看 --raw、CronJob log、state ConfigMap 或 Sub2API 管理 UI。若看到“临时不可调度状态”且包含规则序号/匹配关键词,检查 Sub2API account_temp_unschedulable 日志和账号 temp_unschedulable_* 字段;sentinel 只解释 schedulable=false 的 active quarantine,不解释这类内置临时冷却。
  • 只加强监控、不让哨兵自动冻结账号时,把 YAML sentinel.actions.enabled=falsecodex-pool sync --confirm。此时 marker probe 和 gateway failure monitor 仍记录 would-freeze / observe-only 证据,但不会通过 Sub2API admin 写 schedulable=false/responses/compactcodex.remote_compact.failed 和 compact 上游 5xx failover 只作为 gateway-compact-* 观察事件记录,不作为哨兵自动切换触发器。
  • 单个 request id 报 502/503/中断/没有自动切号:第一步跑 bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>。先看 outcomereasonFAILOVERSELECT-FAILEDACCOUNT SIGNALSWINDOW STATS;只有 trace 报表缺字段或需要审计原始日志时,才加 --show-lines--raw。若 reason=failover-attempted-no-candidate,说明切号动作已发生,但 scheduler 在排除失败账号后没有可用候选;继续用 sentinel-reportvalidate --full 区分 sentinel quarantine、request-path temp-unschedulable、账号 status 或容量耗尽。
  • profile invalid:先修 ~/.codex/config.toml.<profile>base_urlwire_apimodelauth.json.<profile> 的 API key;不要在 YAML 中写密钥。
  • 手动 OAuth/API-key 账号的 WebUI account test 连 chatgpt.com 超时,但同一 Pod 显式 HTTP proxy 探针可通:不要只看 Pod HTTP_PROXY env,按“受保护手动账号代理与分组绑定”小节确认 manualAccounts.protected[].proxyBinding,跑 codex-pool sync --target D601 --confirm 后再用原账号测试复测。
  • 手动 OAuth/API-key 账号 WebUI account test 正常,但 PC Codex 客户端通过统一 key 访问 /responses 返回 503 且 trace 是 account-select-failed / no available accounts:按“受保护手动账号代理与分组绑定”小节确认 manualAccounts.protected[].groupBinding.source: pool-group,跑 codex-pool sync --target D601 --confirm 后用 codex-pool validate --target D601 --full 复测统一 key。
  • Sub2API 卡在 wait-postgres / wait-redis 或服务内大量 context deadline exceeded:先跑 sub2api statusnetworkPolicy.ok,再跑 sub2api validatepostgresCrossPodPgIsReady / redisCrossPodPing;缺失或异常时用 sub2api apply --confirm 恢复受控 NetworkPolicy/allow-all,不要保留手工 iptables bypass 作为长期修复。
  • pool key 401:跑 codex-pool sync --confirm 重建 Sub2API key 与 k3s Secret 绑定,再跑 codex-pool validate
  • 运行中过去的验证探针残留:只用 codex-pool cleanup-probes --confirm 清理 unidesk-probe-* 临时资源;不要把真实 managed account 删除当作探针清理或可用性恢复。
  • FRP 不通:先看 codex-pool expose --confirm 输出的 masterFrpsmasterCaddysub2api-frpc 和 public 401 probe;需要低层证据时只用 trans G14:k3s 做 bounded 查询。
  • D601 external-active 的 api.pikapython.com 不通:先区分 DNS/TLS/Caddy/FRP/Sub2API。DNS 未解析到 YAML 声明的 PK01 地址时,Caddy ACME 会失败,https://api.pikapython.com 不能算完成;可用 PK01 loopback FRP 端口和 PK01 公网 remotePort 证明 D601 FRP 数据路径,但最终仍要等 DNS 生效后重跑 HTTPS health、/v1/models/v1/responses
  • D601 external-active apply 后其他 PK01 HTTPS 服务消失:优先怀疑共享 Caddy managed block 合并失败或旧整文件写入路径复现。用受控 Sub2API apply 输出和 PK01 Caddy managed block markers 取证,再通过各服务自己的 YAML apply/public-exposure 入口恢复;不要手工复制某一份 Caddyfile 作为长期修复。
  • Caddy 下载慢或失败:先确认 config/platform-infra/sub2api.yaml 已设置 publicExposure.pk01.caddyDownloadProxyUrl,并重跑 sub2api apply --target D601 --confirm 看 PK01 apply summary 中的 downloadProxy.mode=curl-proxy。不要反复裸连 GitHub release。
  • /responses/compact 在接近 master Caddy response_header_timeout 的固定时长后返回 504,或 Sub2API 日志稍后记录 codex.remote_compact.succeeded 时,优先检查 master Caddy response_header_timeout 是否由 YAML publicExposure.masterCaddy.responseHeaderTimeoutSeconds 渲染,修正后跑 codex-pool expose --confirm;这类边缘代理超时不会触发 Sub2API 账号级临时下线。reload 前已经在途的 compact 请求仍可能按旧 timeout 结束,判断修复是否生效时只看 reload 之后新发起的请求。
  • /responses/compact 或普通 public URL 在几秒窗口内出现 502Caddy 日志显示 dial tcp 127.0.0.1:<remotePort>: connect: connection refusedEOFconnection reset by peer,同时 frps 日志出现 platform-infra-sub2api proxy closing / listener is closed / new proxy ... success,说明失败在 master Caddy 与 FRP remotePort 边缘层,Sub2API 和 sentinel 可能完全看不到。先确认 publicExposure.masterCaddy.edgeRetry 已按 YAML 渲染并 codex-pool expose --confirm 生效;若仍频繁发生,再继续查 G14 sub2api-frpc 到 master frps 的控制连接稳定性。不要把这类边缘 502 误判成账号池上游错误,也不要通过禁用账号恢复。
  • default profile 递归:检查 YAML default entry 是否使用 *.pre-sub2api 备份文件;必要时恢复备份后重新 configure-local --confirm
  • 上游需要 WebSocket v2:先做 direct Codex WSv2 probe;通过后才给该 profile 配 openaiResponsesWebSocketsV2Mode: ctx_pool|passthrough 并跑 sync --confirm;把它当 capability candidate,容量仍以 YAML 中的 capacity 或默认值为准。
  • Codex 启动 WebSocket 回退:用原入口 Codex smoke 复现,再用 bounded Sub2API 日志确认 account;对 WS handshake 4xx/5xx、openai.websocket_account_select_failed 或 close-before-response.completed 的账号关闭 YAML WSv2 能力后同步。若没有剩余 WSv2-capable account,把 localCodex.supportsWebSocketslocalCodex.responsesWebSocketsV2 一起关掉,不把临时可用性推断写成调度配置。
  • 上游要求 Codex User-Agent:只给该 profile 配 upstreamUserAgent,跑 sync --confirm
  • 上游报 capacity/rate-limit/overload/Bad Gateway/Gateway Timeout 后没有隔离或频繁先失败再恢复:先看 codex-pool sentinel-report 的 marker、动作、冻结 TTL 和下一次 probe,也看 codex-pool validate --full 的 recent gateway failover/forward failure 证据;同时对照当前 Sub2API 源码里 /v1/responses handler、ForwardshouldFailoverOpenAIUpstreamResponsehandleOpenAIAccountUpstreamError 的真实传播路径。不要手动禁用账号、删除账号、改 membership/priority/capacity/loadFactor 或从 YAML 移除问题账号来替代通用 failover 与哨兵隔离/恢复。
  • codex-pool sync --confirmcodex-pool validate 超时:先区分 CLI 传输超时和 Sub2API 运行失败。受控 CLI 应返回远端作业进度和 stdout/stderr tail;如果只是低层 trans 60s 超时,不能据此判定 Sub2API failover 不工作。改用或修复 CLI 的远端 job/poll 路径后重跑,并以最终结构化结果作为证据。
  • Codex 报 weekly-limit、less than 10% of your weekly limit leftRun /status for a breakdown 等账号状态/软配额提示并要求切号:不要把新关键词写成 Sub2API 内置临时不可调度策略来恢复可用性;由 marker-only 哨兵按非 marker 响应统一冻结,并用 sentinel-report / sentinel-probe 验证。
  • 上游 400/503 响应体出现 invalid_encrypted_contentbad_response_status_codeinvalid_request_error + 稳定 unsupported-model 文案、unsupported-model、暂不支持 / 可用模型model_not_foundNo available channel for model ... 或同类稳定模型路由 / Responses encrypted-content 兼容性失败:按通用 temp-unschedulable/failover 加哨兵 marker 证据处理,不用 account membership、priority、capacity、loadFactor、WebSocket mode、User-Agent 或 provider pinning 掩盖该错误族。
  • 上游错误反复触发:invalid_encrypted_content、unsupported-model、Recovered upstream error ...Bad GatewayGateway Timeout、Cloudflare 524、Codex-facing Upstream request failedUnknown errorcontext deadline exceededcontext canceledmodel_not_foundNo available channel for model、大上下文 413openai_error 这类稳定包装文案,先确认 YAML temp-unschedulable 已同步、Sub2API 源码会把该错误族传播成 UpstreamFailoverError、运行日志出现 openai.upstream_failover_switching。若匹配规则后仍只看到 openai.forward_failed,根因是 Sub2API HTTP /responses 没把该错误传播成 UpstreamFailoverError,应修 Sub2API failover classifier/error propagation,不硬编码账号或给 only 特权。
  • Codex auto compact 后丢上下文:先确认 YAML localCodex 是否声明启用 WSv2;若启用,再确认本机 ~/.codex/config.toml 是否有 supports_websockets = trueresponses_websockets_v2 = true,并看 codex-pool validate 的 WSv2 candidate 和 Sub2API 日志里的 transport=responses_websockets_v2。若 YAML 当前禁用 WSv2,则按 HTTP Responses 稳定性排查,不把旧 WS 口径当成验收要求。
  • Codex smoke 有 reconnect/1013:这是上游并发/可用性问题,和 HTTP-only compact context-loss 分开处理;记录 session/log 证据并关联专项 issue,不要用运行时手补覆盖 YAML 容量。

禁止事项

  • 不用原生 kubectl apply/delete/patch 作为正式操作入口。
  • 不在 master server 部署或运行 Sub2API/PostgreSQL/Redis。
  • 不新增 Ingress、NodePort、LoadBalancer、hostPort、hostNetwork 或宽 FRP 端口段。
  • 不用 Sub2API 的 YAML 渲染结果整文件覆盖共享 PK01 Caddyfile;只能通过 managed block merge 更新 Sub2API 自己的块。
  • 不给 Sub2API manifest 添加 CPU/memory limits,除非有新的 YAML 化明确决策。
  • 不打印完整 API key、admin password 或 Secret 明文。
  • 不把普通上游增删做成代码变更、CI/CD、feature flag 或兼容双路径。
  • 不把手动禁用账号、删除账号、移除 YAML entry、降低 membership、临时改 priority/capacity/loadFactor、provider pinning 或给某个账号特权当作通用 failover / 哨兵隔离恢复问题的修复。
  • 不魔改 Sub2API:Sub2API 本身不支持的能力就不做,不通过 UniDesk 脚本、k8s 原地热补、本地 fork、YAML 伪声明或隐藏 fallback 代替上游实现。