From c36f8f1e390fa149dcf5dfddf334de054cd364b1 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 4 May 2026 08:12:34 +0100 Subject: [PATCH] fix(deepseek): expose v4 thinking profile in policy surface --- CHANGELOG.md | 1 + extensions/deepseek/index.ts | 17 +--------- .../deepseek/provider-policy-api.test.ts | 31 ++++++++++++++++++- extensions/deepseek/provider-policy-api.ts | 7 +++++ extensions/deepseek/thinking.ts | 19 ++++++++++++ 5 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 extensions/deepseek/thinking.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b21aa62853..7cfc36e98e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai - Web search: scope explicit bundled `web_search` provider runtime loading through manifest ownership, so selecting DuckDuckGo/Gemini/etc. does not import unrelated bundled providers or log their optional dependency failures. Thanks @vincentkoc. - Plugins/discovery: demote the source-only TypeScript runtime check on already-installed `origin: "global"` plugin packages from a config-blocking error to a warning and let the runtime fall through to the TypeScript source via jiti, so a single broken installed package no longer blocks `plugins install` for unrelated plugins; install-time rejection of newly-installed source-only packages is unchanged. Thanks @romneyda. - Providers/OpenAI Codex: stop the OAuth progress spinner before showing the manual redirect paste prompt, so callback timeouts do not spam `Browser callback did not finish` across terminals. +- Providers/DeepSeek: expose DeepSeek V4 `xhigh` and `max` thinking levels through the lightweight provider-policy surface, so Control UI `/think` pickers keep showing the max reasoning options when the runtime plugin registry is not active. Fixes #77139. Thanks @bittoby. - Release/beta smoke: resolve the dispatched Telegram beta E2E run from `gh run list` when `gh workflow run` returns no run URL, so the maintainer helper does not fail immediately after dispatch. Thanks @vincentkoc. - Media/images: keep HEIC/HEIF attachments fail-closed when optional Sharp conversion is unavailable instead of sending originals that still need conversion. Thanks @vincentkoc. - Google Meet: fork the caller's current agent transcript into agent-mode meeting consultant sessions, so Meet replies inherit the context from the tool call that joined the meeting. diff --git a/extensions/deepseek/index.ts b/extensions/deepseek/index.ts index ca54e59b1c0..8f93bb9eadf 100644 --- a/extensions/deepseek/index.ts +++ b/extensions/deepseek/index.ts @@ -1,27 +1,12 @@ -import type { ProviderThinkingProfile } from "openclaw/plugin-sdk/plugin-entry"; import { readConfiguredProviderCatalogEntries } from "openclaw/plugin-sdk/provider-catalog-shared"; import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry"; import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared"; -import { isDeepSeekV4ModelId } from "./models.js"; import { applyDeepSeekConfig, DEEPSEEK_DEFAULT_MODEL_REF } from "./onboard.js"; import { buildDeepSeekProvider } from "./provider-catalog.js"; import { createDeepSeekV4ThinkingWrapper } from "./stream.js"; +import { resolveDeepSeekV4ThinkingProfile } from "./thinking.js"; const PROVIDER_ID = "deepseek"; -const V4_THINKING_LEVEL_IDS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const; - -function buildDeepSeekV4ThinkingLevel(id: (typeof V4_THINKING_LEVEL_IDS)[number]) { - return { id }; -} - -const DEEPSEEK_V4_THINKING_PROFILE = { - levels: V4_THINKING_LEVEL_IDS.map(buildDeepSeekV4ThinkingLevel), - defaultLevel: "high", -} satisfies ProviderThinkingProfile; - -function resolveDeepSeekV4ThinkingProfile(modelId: string): ProviderThinkingProfile | undefined { - return isDeepSeekV4ModelId(modelId) ? DEEPSEEK_V4_THINKING_PROFILE : undefined; -} export default defineSingleProviderPluginEntry({ id: PROVIDER_ID, diff --git a/extensions/deepseek/provider-policy-api.test.ts b/extensions/deepseek/provider-policy-api.test.ts index 6645ebc9f74..ddaca54bee6 100644 --- a/extensions/deepseek/provider-policy-api.test.ts +++ b/extensions/deepseek/provider-policy-api.test.ts @@ -1,8 +1,37 @@ import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types"; import { describe, expect, it } from "vitest"; -import { normalizeConfig } from "./provider-policy-api.js"; +import { normalizeConfig, resolveThinkingProfile } from "./provider-policy-api.js"; describe("deepseek provider-policy-api", () => { + it("advertises max thinking levels for DeepSeek V4 models", () => { + const expectedV4Levels = ["off", "minimal", "low", "medium", "high", "xhigh", "max"]; + + expect( + resolveThinkingProfile({ + provider: "deepseek", + modelId: "deepseek-v4-pro", + })?.levels.map((level) => level.id), + ).toEqual(expectedV4Levels); + expect( + resolveThinkingProfile({ + provider: "deepseek", + modelId: "deepseek-v4-flash", + })?.defaultLevel, + ).toBe("high"); + expect( + resolveThinkingProfile({ + provider: "deepseek", + modelId: "deepseek-chat", + }), + ).toBe(undefined); + expect( + resolveThinkingProfile({ + provider: "openrouter", + modelId: "deepseek-v4-pro", + }), + ).toBe(null); + }); + it("hydrates contextWindow and cost from catalog for known models", () => { const providerConfig: ModelProviderConfig = { baseUrl: "https://api.deepseek.com", diff --git a/extensions/deepseek/provider-policy-api.ts b/extensions/deepseek/provider-policy-api.ts index 994aee24be3..f87c2e06be4 100644 --- a/extensions/deepseek/provider-policy-api.ts +++ b/extensions/deepseek/provider-policy-api.ts @@ -1,6 +1,7 @@ import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared"; import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types"; import { DEEPSEEK_MODEL_CATALOG } from "./models.js"; +import { resolveDeepSeekV4ThinkingProfile } from "./thinking.js"; type ModelDefinitionDraft = Partial & Pick; @@ -95,3 +96,9 @@ export function normalizeConfig(params: { return { ...providerConfig, models: nextModels as ModelDefinitionConfig[] }; } + +export function resolveThinkingProfile(params: { provider: string; modelId: string }) { + return params.provider.trim().toLowerCase() === "deepseek" + ? resolveDeepSeekV4ThinkingProfile(params.modelId) + : null; +} diff --git a/extensions/deepseek/thinking.ts b/extensions/deepseek/thinking.ts new file mode 100644 index 00000000000..66906e6877c --- /dev/null +++ b/extensions/deepseek/thinking.ts @@ -0,0 +1,19 @@ +import type { ProviderThinkingProfile } from "openclaw/plugin-sdk/plugin-entry"; +import { isDeepSeekV4ModelId } from "./models.js"; + +const V4_THINKING_LEVEL_IDS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const; + +function buildDeepSeekV4ThinkingLevel(id: (typeof V4_THINKING_LEVEL_IDS)[number]) { + return { id }; +} + +const DEEPSEEK_V4_THINKING_PROFILE = { + levels: V4_THINKING_LEVEL_IDS.map(buildDeepSeekV4ThinkingLevel), + defaultLevel: "high", +} satisfies ProviderThinkingProfile; + +export function resolveDeepSeekV4ThinkingProfile( + modelId: string, +): ProviderThinkingProfile | undefined { + return isDeepSeekV4ModelId(modelId) ? DEEPSEEK_V4_THINKING_PROFILE : undefined; +}