Skip to content
Merged
6 changes: 6 additions & 0 deletions docs-site/src/content/docs/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,12 @@ 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.

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.
Expand Down
12 changes: 4 additions & 8 deletions src/codex/catalog/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
20 changes: 9 additions & 11 deletions src/providers/registry/entries-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1256,17 +1256,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
Expand All @@ -1281,6 +1282,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"] },
Comment thread
norths7ar marked this conversation as resolved.
Resolved
// 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"] },
Expand All @@ -1298,7 +1300,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.
Expand Down Expand Up @@ -1337,16 +1339,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" },
Expand Down
15 changes: 5 additions & 10 deletions src/providers/registry/model-seeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"];
Comment thread
norths7ar marked this conversation as resolved.
Resolved
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
Expand Down
10 changes: 9 additions & 1 deletion src/providers/stale-model-roster-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,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. */
Expand Down Expand Up @@ -92,6 +93,13 @@ export const STALE_MODEL_ROSTERS: readonly StaleModelRoster[] = [
// (preserveCustomDestination); only api.meta.ai publishes the roster `to` describes.
destinationBaseUrls: ["https://api.meta.ai/v1"],
},
{
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 },
Comment thread
norths7ar marked this conversation as resolved.
Resolved
destinationBaseUrls: ["https://api.deepseek.com", "https://api.deepseek.com/v1"],
},
...["minimax", "minimax-cn"].map(provider => ({
provider,
from: MINIMAX_MODELS_BEFORE_M31,
Expand Down Expand Up @@ -148,7 +156,7 @@ function fillExisting<T>(container: Record<string, T> | 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;
}
Expand Down
2 changes: 2 additions & 0 deletions structure/catalog-vendor-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion structure/providers-and-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ axis that outranks every source here, so it is where a deliberate text-only over
(`ocx provider edit <provider> --model <id> --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.

Expand Down
3 changes: 2 additions & 1 deletion tests/codex-integration/codex-catalog-exclusions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion tests/codex-integration/codex-catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
});

Expand Down
11 changes: 9 additions & 2 deletions tests/providers/deepseek-inbound-wire.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -153,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();
});
Expand Down Expand Up @@ -248,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<string, unknown>[] = [];
Expand All @@ -270,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: "" },
Expand Down
Loading
Loading