动机见 proposal.md。与方案相关的现状:
OpenAIGatewayHandler.CodexModels:先尝试用账号模型映射本地生成;否则循环 SelectAccountForModelWithExclusions 选账号、FetchCodexModelsManifest 拉取、CompleteAPIKeyCodexModelsManifestForClient 补全、MergeGroupConfiguredCodexModels 做分组过滤与 ETag。FetchCodexModelsManifest 对 API Key 账号走 fetchCachedAPIKeyCodexModelsManifest(30 秒新鲜、5 分钟乐观、单飞后台刷新),对 OAuth 账号直接请求上游且带 agent identity 任务恢复逻辑,不缓存。service.Group → group_repo 创建与更新 setter → api_key_repo 分组字段投影与 groupEntityToService → 认证快照 api_key_auth_cache.go 结构体及 impl 两处映射 → 管理端 DTO / mapper → admin_group.go 创建与更新 → 分组复制。models_list_config 是完整样板。GroupsView.vue 已有 7000 行;模型路由的账号选择用「标签 + 搜索输入 + 下拉」内联实现,Select.vue 仅支持单选。components/admin/group/ 下已有抽出的表单片段组件(如 ReasoningEffortPolicyFields.vue)。platform、group、search 过滤。Goals:
Non-Goals:
groups.codex_models_manifest_config,领域类型 domain.GroupCodexModelsManifestConfig{Enabled bool; AccountIDs []int64; FallbackToScheduler bool},JSON 键为 enabled、account_ids、fallback_to_scheduler。
models_list_config 一致。234_group_codex_models_manifest_config.sql:ADD COLUMN IF NOT EXISTS ... JSONB NOT NULL DEFAULT '{}'::jsonb。ent schema 使用 field.JSON(...).Default(domain.GroupCodexModelsManifestConfig{})。group_codex_models_manifest.go 提供 normalizeCodexModelsManifestConfig(平台非 openai 归零;去重保序;enabled=false 时保留列表便于再次启用)与 validateCodexModelsManifestConfig(ctx, groupID, cfg)(enabled=true 时:非空、≤10、全部属于 accountRepo.ListByGroup(groupID) 且平台 openai)。enabled=true 直接 400。原因:账号绑定发生在分组创建之后,创建时无法校验成员关系;前端也不展示。group.Platform == openai && cfg.Enabled,调用 gatewayService.FetchPinnedCodexModelsManifest(ctx, group, clientVersion)。(*OpenAIModelsResponse, *Account, error),*Account 为配置顺序中第一个成功账号,用于 setOpsSelectedAccount。ErrNoPinnedCodexModelsAccounts 表示;「全部失败」返回最后一个上游错误。handler 根据 FallbackToScheduler 决定:true 时跌入现有调度器循环;false 时无可用账号返回 503、全部失败按 infraerrors.Code(err) 返回。IsActive() && Schedulable && !(AutoPauseOnExpired && 已过期)。刻意不使用 IsSchedulable(),因为它包含限流、过载与临时不可调度窗口。accountRepo.ListByGroup(group.ID)(active 成员),按配置顺序筛选并跳过缺失 ID。BuildGroupConfiguredCodexModelsManifest 已加载的账号列表以省一次查询。否决:那两个列表是可调度集合,会漏掉限流中的账号;一次按分组的查询成本可接受。sync.WaitGroup 对每个可用账号并发执行:FetchCodexModelsManifest(ctx, acc, clientVersion, "") → API Key 账号再 CompleteAPIKeyCodexModelsManifestForClient。结果写入按配置顺序索引的切片,失败记录到同下标的错误切片;各账号独立完成,不因单账号失败取消其他请求。mergeCodexModelsManifestBodies(bodies [][]byte) ([]byte, error):以第一个 body 的顶层信封为基底,models 按 slug 并集,先出现者优先;slug 为空或解析失败的条目按出现顺序保留一次。输出后设置 ETag = codexModelsManifestBodyETag(body),再交给 MergeGroupConfiguredCodexModels 做分组过滤与 304 判断。slog.Warn 带 group_id 与失败账号 ID 列表。openai_codex_models_pinned.go,避免继续膨胀 2300 行的主文件。fetchCachedAPIKeyCodexModelsManifest 泛化为 fetchCachedOpenAIModels(ctx, request, fetch, ifNoneMatch),fetch func(ctx, ifNoneMatch) (*CodexModelsManifest, error) 由调用方提供:API Key 路径传 fetchCodexModelsManifestUpstream;OAuth 路径传一个包含 agent identity 任务恢复逻辑的闭包。handleCodexModelsManifestAccountAuthError 在 OAuth 闭包返回错误时照旧调用。openAIModelsCacheTTL 30s → 60s;openAIModelsCacheStaleTTL 保持 5 分钟;超过 5 分钟 get 删除条目返回 miss,调用方同步等待单飞结果,这与「超期强制等待上游刷新」一致,无需新状态。client_version 不同的客户端各占一个条目。openAIModelsCacheMaxEntries 64 → 512。OAuth 路径接入后条目数为「账号数 × 客户端版本数」,64 条在大规模部署下会被淘汰导致额外上游请求;单条清单通常几十 KB,最坏情况内存占用为几十 MB 量级。context.Background() 加上游 15 秒超时,与现状一致。components/admin/group/CodexManifestAccountsField.vue:props 为 groupId、modelValue({enabled, account_ids, fallback_to_scheduler})与已选账号名称映射;内部实现开关、标签列表、带防抖的搜索输入与下拉、回退子开关。搜索调用 adminAPI.accounts.list(1, 20, {search, platform: 'openai', group: String(groupId)})。GroupsView.vue 只在编辑对话框 OpenAI 区块挂载组件,打开编辑时用 adminAPI.accounts.getById 解析已存 ID 的名称,失败则显示 #<id>。提交时开关打开且列表为空则 toast 报错并阻止。#<id> 提示管理员清理;全部失效时按回退配置处理。{} 反序列化为关闭状态,存量分组行为不变。DROP COLUMN。GatewayHandler.Models 在 OpenAI 分组开启固定账号时调用 FetchPinnedOpenAIModelsList;普通 /models 别名共用该入口。client_version 或身份请求头;从管理端抽出 API Key 请求构造。OAuth 复用 FetchCodexModelsManifest 和规范客户端版本,保留凭据引用、Agent Identity 恢复、401 状态处理。OpenAIModelsResponse;抽出原始 HTTP 响应获取,Codex 专用转换/元数据补全仅在 manifest 路径执行。两种请求使用同一缓存实现,以响应格式、完整 URL、账号、凭据、代理和请求头区分条目;相同 OAuth 请求共享条目。id、created、owned_by 等字段。OAuth slug 转成标准条目;无创建时间采用 0。有效空数组是成功结果,缺失或错误的数组结构是上游错误。ResolveMappedModel 的匹配优先级。新增路由/handler 与 service 测试覆盖普通两条路由、固定账号优先、原始媒体和未知模型、映射与通配别名、部分/全部失败及调度器回退、凭据/请求协议隔离、OAuth 缓存复用、三段缓存时效、分组过滤隔离与空目录、最终 ETag。运行受影响 Go 测试(含 race)、完整 unit 检查及前端类型/组件检查。