From 2d26c3dc406995d96b23ef3815f003d0bff0dbe6 Mon Sep 17 00:00:00 2001 From: norths7ar Date: Fri, 23 Oct 2026 13:22:18 +0800 Subject: [PATCH 1/6] fix(deepseek): restore official V4 Pro availability --- docs-site/src/content/docs/guides/providers.md | 4 ++++ src/codex/catalog/parsing.ts | 12 ++++-------- src/providers/registry/entries-core.ts | 18 ++++++++---------- src/providers/registry/model-seeds.ts | 15 +++++---------- structure/catalog-vendor-context.md | 2 ++ .../codex-catalog-exclusions.test.ts | 3 ++- tests/providers/deepseek-inbound-wire.test.ts | 6 ++++++ .../providers/provider-registry-parity.test.ts | 17 +++++++---------- 8 files changed, 38 insertions(+), 39 deletions(-) diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index 44bb7b0421c..090e6dcdc91 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -660,6 +660,10 @@ selectors, then retry. Signing in from a machine with no existing `kiro-cli` ses The native dashboard choices are add-only and do not sign out `kiro-cli`. A device dialog shows the code and verification destination. Only recognized Kiro or Builder ID hosts are opened as links; an unexpected destination is shown as copyable text for review. Closing the dialog sends cancellation and leaves a bounded background status check to reconcile a commit already in progress. A stalled status response is retried; exhausting the flow deadline produces the neutral ended outcome rather than claiming success. Google and GitHub approval completes when Kiro reports `authorized` (or the legacy `approved` status) with valid tokens and a profile ARN. The account list marks Kiro accounts excluded from automatic selection with a reason, when available. +### DeepSeek API + +The `deepseek` API-key provider includes `deepseek-flash`, its `deepseek-v4-flash` alias, and `deepseek-v4-pro`. DeepSeek [withdrew the Pro retirement plan](https://api-docs.deepseek.com/updates/) and continues serving it; opencodex retains Pro in the catalog with a 1,048,576-token context window, text-only input, and `low`/`high`/`max` reasoning effort. Responses requests use its native Responses endpoint; Chat Completions requests retain the Chat wire. Flash remains the default. Third-party providers have independent model availability. + ### DeepSeek Account (DSH) `dsh-account` is an experimental, import-first account provider designed to reuse the authenticated account credentials of an installed DeepSeek Harness (DSH) Desktop application. diff --git a/src/codex/catalog/parsing.ts b/src/codex/catalog/parsing.ts index bdab81c61e3..27693aa4cdc 100644 --- a/src/codex/catalog/parsing.ts +++ b/src/codex/catalog/parsing.ts @@ -184,15 +184,11 @@ export const ROUTED_MODEL_COMPATIBILITY_EXCLUSIONS = new Set([ "opencode-go/mimo-v2-omni", "opencode-go/mimo-v2-pro", /* - * DeepSeek retired `deepseek-v4-pro` on 2026-09-14 04:00 UTC and routes its requests to - * V4.1-Flash (api-docs.deepseek.com/news/news260910). Deleting the registry rows removes - * the model on providers that publish a static roster, but every provider below discovers - * its models live — there, a deleted row does not remove anything, it only strips the - * context window, the effort ladder and the text-only hint, so the retired model would - * keep appearing with its capabilities broken. Excluding the slug is what actually takes - * it out of the routed catalog. + * These third-party exclusions originated in the superseded V4 Pro retirement plan. + * First-party service continues (api-docs.deepseek.com/updates/); these gateway routes + * remain excluded pending provider-specific verification. Live discovery can reintroduce + * a model removed from a static registry, so these exclusions act on the routed catalog. */ - "deepseek/deepseek-v4-pro", "opencode-go/deepseek-v4-pro", "command-code/deepseek-deepseek-v4-pro", "commandcode/deepseek-deepseek-v4-pro", diff --git a/src/providers/registry/entries-core.ts b/src/providers/registry/entries-core.ts index 24c66507899..e116d7d5c38 100644 --- a/src/providers/registry/entries-core.ts +++ b/src/providers/registry/entries-core.ts @@ -1247,17 +1247,18 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [ // the list only as compatibility aliases so existing saved configs and requests // keep validating and routing (they previously mapped to v4-flash; devlog // _fin/260710_provider_hardening/002_research_cn.md). The current offerings are - // V4.1-Flash — defaultModel and the model-specific wiring below use its live id. + // V4.1-Flash and V4 Pro; Flash remains the default. // Keep the legacy vision-preview alias; see DEEPSEEK_VISION_PREVIEW_MODEL. models: ["deepseek-chat", "deepseek-reasoner", ...DEEPSEEK_NATIVE_THINKING_MODELS, DEEPSEEK_VISION_PREVIEW_MODEL], - // V4.1-Flash is the current first-party offering; `deepseek-v4-flash` now routes there + // V4.1-Flash is the first-party default; `deepseek-v4-flash` now routes there // as a compatibility alias, so a new install should ask for the live id by name. defaultModel: "deepseek-flash", // Official DeepSeek Codex setup (codex-deepseek-setup.sh) advertises 1,048,576 // for both V4 models; the older 1,000,000 figure was a rounded approximation. - modelContextWindows: { "deepseek-flash": 1_048_576, "deepseek-v4-flash": 1_048_576, [DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576 }, + modelContextWindows: { "deepseek-flash": 1_048_576, "deepseek-v4-flash": 1_048_576, "deepseek-v4-pro": 1_048_576, [DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576 }, modelInputModalities: { "deepseek-flash": ["text", "image"], + "deepseek-v4-pro": ["text"], [DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"], }, // DeepSeek documents both V4 models as native Responses API models adapted for Codex @@ -1272,6 +1273,7 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [ // translating them into Responses would add a hop onto our newest upstream path // for no gain. "deepseek-v4-flash": { wire: "openai-responses", inbound: ["responses"] }, + "deepseek-v4-pro": { wire: "openai-responses", inbound: ["responses"] }, // Same Responses contract as the V4 ids it succeeds; without this row the new // default would fall back to the provider-wide Chat wire. "deepseek-flash": { wire: "openai-responses", inbound: ["responses"] }, @@ -1328,16 +1330,12 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [ modelReasoningEfforts: Object.fromEntries(DEEPSEEK_NATIVE_THINKING_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])), modelReasoningEffortMap: Object.fromEntries(DEEPSEEK_NATIVE_THINKING_MODELS.map(id => [id, deepseekReasoningMapFor(id)])), modelSupportsReasoningSummaries: Object.fromEntries(DEEPSEEK_NATIVE_THINKING_MODELS.map(id => [id, true])), - // deepseek-v4-pro is absent from the live sets on purpose (retired upstream, requests - // route to V4.1-Flash — see model-seeds.ts), but it stays SELECTABLE in configs that - // already carry it, and what serves it is a thinking-mode model. A saved config with - // the alias selected would otherwise serialize a bare continuation the upstream 400s - // on (#5421). The preserve list governs replay, not advertisement. - preserveReasoningContentModels: [...DEEPSEEK_NATIVE_THINKING_MODELS, "deepseek-v4-pro"], + // Native thinking models retain assistant reasoning on tool-call continuations (#5421). + preserveReasoningContentModels: DEEPSEEK_NATIVE_THINKING_MODELS, // #4436: first-party deepseek-flash accepts native images on Chat and Responses. // Keep unprobed compatibility aliases on the #88 sidecar path. This must be fixed // here: router enrichment unions this list with saved config, so config cannot remove it. - noVisionModels: ["deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash"], + noVisionModels: ["deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro"], }, // llama-3.3-70b was deprecated by Cerebras on 2026-02-16. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md. { id: "cerebras", label: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://cloud.cerebras.ai/platform/apikeys", defaultModel: "gpt-oss-120b" }, diff --git a/src/providers/registry/model-seeds.ts b/src/providers/registry/model-seeds.ts index 674b37fe607..d9576b78b3b 100644 --- a/src/providers/registry/model-seeds.ts +++ b/src/providers/registry/model-seeds.ts @@ -377,8 +377,8 @@ export const OPENCODE_GO_THINKING_BUDGET_MODELS = ["qwen3.5-plus", "qwen3.6-plus /* * DeepSeek moved the whole V4 name set on 2026-09-10. V4.1-Flash ships as deepseek-flash * on the first-party API; deepseek-v4-flash and the vision preview retire as models but - * keep routing there as compatibility aliases, and deepseek-v4-pro follows from - * 2026-09-14 04:00 UTC. Evidence: https://api-docs.deepseek.com/news/news260910/. + * keep routing there as compatibility aliases. V4 Pro remains available: the planned + * retirement was withdrawn. Evidence: https://api-docs.deepseek.com/updates/ (2026-10-23). * * The spelling differs by who serves it, so one shared list cannot express it: the * first-party API answers to deepseek-flash, while the Zen gateway exposes the route as @@ -388,15 +388,10 @@ export const OPENCODE_GO_THINKING_BUDGET_MODELS = ["qwen3.5-plus", "qwen3.6-plus */ export const DEEPSEEK_V4_LEGACY_MODELS = ["deepseek-v4-flash"]; /* - * `deepseek-v4-pro` is deliberately absent from both live sets. DeepSeek retires it from - * 2026-09-14 04:00 UTC and routes its requests to V4.1-Flash until a V4.1 Pro exists, so a - * row here would advertise a Pro context window and Pro pricing for a route that serves - * Flash. The retirement is followed through every roster in this file, including the - * vendor-hosted ones; providers that discover their models live are handled by - * `ROUTED_MODEL_COMPATIBILITY_EXCLUSIONS` because deleting a row there removes the - * model's capabilities rather than the model. + * The official API continues serving V4 Pro after September 14 with unchanged billing. + * Gateway availability is independent; restoring the native set does not change gateway rosters. */ -export const DEEPSEEK_NATIVE_THINKING_MODELS = ["deepseek-flash", "deepseek-v4-flash"]; +export const DEEPSEEK_NATIVE_THINKING_MODELS = ["deepseek-flash", "deepseek-v4-flash", "deepseek-v4-pro"]; export const DEEPSEEK_GATEWAY_THINKING_MODELS = ["deepseek-v4.1-flash", "deepseek-v4-flash"]; /* * DeepSeek's legacy vision preview id (released 2026-08-21). First-party probes diff --git a/structure/catalog-vendor-context.md b/structure/catalog-vendor-context.md index 2481d17e29f..555085a0cdc 100644 --- a/structure/catalog-vendor-context.md +++ b/structure/catalog-vendor-context.md @@ -43,3 +43,5 @@ Compaction and max input still follow the effective default window, including retained lower soft budgets. Native capacity remains defined by the [native passthrough contract](catalog.md#native-passthrough). Covered by `tests/codex-integration/catalog-native-max-context.test.ts`. + +The first-party DeepSeek catalog retains `deepseek-v4-pro`: its retirement plan was withdrawn, and the official models endpoint still advertises it. `src/codex/catalog/parsing.ts` does not exclude that first-party row; third-party exclusions remain provider-specific. `src/providers/registry/entries-core.ts` restores its text-only capability, context window, reasoning controls and inbound-scoped Responses route while retaining Flash as the default. Evidence: [official service continuation notice](https://api-docs.deepseek.com/updates/), checked 2026-10-23. diff --git a/tests/codex-integration/codex-catalog-exclusions.test.ts b/tests/codex-integration/codex-catalog-exclusions.test.ts index 747196a0ea5..7bfc86eff73 100644 --- a/tests/codex-integration/codex-catalog-exclusions.test.ts +++ b/tests/codex-integration/codex-catalog-exclusions.test.ts @@ -7,7 +7,8 @@ describe("routed catalog compatibility exclusions", () => { // Issue #2330: uncallable or stale OpenCode Go models expect(shouldExposeRoutedModel({ provider: "opencode-go", id: "mimo-v2-omni" })).toBe(false); expect(shouldExposeRoutedModel({ provider: "opencode-go", id: "mimo-v2-pro" })).toBe(false); - expect(shouldExposeRoutedModel({ provider: "deepseek", id: "deepseek-v4-pro" })).toBe(false); + // First-party retirement was withdrawn; gateway availability remains independent. + expect(shouldExposeRoutedModel({ provider: "deepseek", id: "deepseek-v4-pro" })).toBe(true); expect(shouldExposeRoutedModel({ provider: "opencode-go", id: "deepseek-v4-pro" })).toBe(false); expect(shouldExposeRoutedModel({ provider: "opencode-go", id: "grok-4.6" })).toBe(true); expect(shouldExposeRoutedModel({ provider: "opencode-go", id: "glm-5.2" })).toBe(true); diff --git a/tests/providers/deepseek-inbound-wire.test.ts b/tests/providers/deepseek-inbound-wire.test.ts index 72b6b9c2f17..98fbbccaa27 100644 --- a/tests/providers/deepseek-inbound-wire.test.ts +++ b/tests/providers/deepseek-inbound-wire.test.ts @@ -112,6 +112,12 @@ function deepseekReasoningProvider(): OcxProviderConfig { } describe("DeepSeek wire selection is scoped to the inbound protocol", () => { + test("restored Pro preserves native Responses and translated Chat dispatch", () => { + for (const inbound of ["responses", "chat", "anthropic"] as const) { + expect(resolveWireProtocolOverride("deepseek", "deepseek-v4-pro", deepseekProvider(), inbound).adapter) + .toBe(inbound === "responses" ? "openai-responses" : "openai-chat"); + } + }); test("a Responses inbound rides the native Responses wire", () => { const resolved = resolveWireProtocolOverride("deepseek", MODEL, deepseekProvider(), "responses"); expect(resolved.adapter).toBe("openai-responses"); diff --git a/tests/providers/provider-registry-parity.test.ts b/tests/providers/provider-registry-parity.test.ts index cfb6ba27c1f..28825aa98a5 100644 --- a/tests/providers/provider-registry-parity.test.ts +++ b/tests/providers/provider-registry-parity.test.ts @@ -369,13 +369,12 @@ describe("provider registry parity", () => { expect(KEY_LOGIN_PROVIDERS.openrouter.modelContextWindows?.["openai/gpt-5.6-sol"]).toBe(1_050_000); expect(KEY_LOGIN_PROVIDERS.openrouter.modelContextWindows?.["openai/gpt-5.6-terra"]).toBe(1_050_000); expect(KEY_LOGIN_PROVIDERS.openrouter.modelContextWindows?.["openai/gpt-5.6-luna"]).toBe(1_050_000); - // Retired from the first-party API on 2026-09-14; the vendor-hosted rosters keep it. - expect(KEY_LOGIN_PROVIDERS.deepseek.models).not.toContain("deepseek-v4-pro"); + // The official retirement plan was withdrawn; Pro remains a separate text-only model. + expect(KEY_LOGIN_PROVIDERS.deepseek.models).toContain("deepseek-v4-pro"); + expect(KEY_LOGIN_PROVIDERS.deepseek.modelContextWindows?.["deepseek-v4-pro"]).toBe(1_048_576); + expect(KEY_LOGIN_PROVIDERS.deepseek.modelReasoningEfforts?.["deepseek-v4-pro"]).toEqual(["low", "high", "max"]); expect(KEY_LOGIN_PROVIDERS.deepseek.models).toContain("deepseek-flash"); - // #1057: DeepSeek's ladder is low/high/max and the two V4 models resolve it - // differently (api-docs.deepseek.com/guides/thinking_mode, verified 2026-08-06). - // `xhigh` is an alias, so it stays in the wire map but is not advertised. Pro - // does not honor `low` (the vendor maps it to `high`), so Pro must not offer it. + // The current Thinking Mode guide declares low/high/max; aliases stay in the wire map. expect(KEY_LOGIN_PROVIDERS.deepseek.modelReasoningEfforts?.["deepseek-flash"]).toEqual(["low", "high", "max"]); expect(KEY_LOGIN_PROVIDERS.deepseek.modelReasoningEfforts?.["deepseek-v4-flash"]).toEqual(["low", "high", "max"]); expect(KEY_LOGIN_PROVIDERS.deepseek.modelReasoningEffortMap?.["deepseek-flash"]?.low).toBe("low"); @@ -384,14 +383,12 @@ describe("provider registry parity", () => { expect(KEY_LOGIN_PROVIDERS.deepseek.modelReasoningEffortMap?.["deepseek-v4-flash"]?.low).toBe("low"); expect(KEY_LOGIN_PROVIDERS.deepseek.modelReasoningEffortMap?.["deepseek-v4-flash"]?.xhigh).toBe("high"); expect(KEY_LOGIN_PROVIDERS.deepseek.modelReasoningEffortMap?.["deepseek-v4-flash"]?.max).toBe("max"); - // The retired deepseek-v4-pro stays OUT of `models` (line 357) but inside the preserve - // list: it still routes to a thinking-mode model, so a saved config that carries it - // must get reasoning replay rather than a guaranteed 400 (#5421). + // Pro and Flash both retain reasoning on tool-call continuations (#5421). expect(KEY_LOGIN_PROVIDERS.deepseek.preserveReasoningContentModels) .toEqual(["deepseek-flash", "deepseek-v4-flash", "deepseek-v4-pro"]); // #4436: first-party Flash accepts images; unprobed compatibility aliases keep the sidecar. expect(KEY_LOGIN_PROVIDERS.deepseek.noVisionModels).toEqual([ - "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", + "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro", ]); }); From 81db4a8bf047a83cf01ffbdafdc7f89805f9f167 Mon Sep 17 00:00:00 2001 From: norths7ar Date: Fri, 23 Oct 2026 13:34:35 +0800 Subject: [PATCH 2/6] fix(deepseek): restore Pro Responses terminal repair --- src/providers/registry/entries-core.ts | 2 +- tests/providers/deepseek-inbound-wire.test.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/providers/registry/entries-core.ts b/src/providers/registry/entries-core.ts index e116d7d5c38..3653315a374 100644 --- a/src/providers/registry/entries-core.ts +++ b/src/providers/registry/entries-core.ts @@ -1291,7 +1291,7 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [ // devlog/_fin/260807_deepseek_responses_streaming/000_plan.md. // Current official streams normally carry a real terminal; retain a narrow grace // repair for the historical shape that closes after a complete graph without one. - modelResponsesTerminalRepair: { "deepseek-flash": { graceMs: 5_000 }, "deepseek-v4-flash": { graceMs: 5_000 } }, + modelResponsesTerminalRepair: { "deepseek-flash": { graceMs: 5_000 }, "deepseek-v4-flash": { graceMs: 5_000 }, "deepseek-v4-pro": { graceMs: 5_000 } }, // DeepSeek's Responses route emits bare UUID item ids, which leave Codex // clients stuck on an uncommitted turn (#938). Client-facing only — raw // continuation snapshots keep the upstream ids. diff --git a/tests/providers/deepseek-inbound-wire.test.ts b/tests/providers/deepseek-inbound-wire.test.ts index 98fbbccaa27..44612a18025 100644 --- a/tests/providers/deepseek-inbound-wire.test.ts +++ b/tests/providers/deepseek-inbound-wire.test.ts @@ -159,6 +159,7 @@ describe("DeepSeek wire selection is scoped to the inbound protocol", () => { test("the official DeepSeek Responses route opts into terminal repair", () => { const provider = deepseekProvider(); expect(providerModelResponsesTerminalRepair("deepseek", provider, MODEL)).toEqual({ graceMs: 5_000 }); + expect(providerModelResponsesTerminalRepair("deepseek", provider, "deepseek-v4-pro")).toEqual({ graceMs: 5_000 }); expect(providerModelResponsesTerminalRepair("deepseek", provider, "deepseek-chat")).toBeUndefined(); expect(providerModelResponsesTerminalRepair("custom-deepseek", provider, MODEL)).toBeUndefined(); }); @@ -254,7 +255,7 @@ describe("the inbound scope survives the handleResponses replay", () => { expect(request.body.stream).toBe(true); }); - test("HTTP streams a DeepSeek delta before safely repairing a missing terminal", async () => { + test.each([MODEL, "deepseek-v4-pro"])("HTTP streams %s before safely repairing a missing terminal", async (model) => { const source = controlledSse(); const scheduler = new ManualTerminalScheduler(); const requestBodies: Record[] = []; @@ -276,7 +277,7 @@ describe("the inbound scope survives the handleResponses replay", () => { new Request("http://localhost/v1/responses", { method: "POST", headers: { "content-type": "application/json" }, - body: JSON.stringify({ model: MODEL, input: "ping", stream: true }), + body: JSON.stringify({ model, input: "ping", stream: true }), }), config, { model: "", provider: "" }, From 039c7f238f21e1300a459618822aa2c9ff512b97 Mon Sep 17 00:00:00 2001 From: norths7ar Date: Fri, 23 Oct 2026 14:01:24 +0800 Subject: [PATCH 3/6] fix(deepseek): refresh saved native Pro model rosters --- .../src/content/docs/guides/providers.md | 2 + src/providers/stale-model-roster-migration.ts | 7 +++ structure/providers-and-adapters.md | 2 +- .../model-roster-seed-repair.test.ts | 44 +++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index 090e6dcdc91..5d8afbf8304 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -664,6 +664,8 @@ The account list marks Kiro accounts excluded from automatic selection with a re The `deepseek` API-key provider includes `deepseek-flash`, its `deepseek-v4-flash` alias, and `deepseek-v4-pro`. DeepSeek [withdrew the Pro retirement plan](https://api-docs.deepseek.com/updates/) and continues serving it; opencodex retains Pro in the catalog with a 1,048,576-token context window, text-only input, and `low`/`high`/`max` reasoning effort. Responses requests use its native Responses endpoint; Chat Completions requests retain the Chat wire. Flash remains the default. Third-party providers have independent model availability. +On upgrade, startup adds Pro to a saved model list only when it exactly matches the previous built-in DeepSeek seed and still uses the official endpoint and adapter. Customized lists, including empty lists, remain unchanged; live discovery need not be enabled for this repair. + ### DeepSeek Account (DSH) `dsh-account` is an experimental, import-first account provider designed to reuse the authenticated account credentials of an installed DeepSeek Harness (DSH) Desktop application. diff --git a/src/providers/stale-model-roster-migration.ts b/src/providers/stale-model-roster-migration.ts index 0d94641d29f..b007a7aba3b 100644 --- a/src/providers/stale-model-roster-migration.ts +++ b/src/providers/stale-model-roster-migration.ts @@ -75,6 +75,13 @@ const ALIBABA_INTL_TOKEN_PLAN_MODELS_BEFORE_1017 = [ * preview, so live discovery cannot add it; the catalog keeps it only when it is configured. */ export const STALE_MODEL_ROSTERS: readonly StaleModelRoster[] = [ + { + provider: "deepseek", + from: ["deepseek-chat", "deepseek-reasoner", "deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp"], + to: ["deepseek-chat", "deepseek-reasoner", "deepseek-flash", "deepseek-v4-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp"], + contextWindows: { "deepseek-v4-pro": 1_048_576 }, + destinationBaseUrls: ["https://api.deepseek.com"], + }, ...["minimax", "minimax-cn"].map(provider => ({ provider, from: MINIMAX_MODELS_BEFORE_M31, diff --git a/structure/providers-and-adapters.md b/structure/providers-and-adapters.md index 9b82b66beaf..7ca7ac1d641 100644 --- a/structure/providers-and-adapters.md +++ b/structure/providers-and-adapters.md @@ -391,7 +391,7 @@ axis that outranks every source here, so it is where a deliberate text-only over (`ocx provider edit --model --text-only` writes it) and the one declaration a restart cannot take back. -When `/models` omits MiniMax-M3.1-Flash-Preview, `src/providers/stale-model-roster-migration.ts` replaces only a saved roster identical to the old seed and fills existing window and default-effort records during startup; `CALLABLE_CONFIGURED_COMPATIBILITY_MODELS` (`src/codex/catalog/model-hints.ts`) retains it in the live catalog. +When `/models` omits MiniMax-M3.1-Flash-Preview, `src/providers/stale-model-roster-migration.ts` replaces only a saved roster identical to the old seed and fills existing window and default-effort records during startup; `CALLABLE_CONFIGURED_COMPATIBILITY_MODELS` (`src/codex/catalog/model-hints.ts`) retains it in the live catalog. The same startup projection restores DeepSeek V4 Pro only for the exact previous native seed on the official destination and registry adapter, filling missing Pro context in existing maps. Operator lists (including empty lists), custom destinations and explicit context overrides remain unchanged; normal registry enrichment supplies Pro's reasoning metadata. Both MiniMax Coding Plan presets declare `text` and `image` for M3 and M3.1 in `src/providers/registry/model-seeds.ts`; registry enrichment fills missing saved declarations, so id-only live rows enable the Combo image switch when every target supports images. Explicit overrides still win; video is outside Codex's catalog input enum. diff --git a/tests/providers/model-roster-seed-repair.test.ts b/tests/providers/model-roster-seed-repair.test.ts index 4b0530ede10..b97d5611a43 100644 --- a/tests/providers/model-roster-seed-repair.test.ts +++ b/tests/providers/model-roster-seed-repair.test.ts @@ -5,10 +5,54 @@ import { projectStartupConfigRepairs } from "../../src/providers/model-rename-st import { projectStaleModelRosters, STALE_MODEL_ROSTERS } from "../../src/providers/stale-model-roster-migration"; import { ALIBABA_TOKEN_PLAN_MAX_OUTPUT_TOKENS, MINIMAX_MODELS, MINIMAX_MODELS_BEFORE_M31 } from "../../src/providers/registry/model-seeds"; import { PROVIDER_REGISTRY } from "../../src/providers/registry"; +import { enrichProviderFromRegistry } from "../../src/providers/derive"; import type { OcxConfig, OcxProviderConfig } from "../../src/types"; const PREVIEW = "MiniMax-M3.1-Flash-Preview"; +describe("DeepSeek Pro saved roster refresh", () => { + const before = ["deepseek-chat", "deepseek-reasoner", "deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp"]; + const configFor = (models = before, baseUrl = "https://api.deepseek.com") => ({ providers: { deepseek: { + adapter: "openai-chat", baseUrl, models: [...models], liveModels: false, + defaultModel: "deepseek-flash", modelContextWindows: { "deepseek-flash": 1_048_576 }, + } } } as unknown as OcxConfig); + + test("startup restores the exact native seed without live discovery", () => { + const first = projectStartupConfigRepairs(configFor()); + const row = first.config.providers!.deepseek!; + expect(first.changed).toBe(true); + expect(row.models).toContain("deepseek-v4-pro"); + expect(row.modelContextWindows?.["deepseek-v4-pro"]).toBe(1_048_576); + expect(row.liveModels).toBe(false); + expect(row.defaultModel).toBe("deepseek-flash"); + expect(projectStartupConfigRepairs(structuredClone(first.config)).changed).toBe(false); + enrichProviderFromRegistry("deepseek", row); + expect(row.modelReasoningEfforts?.["deepseek-v4-pro"]).toEqual(["low", "high", "max"]); + expect(row.modelSupportsReasoningSummaries?.["deepseek-v4-pro"]).toBe(true); + }); + + test("preserves operator lists, custom destinations and adapters", () => { + for (const models of [[], before.slice(1), [...before].reverse(), [...before, "custom-model"]]) { + const config = configFor(models); + expect(projectStaleModelRosters(config).changed).toBe(false); + expect(config.providers!.deepseek!.models).toEqual(models); + } + expect(projectStaleModelRosters(configFor(before, "https://example.com")).changed).toBe(false); + const config = configFor(); + config.providers!.deepseek!.adapter = "anthropic"; + expect(projectStaleModelRosters(config).changed).toBe(false); + }); + + test("preserves explicit Pro context and absent metadata containers", () => { + const config = configFor(); + config.providers!.deepseek!.modelContextWindows!["deepseek-v4-pro"] = 123; + expect(projectStaleModelRosters(config).config.providers!.deepseek!.modelContextWindows!["deepseek-v4-pro"]).toBe(123); + const absent = configFor(); + delete absent.providers!.deepseek!.modelContextWindows; + expect(projectStaleModelRosters(absent).config.providers!.deepseek!.modelContextWindows).toBeUndefined(); + }); +}); + function minimaxConfig(provider: string, row: Partial): OcxConfig { return { providers: { From d282c0af569b68e3396355ab6a15e24c8c174d55 Mon Sep 17 00:00:00 2001 From: norths7ar Date: Fri, 23 Oct 2026 14:28:44 +0800 Subject: [PATCH 4/6] fix(deepseek): accept official v1 roster repair alias --- src/providers/stale-model-roster-migration.ts | 2 +- tests/providers/model-roster-seed-repair.test.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/providers/stale-model-roster-migration.ts b/src/providers/stale-model-roster-migration.ts index b007a7aba3b..f4ae2decd07 100644 --- a/src/providers/stale-model-roster-migration.ts +++ b/src/providers/stale-model-roster-migration.ts @@ -80,7 +80,7 @@ export const STALE_MODEL_ROSTERS: readonly StaleModelRoster[] = [ from: ["deepseek-chat", "deepseek-reasoner", "deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp"], to: ["deepseek-chat", "deepseek-reasoner", "deepseek-flash", "deepseek-v4-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp"], contextWindows: { "deepseek-v4-pro": 1_048_576 }, - destinationBaseUrls: ["https://api.deepseek.com"], + destinationBaseUrls: ["https://api.deepseek.com", "https://api.deepseek.com/v1"], }, ...["minimax", "minimax-cn"].map(provider => ({ provider, diff --git a/tests/providers/model-roster-seed-repair.test.ts b/tests/providers/model-roster-seed-repair.test.ts index b97d5611a43..cb70b818a75 100644 --- a/tests/providers/model-roster-seed-repair.test.ts +++ b/tests/providers/model-roster-seed-repair.test.ts @@ -17,13 +17,14 @@ describe("DeepSeek Pro saved roster refresh", () => { defaultModel: "deepseek-flash", modelContextWindows: { "deepseek-flash": 1_048_576 }, } } } as unknown as OcxConfig); - test("startup restores the exact native seed without live discovery", () => { - const first = projectStartupConfigRepairs(configFor()); + test.each(["https://api.deepseek.com", "https://api.deepseek.com/v1", "https://api.deepseek.com/v1/"])("startup restores the native seed without live discovery at %s", (baseUrl) => { + const first = projectStartupConfigRepairs(configFor(before, baseUrl)); const row = first.config.providers!.deepseek!; expect(first.changed).toBe(true); expect(row.models).toContain("deepseek-v4-pro"); expect(row.modelContextWindows?.["deepseek-v4-pro"]).toBe(1_048_576); expect(row.liveModels).toBe(false); + expect(row.baseUrl).toBe(baseUrl); expect(row.defaultModel).toBe("deepseek-flash"); expect(projectStartupConfigRepairs(structuredClone(first.config)).changed).toBe(false); enrichProviderFromRegistry("deepseek", row); From 3b7030786e2fdf61526c4828c08b3f628da94ea4 Mon Sep 17 00:00:00 2001 From: norths7ar Date: Fri, 23 Oct 2026 15:00:19 +0800 Subject: [PATCH 5/6] fix(providers): preserve case-folded roster metadata overrides --- src/providers/stale-model-roster-migration.ts | 3 ++- tests/providers/model-roster-seed-repair.test.ts | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/providers/stale-model-roster-migration.ts b/src/providers/stale-model-roster-migration.ts index f4ae2decd07..39247de4078 100644 --- a/src/providers/stale-model-roster-migration.ts +++ b/src/providers/stale-model-roster-migration.ts @@ -29,6 +29,7 @@ import { MINIMAX_MODEL_CONTEXT_WINDOWS, } from "./registry/model-seeds"; import type { OcxConfig } from "../types"; +import { modelRecordValue } from "../reasoning-effort"; export interface StaleModelRoster { /** Registry provider id whose saved rows may carry the old roster. */ @@ -128,7 +129,7 @@ function fillExisting(container: Record | undefined, values: Reado let filled = false; for (const id of ids) { const value = values[id]; - if (value !== undefined && !Object.prototype.hasOwnProperty.call(container, id)) { + if (value !== undefined && modelRecordValue(container, id) === undefined) { container[id] = value; filled = true; } diff --git a/tests/providers/model-roster-seed-repair.test.ts b/tests/providers/model-roster-seed-repair.test.ts index cb70b818a75..ee2cd29a4c7 100644 --- a/tests/providers/model-roster-seed-repair.test.ts +++ b/tests/providers/model-roster-seed-repair.test.ts @@ -44,10 +44,11 @@ describe("DeepSeek Pro saved roster refresh", () => { expect(projectStaleModelRosters(config).changed).toBe(false); }); - test("preserves explicit Pro context and absent metadata containers", () => { + test.each(["deepseek-v4-pro", "DeepSeek-V4-Pro"])("preserves explicit Pro context under %s and absent metadata containers", (key) => { const config = configFor(); - config.providers!.deepseek!.modelContextWindows!["deepseek-v4-pro"] = 123; - expect(projectStaleModelRosters(config).config.providers!.deepseek!.modelContextWindows!["deepseek-v4-pro"]).toBe(123); + config.providers!.deepseek!.modelContextWindows![key] = 123; + const windows = { ...config.providers!.deepseek!.modelContextWindows }; + expect(projectStaleModelRosters(config).config.providers!.deepseek!.modelContextWindows).toEqual(windows); const absent = configFor(); delete absent.providers!.deepseek!.modelContextWindows; expect(projectStaleModelRosters(absent).config.providers!.deepseek!.modelContextWindows).toBeUndefined(); From d9697fcbb437fb801df5c6e9b2792083ba407817 Mon Sep 17 00:00:00 2001 From: JUN <243035832+lidge-jun@users.noreply.github.com> Date: Mon, 26 Oct 2026 16:41:48 +0900 Subject: [PATCH 6/6] test(deepseek): include restored V4 Pro in registry-derived expectations CI shards 1/4 and 2/4 failed because two assertions enumerate the DeepSeek registry's no-vision and reasoning-summary lists, which now include deepseek-v4-pro (text-only, summaries supported). --- tests/codex-integration/codex-catalog.test.ts | 2 +- tests/routing/router.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/codex-integration/codex-catalog.test.ts b/tests/codex-integration/codex-catalog.test.ts index 01018f767e8..62c5350666d 100644 --- a/tests/codex-integration/codex-catalog.test.ts +++ b/tests/codex-integration/codex-catalog.test.ts @@ -6266,7 +6266,7 @@ describe("Codex catalog routed normalization", () => { expect(provider.modelSupportsReasoningSummaries).toEqual({ "deepseek-v4-flash": false, - "deepseek-flash": true, + "deepseek-flash": true, "deepseek-v4-pro": true, }); }); diff --git a/tests/routing/router.test.ts b/tests/routing/router.test.ts index 239461bc01c..ee031439e81 100644 --- a/tests/routing/router.test.ts +++ b/tests/routing/router.test.ts @@ -451,7 +451,7 @@ describe("routeModel registry effort defaults", () => { const route = routeModel(config, "deepseek/deepseek-v4-flash"); expect(route.provider.noVisionModels).toEqual([ - "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", + "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro", ]); });