Fix DeepSeek reasoning error.

This commit is contained in:
Dominic Cooney
2026-05-18 08:23:22 -07:00
committed by Max Paulus 🥪
parent 693bd3f7da
commit 82badf29ae
3 changed files with 102 additions and 3 deletions
@@ -564,8 +564,9 @@ Uncomment each and confirm the call site compiles against the contract. If anyth
- Read `src/core/api/providers/deepseek.ts`. Confirm `getModel()` reads `modelId`/`modelInfo` from passed-in config, not a static map.
- If a static-map lookup exists, replace with read from passed-in config. Add test: `getModel().info` equals the passed-in info.
- Verify `cline-session-factory.ts` resolves provider config via `ProviderConfigStore` and snapshots at task start.
- Audit every auxiliary field the legacy provider panel could set, not just `modelId`/`modelInfo`. For DeepSeek this includes reasoning/thinking state (`enabled`, `effort`, and budget tokens). The migrated runtime must either carry each auxiliary field through the SDK provider settings/session config coherently, or explicitly document that field as out of scope for the picker migration. In particular, disabled thinking must clear any orphaned reasoning effort before inference; otherwise provider switches can tear configuration across providers.
**Exit:** Runtime test passes. `cline-session-factory` does not import `deepSeekModels`.
**Exit:** Runtime test passes. `cline-session-factory` does not import `deepSeekModels`. Auxiliary-field tests prove DeepSeek does not send contradictory thinking/reasoning options after switching from a provider with reasoning enabled.
### Step 6.3 — End-to-end live test
+31
View File
@@ -8,6 +8,7 @@ import {
buildStartSessionInput,
createHistoryItemFromSession,
getHistoryItemById,
normalizeProviderReasoningSettings,
updateHistoryItem,
} from "./cline-session-factory"
@@ -123,6 +124,36 @@ describe("buildResumeSessionInput", () => {
})
})
// ---------------------------------------------------------------------------
// normalizeProviderReasoningSettings
// ---------------------------------------------------------------------------
describe("normalizeProviderReasoningSettings", () => {
it("does not emit reasoningEffort when thinking is disabled", () => {
const result = normalizeProviderReasoningSettings({ enabled: false, effort: "medium" })
expect(result).toEqual({ thinking: false })
})
it("treats effort none as disabled thinking", () => {
const result = normalizeProviderReasoningSettings({ effort: "none" })
expect(result).toEqual({ thinking: false })
})
it("passes enabled reasoning with a concrete effort", () => {
const result = normalizeProviderReasoningSettings({ enabled: true, effort: "high" })
expect(result).toEqual({ thinking: true, reasoningEffort: "high" })
})
it("leaves explicit effort-only settings enabled by SDK/provider defaults", () => {
const result = normalizeProviderReasoningSettings({ effort: "medium" })
expect(result).toEqual({ reasoningEffort: "medium" })
})
})
// ---------------------------------------------------------------------------
// createHistoryItemFromSession
// ---------------------------------------------------------------------------
+69 -2
View File
@@ -8,7 +8,12 @@
//
// The factory does NOT handle UI concerns — that's the SdkController's job.
import { type ClineCoreStartInput, type CoreSessionConfig, type StartSessionResult } from "@cline/core"
import {
type ClineCoreStartInput,
type CoreSessionConfig,
type ProviderSettings,
type StartSessionResult,
} from "@cline/core"
import { buildClineSystemPrompt } from "@cline/shared"
import type { ApiConfiguration } from "@shared/api"
import type { HistoryItem } from "@shared/HistoryItem"
@@ -106,6 +111,67 @@ function resolveWorkspaceName(workspacePath: string): string {
return name || "workspace"
}
type ReasoningEffort = NonNullable<CoreSessionConfig["reasoningEffort"]>
type ProviderReasoningSettings = NonNullable<ProviderSettings["reasoning"]>
type SessionReasoningConfig = Pick<CoreSessionConfig, "thinking" | "reasoningEffort">
function isReasoningEffort(value: unknown): value is ReasoningEffort {
return value === "low" || value === "medium" || value === "high" || value === "xhigh"
}
function hasStaleDisabledReasoningFields(reasoning: ProviderReasoningSettings | undefined): boolean {
return reasoning?.enabled === false && (reasoning.effort !== undefined || reasoning.budgetTokens !== undefined)
}
/**
* Convert SDK provider-level reasoning settings into the SDK session fields that
* are actually forwarded as model options. Keep `thinking` and
* `reasoningEffort` coherent: a disabled/none state must never carry an effort.
*/
export function normalizeProviderReasoningSettings(reasoning: ProviderReasoningSettings | undefined): SessionReasoningConfig {
if (!reasoning) {
return {}
}
if (reasoning.enabled === false || reasoning.effort === "none") {
return { thinking: false }
}
if (reasoning.enabled === true) {
return {
thinking: true,
...(isReasoningEffort(reasoning.effort) ? { reasoningEffort: reasoning.effort } : {}),
}
}
return isReasoningEffort(reasoning.effort) ? { reasoningEffort: reasoning.effort } : {}
}
function resolveProviderReasoningConfig(providerId: string): SessionReasoningConfig {
try {
const manager = getProviderSettingsManager(resolveDataDir())
const settings = manager.getProviderSettings(providerId)
if (!settings) {
return {}
}
if (hasStaleDisabledReasoningFields(settings.reasoning)) {
const sanitizedSettings: ProviderSettings = {
...settings,
reasoning: { enabled: false },
}
manager.saveProviderSettings(sanitizedSettings, { setLastUsed: false })
Logger.warn(`[SessionFactory] Cleared stale disabled reasoning fields for provider=${providerId}`)
return normalizeProviderReasoningSettings(sanitizedSettings.reasoning)
}
return normalizeProviderReasoningSettings(settings.reasoning)
} catch (error) {
Logger.warn("[SessionFactory] Provider reasoning resolution failed:", error)
return {}
}
}
// ---------------------------------------------------------------------------
// Provider → API key field mapping
// ---------------------------------------------------------------------------
@@ -365,6 +431,7 @@ export async function buildSessionConfig(input: SessionConfigInput): Promise<Cor
providerId = providerId ?? "cline"
modelId = modelId ?? "openai/gpt-5.4"
apiKey = apiKey ?? ""
const reasoningConfig = resolveProviderReasoningConfig(providerId)
// Build the system prompt using the shared prompt builder. Core still
// expects callers to provide a concrete systemPrompt, but the prompt builder
@@ -423,7 +490,7 @@ export async function buildSessionConfig(input: SessionConfigInput): Promise<Cor
enableAgentTeams: false,
disableMcpSettingsTools: true,
mode: mode === "plan" ? "plan" : "act",
thinking: false,
...reasoningConfig,
maxIterations: undefined,
logger: sdkLogger,
extensionContext: {