diff --git a/src/agent/model-family-policy.test.ts b/src/agent/model-family-policy.test.ts index 19c18c38e..43b768fae 100644 --- a/src/agent/model-family-policy.test.ts +++ b/src/agent/model-family-policy.test.ts @@ -411,7 +411,7 @@ describe("resolveModelFamilyPolicy", () => { test("orchestrators get the d1 primary residual, no leaf role body", () => { const p = v4({ orchestrator: true }); - expect(p.family).toBe("deepseek-v4"); + expect(p.family).toBe("deepseek"); expect(p.promptResidual).toBeDefined(); expect(p.promptResidual).toContain("DeepSeek V4 Flash"); expect(p.leafRoleBody).toBeUndefined(); @@ -419,7 +419,7 @@ describe("resolveModelFamilyPolicy", () => { test("coder leaves get the tuned slim coder body, no residual", () => { const p = v4({ orchestrator: false, directorId: "coder" }); - expect(p.family).toBe("deepseek-v4"); + expect(p.family).toBe("deepseek"); expect(p.leafRoleBody).toBeDefined(); expect(p.leafRoleBody).toContain("Coder"); expect(p.promptResidual).toBeUndefined(); @@ -427,7 +427,7 @@ describe("resolveModelFamilyPolicy", () => { test("reviewer leaves get the tuned slim reviewer body", () => { const p = v4({ orchestrator: false, directorId: "reviewer" }); - expect(p.family).toBe("deepseek-v4"); + expect(p.family).toBe("deepseek"); expect(p.leafRoleBody).toBeDefined(); expect(p.leafRoleBody).toContain("Reviewer"); expect(p.promptResidual).toBeUndefined(); @@ -435,7 +435,7 @@ describe("resolveModelFamilyPolicy", () => { test("explorer leaves get the g2 leaf residual only", () => { const p = v4({ orchestrator: false, directorId: "explorer" }); - expect(p.family).toBe("deepseek-v4"); + expect(p.family).toBe("deepseek"); expect(p.promptResidual).toBeDefined(); expect(p.promptResidual).toContain("DeepSeek V4 Flash"); expect(p.leafRoleBody).toBeUndefined(); @@ -443,7 +443,7 @@ describe("resolveModelFamilyPolicy", () => { test("a leaf without a tuned director id gets the default policy, no body/residual", () => { const p = v4({ orchestrator: false }); - expect(p.family).toBe("deepseek-v4"); + expect(p.family).toBe("deepseek"); expect(p.leafRoleBody).toBeUndefined(); expect(p.promptResidual).toBeUndefined(); }); diff --git a/src/agent/model-family-policy.ts b/src/agent/model-family-policy.ts index 5b7cf1606..c26cefdbd 100644 --- a/src/agent/model-family-policy.ts +++ b/src/agent/model-family-policy.ts @@ -299,7 +299,7 @@ export function resolveModelFamilyPolicy(input: { // Generic gpt prompts are byte-identical — only astra carries the // residual. return { family, ...ASTRA_POLICY }; - case "deepseek-v4": { + case "deepseek": { // Winning d1+slim config: d1 primary residual on orchestrators, g2 leaf // residual on explorer leaves, tuned slim coder/reviewer bodies. const directorId = input.directorId; diff --git a/src/config/index.ts b/src/config/index.ts index b323cf977..fb940e42d 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -60,7 +60,7 @@ import { } from "../provider/grok-responses.js"; import { BIFROST_PROVIDER } from "../provider/bifrost-adapter.js"; import { DEEPSEEK_V4_PROVIDER } from "../provider/deepseek-v4-adapter.js"; -import { isDeepSeekV4Model } from "../provider/deepseek-v4-effort.js"; +import { isDeepSeekModel } from "../provider/deepseek-v4-effort.js"; import { isOllamaProviderId, ollamaOpenAIBaseURL } from "../provider/ollama.js"; import { selectableGoModelIds } from "../provider/model-catalogs.js"; import { @@ -290,7 +290,7 @@ export function buildOpenAISource(fields: { registerSourceSecret(fields.id, fields.apiKey); return { id: fields.id, - provider: isDeepSeekV4Model(fields.model) + provider: isDeepSeekModel(fields.model) ? DEEPSEEK_V4_PROVIDER : "openai-compatible", baseURL: isOllamaProviderId(fields.id) diff --git a/src/config/inference-sources.test.ts b/src/config/inference-sources.test.ts index eca705a60..5035abcd9 100644 --- a/src/config/inference-sources.test.ts +++ b/src/config/inference-sources.test.ts @@ -814,12 +814,15 @@ describe("deepseek-v4 provider stamping (PR A)", () => { ); } - test("a deepseek-v4-* model stamps provider 'deepseek-v4'", () => { + test("a deepseek-* model stamps provider 'deepseek-v4' (family routing)", () => { const source = v4SourceFor("deepseek-v4-pro"); expect(source?.provider).toBe("deepseek-v4"); + + const preV4 = v4SourceFor("deepseek-v3"); + expect(preV4?.provider).toBe("deepseek-v4"); }); - test("non-V4 custom models stay provider 'openai-compatible'", () => { + test("non-deepseek custom models stay provider 'openai-compatible'", () => { const source = v4SourceFor("gpt-5"); expect(source?.provider).toBe("openai-compatible"); }); diff --git a/src/provider/deepseek-v4-adapter.test.ts b/src/provider/deepseek-v4-adapter.test.ts index 0681e1236..6b7da44aa 100644 --- a/src/provider/deepseek-v4-adapter.test.ts +++ b/src/provider/deepseek-v4-adapter.test.ts @@ -47,12 +47,12 @@ describe("deepseek-v4 adapter effort wiring", () => { expect("reasoning_effort" in body).toBe(false); }); - test("off-ladder effort is left untouched (no high→xhigh coercion)", () => { - const body = bodyFor({ - providerOptions: { reasoning_effort: "high" }, - } as InferenceOptions); - expect(body["reasoning_effort"]).toBe("high"); - expect("chat_template_kwargs" in body).toBe(false); + test("off-ladder effort is rejected (no pass-through to the wire)", () => { + expect(() => + bodyFor({ + providerOptions: { reasoning_effort: "high" }, + } as InferenceOptions), + ).toThrow(/does not support reasoning effort "high"/); }); test("unset effort does not inject empty chat_template_kwargs", () => { @@ -223,4 +223,29 @@ describe("deepseek-v4 adapter byte-identical to base for non-V4", () => { const body = JSON.parse(built.body) as Record; expect(body).toEqual(baseBody); }); + + test("pre-V4 deepseek gets no V4 quirks (family route, V4-variant gate)", () => { + const baseAdapter = createOpenAICompatibleAdapter({ + ...source, + model: "deepseek-v3", + } as typeof source); + const baseBuilt = baseAdapter.buildRequest(messages, "deepseek-v3", { + providerOptions: { reasoning_effort: "none" }, + } as InferenceOptions); + + const adapter = createDeepSeekV4Adapter({ + ...source, + model: "deepseek-v3", + } as typeof source); + const built = adapter.buildRequest(messages, "deepseek-v3", { + providerOptions: { reasoning_effort: "none" }, + } as InferenceOptions); + + const baseBody = JSON.parse(baseBuilt.body) as Record; + const body = JSON.parse(built.body) as Record; + expect(body).toEqual(baseBody); + expect("top_p" in body).toBe(false); + expect("temperature" in body).toBe(false); + expect("stream_options" in body).toBe(false); + }); }); diff --git a/src/provider/deepseek-v4-adapter.ts b/src/provider/deepseek-v4-adapter.ts index 24d53b70d..36a8b25c6 100644 --- a/src/provider/deepseek-v4-adapter.ts +++ b/src/provider/deepseek-v4-adapter.ts @@ -2,6 +2,7 @@ import { type ProviderAdapter } from "@intx/inference"; import { createOpenAICompatibleAdapter } from "./openai-compatible-adapter.js"; import { DEEPSEEK_V4_MODEL_CARD, + isDeepSeekModel, isDeepSeekV4Model, mapV4Effort, V4_STREAM_OPTIONS, @@ -13,7 +14,9 @@ type AdapterSource = Parameters[0]; // Wraps the patched openai-compatible adapter so V4 requests inherit the // providerOptions merge, NIM null-delta patch, and Accept header; this layer -// only applies the V4 effort/sampling/stream quirks to the built body. +// routes the whole DeepSeek family through this family adapter and applies the +// V4 effort/sampling/stream quirks to the built body only for the V4 variant +// (pre-V4 deepseek stays on the generic openai-compatible body, byte-identical). export function createDeepSeekV4Adapter( source: AdapterSource, ): ProviderAdapter { @@ -25,12 +28,12 @@ export function createDeepSeekV4Adapter( options, ) => { const req = base.buildRequest(messages, model, options); - if (!isDeepSeekV4Model(model)) return req; + if (!isDeepSeekModel(model) || !isDeepSeekV4Model(model)) return req; const body = JSON.parse(req.body) as Record; // V4 effort ladder (none/xhigh/max): none drops reasoning_effort and - // turns thinking off; xhigh/max go out raw with thinking on; off-ladder - // or absent efforts are left untouched. + // turns thinking off; xhigh/max go out raw with thinking on. Off-ladder + // efforts throw inside mapV4Effort (rejected, never passed to the wire). if ("reasoning_effort" in body) { const effort = body["reasoning_effort"] as Parameters< typeof mapV4Effort @@ -39,7 +42,7 @@ export function createDeepSeekV4Adapter( if (wire === null) { delete body["reasoning_effort"]; mergeThinking(body, false); - } else if (wire !== undefined) { + } else { body["reasoning_effort"] = wire; mergeThinking(body, true); } diff --git a/src/provider/deepseek-v4-effort.test.ts b/src/provider/deepseek-v4-effort.test.ts index ec52b9d14..e20549155 100644 --- a/src/provider/deepseek-v4-effort.test.ts +++ b/src/provider/deepseek-v4-effort.test.ts @@ -2,12 +2,43 @@ import { describe, test, expect } from "bun:test"; import { DEEPSEEK_V4_EFFORTS, DEEPSEEK_V4_MODEL_CARD, + DEEPSEEK_V4_ROLE_EFFORT, + isDeepSeekModel, isDeepSeekV4Model, mapV4Effort, V4_STREAM_OPTIONS, v4Thinking, } from "./deepseek-v4-effort.js"; +describe("isDeepSeekModel", () => { + test.each([ + "deepseek-v4", + "deepseek-v4-pro", + "deepseek-v4-flash", + "deepseek-v4.1-flash", + "deepseek-v4-flash-vision-exp", + "deepseek-ai/DeepSeek-V4-Flash-0731", + "deepseek-ai/deepseek-v4-flash", + "deepseek-v3", + "deepseek-r1", + "deepseek-coder", + "deepseek-chat", + "deepseek-ai/DeepSeek-Chat", + ])("true for deepseek family id %s", (model) => { + expect(isDeepSeekModel(model)).toBe(true); + }); + test.each([ + "gpt-5", + "kimi-k2", + "claude-sonnet-4.5", + "glm-5.3", + "grok-4.6", + "some-proxy-deepseek-pro", + ])("false for non-deepseek id %s", (model) => { + expect(isDeepSeekModel(model)).toBe(false); + }); +}); + describe("isDeepSeekV4Model", () => { test.each([ "deepseek-v4", @@ -25,6 +56,7 @@ describe("isDeepSeekV4Model", () => { "deepseek-r1", "deepseek-coder", "deepseek-chat", + "deepseek-ai/DeepSeek-Chat", "gpt-5", "kimi-k2", "claude-sonnet-4.5", @@ -42,6 +74,28 @@ describe("DEEPSEEK_V4_EFFORTS", () => { }); }); +describe("DEEPSEEK_V4_ROLE_EFFORT", () => { + test("coder defaults to max", () => { + expect(DEEPSEEK_V4_ROLE_EFFORT.coder).toBe("max"); + }); + test("dispatch/planner/reviewer are max and other leaves xhigh", () => { + expect(DEEPSEEK_V4_ROLE_EFFORT.dispatch).toBe("max"); + expect(DEEPSEEK_V4_ROLE_EFFORT.planner).toBe("max"); + expect(DEEPSEEK_V4_ROLE_EFFORT.reviewer).toBe("max"); + for (const role of [ + "explorer", + "artist", + "qa-lead", + "prober", + "designer", + "shakespeare", + "warden", + ] as const) { + expect(DEEPSEEK_V4_ROLE_EFFORT[role]).toBe("xhigh"); + } + }); +}); + describe("mapV4Effort", () => { test.each([ ["xhigh", "xhigh"], @@ -53,9 +107,11 @@ describe("mapV4Effort", () => { expect(mapV4Effort("none")).toBeNull(); }); test.each(["low", "medium", "high", "ultra", "minimal"] as const)( - "leaves off-ladder %s untouched (undefined)", + "rejects off-ladder %s with a clear error (no pass-through)", (effort) => { - expect(mapV4Effort(effort)).toBeUndefined(); + expect(() => mapV4Effort(effort)).toThrow( + `DeepSeek V4 does not support reasoning effort "${effort}"`, + ); }, ); }); diff --git a/src/provider/deepseek-v4-effort.ts b/src/provider/deepseek-v4-effort.ts index 14bac7ca3..adf790ef1 100644 --- a/src/provider/deepseek-v4-effort.ts +++ b/src/provider/deepseek-v4-effort.ts @@ -2,15 +2,15 @@ import type { ReasoningEffort } from "../agent/profile-types.js"; import type { DirectorId } from "../agent/directors/types.js"; /** - * Per-role V4 effort defaults. coder is the cheapest spawn: 'low' clamps to - * the V4 'none' wire rung (thinking off), per the operator's "simplest - * implementer = none" intent. Every other role takes its rung from the map. + * Per-role V4 effort defaults. dispatch/planner/reviewer dispatch and judge — + * max. coder defaults to max (operator-confirmed). Every other leaf reasons at + * xhigh. */ export const DEEPSEEK_V4_ROLE_EFFORT: Record = { dispatch: "max", planner: "max", reviewer: "max", - coder: "low", + coder: "max", explorer: "xhigh", artist: "xhigh", "qa-lead": "xhigh", @@ -20,13 +20,23 @@ export const DEEPSEEK_V4_ROLE_EFFORT: Record = { warden: "xhigh", }; -// DeepSeek V4 effort translator shared by the adapter and the effort picker. -// V4 ships under several ids across two catalogs — deepseek-v4-pro/-flash/ -// -flash-vision-exp — and under a vendor-qualified host id like -// deepseek-ai/DeepSeek-V4-Flash-0731. Match the `deepseek-v4` segment at the -// start or after a `/`, so both bare and qualified ids resolve while pre-V4 -// ids (deepseek-coder, deepseek-chat) do not. This mirrors the grok/kimi/astra -// leaf predicates, which also accept an org-qualified segment. +// DeepSeek family predicate shared by the adapter router and the family policy. +// The family ships under several ids across two catalogs — deepseek-v4-pro/ +// -flash/-flash-vision-exp and deepseek-chat/deepseek-coder/deepseek-v3 — and +// under a vendor-qualified host id like deepseek-ai/DeepSeek-V4-Flash-0731. +// Match the `deepseek` segment at the start or after a `/`, so both bare and +// qualified ids resolve across the whole family (including future versions). +// This mirrors the grok/kimi/astra leaf predicates, which also accept an +// org-qualified segment. +export function isDeepSeekModel(model: string): boolean { + return /(^|\/)deepseek/i.test(model.trim()); +} + +// V4-variant detection: distinguishes the V4 model line from pre-V4 DeepSeek +// (deepseek-chat, deepseek-coder, deepseek-v3). Only where the wire semantics +// genuinely differ between V4 and pre-V4 deepseek — the V4 adapter's model-card +// and effort quirks, the reasoning_content keep-vs-strip behavior, and the +// none/xhigh/max effort ladder — do callers gate on this variant predicate. export function isDeepSeekV4Model(model: string): boolean { return /(^|\/)deepseek-v4/i.test(model.trim()); } @@ -43,15 +53,17 @@ export const DEEPSEEK_V4_EFFORTS: readonly ReasoningEffort[] = [ /** * Translate a V4 effort to its raw wire value: `xhigh`/`max` pass through * verbatim, `none` maps to null (the caller drops `reasoning_effort`, since - * `reasoning_effort:"none"` is illegal on the wire). Off-ladder values return - * undefined — the caller leaves `reasoning_effort` untouched rather than - * coercing it onto a V4 rung. + * `reasoning_effort:"none"` is illegal on the wire). The V4 ladder is strictly + * none/xhigh/max: any off-ladder effort arriving on a manual/exec/config path + * is rejected with a clear error rather than coerced or passed through, so no + * off-ladder value can reach the wire for a V4 model. */ -export function mapV4Effort( - effort: ReasoningEffort, -): "xhigh" | "max" | null | undefined { +export function mapV4Effort(effort: ReasoningEffort): "xhigh" | "max" | null { if (effort === "none") return null; - return effort === "xhigh" || effort === "max" ? effort : undefined; + if (effort === "xhigh" || effort === "max") return effort; + throw new Error( + `DeepSeek V4 does not support reasoning effort "${effort}" (supported: none, xhigh, max).`, + ); } /** diff --git a/src/provider/openai-compatible-adapter.ts b/src/provider/openai-compatible-adapter.ts index 865bed071..1d69550e0 100644 --- a/src/provider/openai-compatible-adapter.ts +++ b/src/provider/openai-compatible-adapter.ts @@ -1,7 +1,7 @@ import { type BuiltRequest, type ProviderAdapter } from "@intx/inference"; import { createOpenAIAdapter } from "@intx/inference/providers"; import { normalizeNullDeltaFields } from "./null-delta-fields.js"; -import { isDeepSeekV4Model } from "./deepseek-v4-effort.js"; +import { isDeepSeekModel, isDeepSeekV4Model } from "./deepseek-v4-effort.js"; // The stock OpenAI adapter builds the request body from a fixed set of fields // (max_tokens, temperature, tools, messages, response_format) and ignores @@ -51,7 +51,7 @@ export function createOpenAICompatibleAdapter( // keeps each turn's reasoning on replay (handled by its own adapter), so only // pre-V4 DeepSeek strips it here. const m = model.toLowerCase().trim(); - const isDeepSeek = m.includes("deepseek"); + const isDeepSeek = isDeepSeekModel(m); const stripReasoning = isDeepSeek && !isDeepSeekV4Model(m); needsDeepSeekPatch = isDeepSeek; if (!hasProviderOptions && !stripReasoning) return ensureAccept(built); diff --git a/src/subagent/agent-fleet-effort.test.ts b/src/subagent/agent-fleet-effort.test.ts index f0584df33..f6e140260 100644 --- a/src/subagent/agent-fleet-effort.test.ts +++ b/src/subagent/agent-fleet-effort.test.ts @@ -390,15 +390,14 @@ describe("deepseek-v4 per-role effort policy (CL-10294)", () => { return worker.provider.reasoningEffort as ReasoningEffort; } - test("planner/reviewer max, coder cheapest, other leaves xhigh", async () => { - // V4's native ladder is ["none","xhigh","max"]. coder's low clamps to the - // cheapest wire rung (none, thinking off) per the operator's "simplest - // implementer = none" intent; planner/reviewer resolve max. dispatch is the - // primary session (not a spawned worker) and already defaults to max for + test("planner/reviewer/coder max, other leaves xhigh", async () => { + // V4's native ladder is ["none","xhigh","max"]. coder defaults to max + // (operator-confirmed, PR #1377); planner/reviewer resolve max. dispatch is + // the primary session (not a spawned worker) and already defaults to max for // V4 via defaultEffortForModel. expect(await captureEffort("planner")).toBe("max"); expect(await captureEffort("reviewer")).toBe("max"); - expect(await captureEffort("coder")).toBe("none"); + expect(await captureEffort("coder")).toBe("max"); expect(await captureEffort("explorer")).toBe("xhigh"); expect(await captureEffort("artist")).toBe("xhigh"); expect(await captureEffort("qa-lead")).toBe("xhigh"); diff --git a/src/subagent/agent-fleet.ts b/src/subagent/agent-fleet.ts index 3fe5f8cfb..199b6cbbc 100644 --- a/src/subagent/agent-fleet.ts +++ b/src/subagent/agent-fleet.ts @@ -68,7 +68,7 @@ import { } from "../provider/reasoning-effort.js"; import { DEEPSEEK_V4_ROLE_EFFORT, - isDeepSeekV4Model, + isDeepSeekModel, } from "../provider/deepseek-v4-effort.js"; import type { AgentProfile, CapabilityFilter } from "../agent/profiles.js"; import { @@ -1192,7 +1192,7 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool { // role default still win over this table when supported. const roleEffortPin = resolved.effortPin ?? - (isDeepSeekV4Model(provider.model) && + (isDeepSeekModel(provider.model) && resolved.directorId in DEEPSEEK_V4_ROLE_EFFORT ? DEEPSEEK_V4_ROLE_EFFORT[ resolved.directorId as keyof typeof DEEPSEEK_V4_ROLE_EFFORT diff --git a/src/subagent/provider-family.test.ts b/src/subagent/provider-family.test.ts index 4a0cffc30..8392e06db 100644 --- a/src/subagent/provider-family.test.ts +++ b/src/subagent/provider-family.test.ts @@ -3,7 +3,7 @@ import { detectModelFamily, isAstraLeafProvider, isClaudeLeafProvider, - isDeepSeekV4LeafProvider, + isDeepSeekLeafProvider, isGptProvider, isKimiLeafProvider, isXaiGrokLeafProvider, @@ -77,26 +77,24 @@ describe("detectModelFamily", () => { [{ providerName: "anthropic", model: "claude-sonnet-4" }, "claude"], [{ providerName: "openai-compat", model: "claude-opus-4-6" }, "claude"], [{ providerName: "openai", model: "gpt-5.6" }, "gpt"], + [{ providerName: "openai-compat", model: "deepseek-v4-pro" }, "deepseek"], + [{ providerName: "opencode-go", model: "deepseek-v4-flash" }, "deepseek"], [ - { providerName: "openai-compat", model: "deepseek-v4-pro" }, - "deepseek-v4", - ], - [ - { providerName: "opencode-go", model: "deepseek-v4-flash" }, - "deepseek-v4", + { providerName: "openai-compat", model: "deepseek-v4-flash-vision-exp" }, + "deepseek", ], [ - { providerName: "openai-compat", model: "deepseek-v4-flash-vision-exp" }, - "deepseek-v4", + { providerName: "openai-compat", model: "deepseek-ai/deepseek-v3" }, + "deepseek", ], - [{ providerName: "openai-compat", model: "deepseek-coder" }, "default"], - [{ providerName: "unknown-provider", model: "unknown-model" }, "default"], + [{ providerName: "openai-compat", model: "deepseek-coder" }, "deepseek"], + [{ providerName: "openai-compat", model: "unknown-model" }, "default"], ])("resolves %j -> %s", (row, expected) => { expect(detectModelFamily(row)).toBe(expected); }); }); -describe("isDeepSeekV4LeafProvider", () => { +describe("isDeepSeekLeafProvider", () => { test.each<[ProviderRow, boolean]>([ [{ providerName: "openai-compat", model: "deepseek-v4-pro" }, true], [{ providerName: "openai-compat", model: "deepseek-v4-flash" }, true], @@ -105,12 +103,16 @@ describe("isDeepSeekV4LeafProvider", () => { true, ], [{ providerName: "openai-compat", model: "DEEPSEEK-V4-PRO" }, true], + [{ providerName: "openai-compat", model: "deepseek-v3" }, true], + [ + { providerName: "openai-compat", model: "deepseek-ai/deepseek-chat" }, + true, + ], [{ providerName: "xai/default", model: "grok-4.6" }, false], [{ providerName: "openai", model: "gpt-5.6" }, false], - [{ providerName: "openai-compat", model: "deepseek-v3" }, false], [{ providerName: "openai-compat" }, false], ])("matches %j -> %s", (row, expected) => { - expect(isDeepSeekV4LeafProvider(row)).toBe(expected); + expect(isDeepSeekLeafProvider(row)).toBe(expected); }); }); diff --git a/src/subagent/provider-family.ts b/src/subagent/provider-family.ts index c1e6030a7..4702d363b 100644 --- a/src/subagent/provider-family.ts +++ b/src/subagent/provider-family.ts @@ -1,7 +1,7 @@ import { GROK_RESPONSES_PROVIDER } from "../provider/grok-responses.js"; import { isXaiProviderName } from "../config/xai-providers.js"; import { isCodexProviderName } from "../config/codex-providers.js"; -import { isDeepSeekV4Model } from "../provider/deepseek-v4-effort.js"; +import { isDeepSeekModel } from "../provider/deepseek-v4-effort.js"; /** * True when the leaf inference path is xAI / Grok family. @@ -65,15 +65,15 @@ export function isAstraLeafProvider(input: { } /** - * True when the model is the DeepSeek V4 family. Single-sourced from the - * shared V4 effort module (isDeepSeekV4Model, /^deepseek-v4/i) so the adapter - * family, effort routing, and model-family key stay consistent. + * True when the model is the DeepSeek family. Single-sourced from the shared + * effort module (isDeepSeekModel, /(^|\/)deepseek/i) so the adapter family, + * effort routing, and model-family key stay consistent. */ -export function isDeepSeekV4LeafProvider(input: { +export function isDeepSeekLeafProvider(input: { providerName: string; model?: string; }): boolean { - return input.model !== undefined && isDeepSeekV4Model(input.model); + return input.model !== undefined && isDeepSeekModel(input.model); } /** @@ -104,7 +104,7 @@ export type ModelFamily = | "claude" | "gpt" | "astra" - | "deepseek-v4" + | "deepseek" | "default"; /** @@ -122,7 +122,7 @@ export function detectModelFamily(input: { if (isMuseSparkLeafProvider(input)) return "muse"; if (isClaudeLeafProvider(input)) return "claude"; if (isAstraLeafProvider(input)) return "astra"; - if (isDeepSeekV4LeafProvider(input)) return "deepseek-v4"; + if (isDeepSeekLeafProvider(input)) return "deepseek"; if (isGptProvider(input)) return "gpt"; return "default"; }