icebear icebear0828 commited on
Commit
1e54caa
·
unverified ·
1 Parent(s): bf39e56

fix: remove isCodexCompatibleId whitelist — trust backend model list (#129)

Browse files

The regex whitelist filtered out models like gpt-5.4-mini whose ID
didn't match hardcoded patterns. Remove the filter entirely and trust
the /codex/models endpoint which only returns Codex-compatible models.

Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>

CHANGELOG.md CHANGED
@@ -8,6 +8,8 @@
8
 
9
  ### Fixed
10
 
 
 
11
  - 同一 Team 的多个账号因共享 `chatgpt_account_id` 只能添加一个的问题(#126)
12
  - 去重逻辑改为 `accountId + userId` 组合键,Team 成员各自保留独立条目
13
  - `AccountEntry` 新增 `userId` 字段,持久化层自动回填
 
8
 
9
  ### Fixed
10
 
11
+ - 新模型(如 `gpt-5.4-mini`)无法被动态发现的问题
12
+ - 移除 `isCodexCompatibleId()` 白名单过滤,信任后端 `/codex/models` 返回
13
  - 同一 Team 的多个账号因共享 `chatgpt_account_id` 只能添加一个的问题(#126)
14
  - 去重逻辑改为 `accountId + userId` 组合键,Team 成员各自保留独立条目
15
  - `AccountEntry` 新增 `userId` 字段,持久化层自动回填
src/models/__tests__/plan-routing.test.ts CHANGED
@@ -118,18 +118,18 @@ describe("plan-based model routing", () => {
118
  expect(getModelPlanTypes("nonexistent-model")).toEqual([]);
119
  });
120
 
121
- it("non-Codex model slugs are filtered out", () => {
122
  applyBackendModelsForPlan("free", [
123
  makeModel("gpt-5.2-codex"),
124
- makeModel("research"), // not Codex-compatible
125
- makeModel("gpt-5-2"), // hyphen instead of dot
126
- makeModel("some-internal-slug"), // not Codex-compatible
127
  ]);
128
 
129
  expect(getModelPlanTypes("gpt-5.2-codex")).toContain("free");
130
- expect(getModelPlanTypes("research")).toEqual([]);
131
- expect(getModelPlanTypes("gpt-5-2")).toEqual([]);
132
- expect(getModelPlanTypes("some-internal-slug")).toEqual([]);
133
  });
134
 
135
  it("gpt-oss-* models are admitted", () => {
 
118
  expect(getModelPlanTypes("nonexistent-model")).toEqual([]);
119
  });
120
 
121
+ it("all backend model slugs are admitted (no client-side filtering)", () => {
122
  applyBackendModelsForPlan("free", [
123
  makeModel("gpt-5.2-codex"),
124
+ makeModel("research"),
125
+ makeModel("gpt-5-2"),
126
+ makeModel("some-internal-slug"),
127
  ]);
128
 
129
  expect(getModelPlanTypes("gpt-5.2-codex")).toContain("free");
130
+ expect(getModelPlanTypes("research")).toContain("free");
131
+ expect(getModelPlanTypes("gpt-5-2")).toContain("free");
132
+ expect(getModelPlanTypes("some-internal-slug")).toContain("free");
133
  });
134
 
135
  it("gpt-oss-* models are admitted", () => {
src/models/model-store.ts CHANGED
@@ -140,33 +140,19 @@ function normalizeBackendModel(raw: BackendModelEntry): NormalizedModelWithMeta
140
  };
141
  }
142
 
143
- /** Check if a model ID is Codex-compatible (gpt-X.Y-codex-*, bare gpt-X.Y, or gpt-oss-*). */
144
- function isCodexCompatibleId(id: string): boolean {
145
- if (/^gpt-\d+(\.\d+)?-codex/.test(id)) return true;
146
- if (/^gpt-\d+(\.\d+)?$/.test(id)) return true;
147
- if (/^gpt-oss-/.test(id)) return true;
148
- return false;
149
- }
150
-
151
  /**
152
  * Merge backend models into the catalog.
153
  *
154
  * Strategy:
 
 
155
  * - Backend models overwrite static models with the same ID
156
  * (but YAML fields fill in missing backend fields)
157
  * - Static-only models are preserved (YAML may know about models the backend doesn't list)
158
- * - New Codex models from backend are auto-admitted (prevents missing new releases)
159
  * - Aliases are never touched (always from YAML)
160
  */
161
  export function applyBackendModels(backendModels: BackendModelEntry[]): void {
162
- // Keep models that either exist in static catalog OR are Codex models.
163
- // This prevents ChatGPT-only slugs (gpt-5-2, research, etc.) from
164
- // entering the catalog, while auto-admitting new Codex releases.
165
- const staticIds = new Set(_catalog.map((m) => m.id));
166
- const filtered = backendModels.filter((raw) => {
167
- const id = raw.slug ?? raw.id ?? raw.name ?? "";
168
- return staticIds.has(id) || isCodexCompatibleId(id);
169
- });
170
 
171
  const staticMap = new Map(_catalog.map((m) => [m.id, m]));
172
  const merged: CodexModelInfo[] = [];
@@ -206,9 +192,8 @@ export function applyBackendModels(backendModels: BackendModelEntry[]): void {
206
 
207
  _catalog = merged;
208
  _lastFetchTime = new Date().toISOString();
209
- const skipped = backendModels.length - filtered.length;
210
  console.log(
211
- `[ModelStore] Merged ${filtered.length} backend (${skipped} non-codex skipped) + ${merged.length - filtered.length} static-only = ${merged.length} total models`,
212
  );
213
 
214
  // Auto-sync merged catalog back to models.yaml
@@ -261,12 +246,9 @@ export function applyBackendModelsForPlan(planType: string, backendModels: Backe
261
 
262
  // Build new model set for this plan and replace atomically
263
  const admittedIds = new Set<string>();
264
- const catalogIds = new Set(_catalog.map((m) => m.id));
265
  for (const raw of backendModels) {
266
  const id = raw.slug ?? raw.id ?? raw.name ?? "";
267
- if (catalogIds.has(id) || isCodexCompatibleId(id)) {
268
- admittedIds.add(id);
269
- }
270
  }
271
  _planModelMap.set(planType, admittedIds);
272
 
 
140
  };
141
  }
142
 
 
 
 
 
 
 
 
 
143
  /**
144
  * Merge backend models into the catalog.
145
  *
146
  * Strategy:
147
+ * - Trust backend: all models returned by the backend are accepted
148
+ * (primary endpoint /codex/models only returns Codex-compatible models)
149
  * - Backend models overwrite static models with the same ID
150
  * (but YAML fields fill in missing backend fields)
151
  * - Static-only models are preserved (YAML may know about models the backend doesn't list)
 
152
  * - Aliases are never touched (always from YAML)
153
  */
154
  export function applyBackendModels(backendModels: BackendModelEntry[]): void {
155
+ const filtered = backendModels;
 
 
 
 
 
 
 
156
 
157
  const staticMap = new Map(_catalog.map((m) => [m.id, m]));
158
  const merged: CodexModelInfo[] = [];
 
192
 
193
  _catalog = merged;
194
  _lastFetchTime = new Date().toISOString();
 
195
  console.log(
196
+ `[ModelStore] Merged ${filtered.length} backend + ${merged.length - filtered.length} static-only = ${merged.length} total models`,
197
  );
198
 
199
  // Auto-sync merged catalog back to models.yaml
 
246
 
247
  // Build new model set for this plan and replace atomically
248
  const admittedIds = new Set<string>();
 
249
  for (const raw of backendModels) {
250
  const id = raw.slug ?? raw.id ?? raw.name ?? "";
251
+ if (id) admittedIds.add(id);
 
 
252
  }
253
  _planModelMap.set(planType, admittedIds);
254