Client or integration
Direct HTTP/API client (also visible in the OpenCodex dashboard and ocx models live)
Area
Catalog / models
Summary
mergeConfiguredModelsIntoLiveCatalog() has a dated-alias folding path whose job is to keep a configured model id in the catalog when live discovery returns the same model under a different, date-suffixed id. That fold is implemented by isDatedVariantId(), which is wrong in two independent ways:
- It only accepts an 8-digit
YYYYMMDD suffix. Providers that use a 4-digit MMDD suffix (Alibaba Token Plan / DeepSeek: deepseek-v4-pro-0813, deepseek-v4-flash-0731) never match.
- It only folds
configured = base -> live = dated. The reverse case (configured = dated, live = base) is not handled at all.
The result: a model that is on the account's plan, present in providers.<name>.models, and verifiably callable is silently removed from the authoritative live catalog, so it cannot be selected in the dashboard, in ocx models live, or in the Codex model picker. GET /api/providers still reports discovery: { "status": "ok" }, so nothing on the API surface indicates a model was dropped.
I expected the configured id to survive, because upstream does return this model — just under its base id (deepseek-v4-pro) — which is exactly the situation the dated-alias fold exists to handle.
Note this is not the same request as #1690. #1690 asks for a new opt-in retainModels field to retain models that upstream genuinely does not return, and the maintainer review there correctly notes that retaining unconditionally would resurrect dead rows. This report is narrower: the existing fold already requires a matching live row to be present before it retains anything, so fixing it cannot resurrect a model the upstream does not advertise.
Reproduction
-
Configure alibaba-token-plan-intl (international Token Plan, ap-southeast-1) with liveModels: true and a models list that matches the plan's text/reasoning models, including deepseek-v4-pro-0813.
-
Confirm the configured list is intact:
GET /api/providers -> models: [ ..., "deepseek-v4-pro", "deepseek-v4-pro-0813", ... ] (9 ids)
-
Run live discovery:
ocx models live --provider alibaba-token-plan-intl
-
Observe that deepseek-v4-pro-0813 is gone, while deepseek-v4-pro remains. GET /api/models shows the same 10 rows.
-
Confirm the dropped model is actually callable, both directly upstream and through the proxy (see logs below). Both return HTTP 200.
Version
2.36.0
Operating system
Windows 11 Pro 26200
Provider and model
alibaba-token-plan-intl / deepseek-v4-pro-0813
Logs or error output
# 1) Configured models via management API -- 9 ids, includes the dated one
GET /api/providers -> alibaba-token-plan-intl
liveModels = true
models = qwen3.8-max, qwen3.7-max, qwen3.7-plus, qwen3.6-flash,
deepseek-v4-pro, deepseek-v4-pro-0813, glm-5.2,
deepseek-v4-flash-0731, qwen3.8-flash
discovery = { "status": "ok" } # no indication anything was dropped
# 2) Upstream GET /models (public Token Plan intl endpoint) -- 12 ids
# NOTE: it advertises the BASE id only.
deepseek-v4-flash-0731
deepseek-v4-pro <-- base id present
glm-5.2
qwen-audio-3.0-realtime-plus
qwen-audio-3.0-tts-plus
qwen3.6-flash
qwen3.7-max
qwen3.7-plus
qwen3.8-flash
qwen3.8-max
wan2.7-image (filtered out by OpenCodex: image-generation)
wan2.7-image-pro (filtered out by OpenCodex: image-generation)
# deepseek-v4-pro-0813 is NOT advertised upstream
# 3) ocx models live --provider alibaba-token-plan-intl -- 10 rows, dated id dropped
alibaba-token-plan-intl/deepseek-v4-flash-0731 [routed, disabled]
alibaba-token-plan-intl/deepseek-v4-pro [routed, disabled]
alibaba-token-plan-intl/glm-5.2 [routed, disabled]
alibaba-token-plan-intl/qwen-audio-3.0-realtime-plus [routed, disabled]
alibaba-token-plan-intl/qwen-audio-3.0-tts-plus [routed, disabled]
alibaba-token-plan-intl/qwen3.6-flash [routed, disabled]
alibaba-token-plan-intl/qwen3.7-max [routed, disabled]
alibaba-token-plan-intl/qwen3.7-plus [routed, disabled]
alibaba-token-plan-intl/qwen3.8-flash [routed, disabled]
alibaba-token-plan-intl/qwen3.8-max [routed, enabled]
# deepseek-v4-pro-0813 absent
# 4) The dropped model is callable -- DIRECT to upstream
POST <token-plan intl baseUrl>/chat/completions
Authorization: Bearer <redacted>
{"model":"deepseek-v4-pro-0813","messages":[{"role":"user","content":"Reply with exactly: OK"}],"max_tokens":32}
-> HTTP 200 model="deepseek-v4-pro-0813" content="OK"
# 5) The dropped model is callable -- THROUGH the OpenCodex proxy
POST /v1/chat/completions
{"model":"alibaba-token-plan-intl/deepseek-v4-pro-0813","messages":[{"role":"user","content":"Reply with exactly: OK"}],"max_tokens":32}
-> HTTP 200 model="deepseek-v4-pro-0813" content="OK"
# So routing by explicit namespace still works; only catalog/picker visibility is lost.
Root cause
src/codex/catalog/provider-fetch.ts:938 (identical on main as of this report):
export function isDatedVariantId(liveId: string, configuredId: string): boolean {
if (!liveId.startsWith(`${configuredId}-`)) return false;
return /^\d{8}$/.test(liveId.slice(configuredId.length + 1));
}
Used by the fold in mergeConfiguredModelsIntoLiveCatalog() at src/codex/catalog/provider-fetch.ts:1668-1686:
for (const candidate of configured) {
if (present.has(candidate.id)) continue;
const dated = out.find(live => isDatedVariantId(live.id, candidate.id));
if (dated) { /* retain candidate.id, with provider config hints applied */ }
...
droppedConfiguredIds.push(candidate.id);
}
For configured = "deepseek-v4-pro-0813", live = "deepseek-v4-pro":
liveId.startsWith("deepseek-v4-pro-0813-") is false -> no match (direction).
- Even with the arguments swapped, the slice yields
"0813", and /^\d{8}$/ rejects 4 digits (format).
Both guards fail, so the id falls through to droppedConfiguredIds. The only remaining retention path is shouldRetainConfiguredProviderModel() at src/codex/catalog/provider-fetch.ts:1631, which is hardcoded to kimi, xai, and opencode-free — so there is no escape hatch for this provider.
alibaba-token-plan-intl is not in QUIET_AUTHORITATIVE_CATALOG_PROVIDERS, so warnDroppedConfiguredIdsOnce() does emit a one-shot console warning, but GET /api/providers still reports discovery.status = "ok", which is what makes this look like "the model does not exist" from the dashboard.
Suggested fix
Make the fold symmetric and accept MMDD, while keeping it strict enough not to swallow non-date numeric suffixes (e.g. a -4096 context-size suffix):
const DATE_SUFFIX = /^(\d{8}|(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01]))$/;
function hasDatedSuffix(longer: string, shorter: string): boolean {
return longer.startsWith(`${shorter}-`)
&& DATE_SUFFIX.test(longer.slice(shorter.length + 1));
}
export function isDatedVariantId(liveId: string, configuredId: string): boolean {
return hasDatedSuffix(liveId, configuredId) || hasDatedSuffix(configuredId, liveId);
}
This stays safe with respect to the concern raised in the #1690 review: the fold still only retains a configured id when a corresponding live row is actually present, and the retained row already goes through applyProviderConfigHints(), so per-model context/reasoning hints from config still win.
Happy to open a PR with unit tests covering both directions and both suffix widths if that is useful.
Redacted configuration
{
"providers": {
"alibaba-token-plan-intl": {
"adapter": "openai-chat",
"baseUrl": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
"apiKey": "<redacted>",
"defaultModel": "qwen3.8-max",
"liveModels": true,
"models": [
"qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash",
"deepseek-v4-pro", "deepseek-v4-pro-0813", "glm-5.2",
"deepseek-v4-flash-0731", "qwen3.8-flash"
]
}
}
}
The plan console lists deepseek-v4-pro-0813, deepseek-v4-pro, and deepseek-v4-flash-0731 as three separate entitled Text Generation / Reasoning models, which is why all three are in models.
Related: #1690 (feature request for an opt-in retainModels allow-list — complementary, not the same defect), #1671 (earlier user-facing symptom of a configured model being dropped by live discovery).
Client or integration
Direct HTTP/API client (also visible in the OpenCodex dashboard and
ocx models live)Area
Catalog / models
Summary
mergeConfiguredModelsIntoLiveCatalog()has a dated-alias folding path whose job is to keep a configured model id in the catalog when live discovery returns the same model under a different, date-suffixed id. That fold is implemented byisDatedVariantId(), which is wrong in two independent ways:YYYYMMDDsuffix. Providers that use a 4-digitMMDDsuffix (Alibaba Token Plan / DeepSeek:deepseek-v4-pro-0813,deepseek-v4-flash-0731) never match.configured = base->live = dated. The reverse case (configured = dated,live = base) is not handled at all.The result: a model that is on the account's plan, present in
providers.<name>.models, and verifiably callable is silently removed from the authoritative live catalog, so it cannot be selected in the dashboard, inocx models live, or in the Codex model picker.GET /api/providersstill reportsdiscovery: { "status": "ok" }, so nothing on the API surface indicates a model was dropped.I expected the configured id to survive, because upstream does return this model — just under its base id (
deepseek-v4-pro) — which is exactly the situation the dated-alias fold exists to handle.Note this is not the same request as #1690. #1690 asks for a new opt-in
retainModelsfield to retain models that upstream genuinely does not return, and the maintainer review there correctly notes that retaining unconditionally would resurrect dead rows. This report is narrower: the existing fold already requires a matching live row to be present before it retains anything, so fixing it cannot resurrect a model the upstream does not advertise.Reproduction
Configure
alibaba-token-plan-intl(international Token Plan,ap-southeast-1) withliveModels: trueand amodelslist that matches the plan's text/reasoning models, includingdeepseek-v4-pro-0813.Confirm the configured list is intact:
Run live discovery:
Observe that
deepseek-v4-pro-0813is gone, whiledeepseek-v4-proremains.GET /api/modelsshows the same 10 rows.Confirm the dropped model is actually callable, both directly upstream and through the proxy (see logs below). Both return HTTP 200.
Version
2.36.0
Operating system
Windows 11 Pro 26200
Provider and model
alibaba-token-plan-intl / deepseek-v4-pro-0813
Logs or error output
Root cause
src/codex/catalog/provider-fetch.ts:938(identical onmainas of this report):Used by the fold in
mergeConfiguredModelsIntoLiveCatalog()atsrc/codex/catalog/provider-fetch.ts:1668-1686:For
configured = "deepseek-v4-pro-0813",live = "deepseek-v4-pro":liveId.startsWith("deepseek-v4-pro-0813-")isfalse-> no match (direction)."0813", and/^\d{8}$/rejects 4 digits (format).Both guards fail, so the id falls through to
droppedConfiguredIds. The only remaining retention path isshouldRetainConfiguredProviderModel()atsrc/codex/catalog/provider-fetch.ts:1631, which is hardcoded tokimi,xai, andopencode-free— so there is no escape hatch for this provider.alibaba-token-plan-intlis not inQUIET_AUTHORITATIVE_CATALOG_PROVIDERS, sowarnDroppedConfiguredIdsOnce()does emit a one-shot console warning, butGET /api/providersstill reportsdiscovery.status = "ok", which is what makes this look like "the model does not exist" from the dashboard.Suggested fix
Make the fold symmetric and accept
MMDD, while keeping it strict enough not to swallow non-date numeric suffixes (e.g. a-4096context-size suffix):This stays safe with respect to the concern raised in the #1690 review: the fold still only retains a configured id when a corresponding live row is actually present, and the retained row already goes through
applyProviderConfigHints(), so per-model context/reasoning hints from config still win.Happy to open a PR with unit tests covering both directions and both suffix widths if that is useful.
Redacted configuration
{ "providers": { "alibaba-token-plan-intl": { "adapter": "openai-chat", "baseUrl": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1", "apiKey": "<redacted>", "defaultModel": "qwen3.8-max", "liveModels": true, "models": [ "qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash", "deepseek-v4-pro", "deepseek-v4-pro-0813", "glm-5.2", "deepseek-v4-flash-0731", "qwen3.8-flash" ] } } }The plan console lists
deepseek-v4-pro-0813,deepseek-v4-pro, anddeepseek-v4-flash-0731as three separate entitled Text Generation / Reasoning models, which is why all three are inmodels.Related: #1690 (feature request for an opt-in
retainModelsallow-list — complementary, not the same defect), #1671 (earlier user-facing symptom of a configured model being dropped by live discovery).