Spaces:
Paused
Paused
icebear icebear0828 commited on
fix: remove isCodexCompatibleId whitelist — trust backend model list (#129)
Browse filesThe 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 +2 -0
- src/models/__tests__/plan-routing.test.ts +7 -7
- src/models/model-store.ts +5 -23
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("
|
| 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")).
|
| 131 |
-
expect(getModelPlanTypes("gpt-5-2")).
|
| 132 |
-
expect(getModelPlanTypes("some-internal-slug")).
|
| 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 |
-
|
| 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
|
| 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 (
|
| 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 |
|