Add DeepSeek V4 models

This commit is contained in:
avaritiachaos 2026-04-29 12:07:50 +08:00
parent ad25634905
commit e882b9ddcc
9 changed files with 446 additions and 14 deletions

View file

@ -0,0 +1,5 @@
---
"roo-code": patch
---
Add DeepSeek V4 Flash and Pro model definitions with thinking mode controls and a chat quick selector for reasoning effort.

View file

@ -32,6 +32,34 @@ export const deepSeekModels = {
cacheReadsPrice: 0.028, // $0.028 per million tokens (cache hit) - Updated Dec 9, 2025
description: `DeepSeek-V3.2 (Thinking Mode) achieves performance comparable to OpenAI-o1 across math, code, and reasoning tasks. Supports Chain of Thought reasoning with up to 8K output tokens. Supports JSON output, tool calls, and chat prefix completion (beta).`,
},
"deepseek-v4-flash": {
maxTokens: 384_000, // 384K max output
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
preserveReasoning: true,
supportsReasoningEffort: ["disable", "high", "xhigh"],
reasoningEffort: "high",
inputPrice: 0.14, // $0.14 per million tokens (cache miss) - Updated Apr 24, 2026
outputPrice: 0.28, // $0.28 per million tokens - Updated Apr 24, 2026
cacheWritesPrice: 0.14, // $0.14 per million tokens (cache miss) - Updated Apr 24, 2026
cacheReadsPrice: 0.0028, // $0.0028 per million tokens (cache hit) - Updated Apr 24, 2026
description: `DeepSeek-V4 Flash supports 1M context, 384K max output, tool calls, and thinking/non-thinking modes.`,
},
"deepseek-v4-pro": {
maxTokens: 384_000, // 384K max output
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
preserveReasoning: true,
supportsReasoningEffort: ["disable", "high", "xhigh"],
reasoningEffort: "high",
inputPrice: 0.435, // $0.435 per million tokens (cache miss) - Updated Apr 24, 2026
outputPrice: 0.87, // $0.87 per million tokens - Updated Apr 24, 2026
cacheWritesPrice: 0.435, // $0.435 per million tokens (cache miss) - Updated Apr 24, 2026
cacheReadsPrice: 0.003625, // $0.003625 per million tokens (cache hit) - Updated Apr 24, 2026
description: `DeepSeek-V4 Pro supports 1M context, 384K max output, tool calls, and thinking/non-thinking modes.`,
},
} as const satisfies Record<string, ModelInfo>
// https://api-docs.deepseek.com/quick_start/parameter_settings

View file

@ -30,7 +30,8 @@ vi.mock("openai", () => {
}
// Check if this is a reasoning_content test by looking at model
const isReasonerModel = options.model?.includes("deepseek-reasoner")
const isReasonerModel =
options.model?.includes("deepseek-reasoner") || options.model?.includes("deepseek-v4")
const isToolCallTest = options.tools?.length > 0
// Return async iterator for streaming
@ -122,7 +123,7 @@ vi.mock("openai", () => {
import OpenAI from "openai"
import type { Anthropic } from "@anthropic-ai/sdk"
import { deepSeekDefaultModelId, DEEP_SEEK_DEFAULT_TEMPERATURE, type ModelInfo } from "@roo-code/types"
import { deepSeekDefaultModelId, deepSeekModels, DEEP_SEEK_DEFAULT_TEMPERATURE, type ModelInfo } from "@roo-code/types"
import type { ApiHandlerOptions } from "../../../shared/api"
@ -226,6 +227,28 @@ describe("DeepSeekHandler", () => {
expect(model.info.supportsPromptCache).toBe(true)
})
it.each([
["deepseek-v4-flash", deepSeekModels["deepseek-v4-flash"]],
["deepseek-v4-pro", deepSeekModels["deepseek-v4-pro"]],
])("should return correct model info for %s", (modelId, expectedInfo) => {
const handlerWithV4 = new DeepSeekHandler({
...mockOptions,
apiModelId: modelId,
})
const model = handlerWithV4.getModel()
const info = model.info as ModelInfo
expect(model.id).toBe(modelId)
expect(info.maxTokens).toBe(384_000)
expect(info.contextWindow).toBe(1_000_000)
expect(info.supportsPromptCache).toBe(true)
expect(info.preserveReasoning).toBe(true)
expect(info.supportsReasoningEffort).toEqual(["disable", "high", "xhigh"])
expect(info.reasoningEffort).toBe("high")
expect(model.info).toBe(expectedInfo)
})
it("should have preserveReasoning enabled for deepseek-reasoner to support interleaved thinking", () => {
// This is critical for DeepSeek's interleaved thinking mode with tool calls.
// See: https://api-docs.deepseek.com/guides/thinking_mode
@ -475,6 +498,60 @@ describe("DeepSeekHandler", () => {
expect(callArgs.thinking).toBeUndefined()
})
it("should pass reasoning parameters for DeepSeek V4 models", async () => {
const v4Handler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-v4-flash",
enableReasoningEffort: true,
reasoningEffort: "high",
})
const stream = v4Handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
// Consume the stream
}
const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs.thinking).toEqual({ type: "enabled" })
expect(callArgs.reasoning_effort).toBe("high")
})
it("should map xhigh reasoning effort to DeepSeek max", async () => {
const v4Handler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-v4-pro",
enableReasoningEffort: true,
reasoningEffort: "xhigh",
})
const stream = v4Handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
// Consume the stream
}
const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs.thinking).toEqual({ type: "enabled" })
expect(callArgs.reasoning_effort).toBe("max")
})
it("should disable thinking for DeepSeek V4 when reasoning effort is disabled", async () => {
const v4Handler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-v4-flash",
enableReasoningEffort: true,
reasoningEffort: "disable",
})
const stream = v4Handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
// Consume the stream
}
const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs.thinking).toEqual({ type: "disabled" })
expect(callArgs.reasoning_effort).toBeUndefined()
})
it("should handle tool calls with reasoning_content", async () => {
const reasonerHandler = new DeepSeekHandler({
...mockOptions,

View file

@ -391,6 +391,78 @@ describe("OpenAiHandler", () => {
expect(callArgs.reasoning_effort).toBeUndefined()
})
it("should include DeepSeek V4 thinking and high reasoning effort for OpenAI-compatible endpoints", async () => {
const deepSeekV4Options: ApiHandlerOptions = {
...mockOptions,
openAiBaseUrl: "https://api.deepseek.com",
openAiModelId: "deepseek-v4-flash",
enableReasoningEffort: true,
reasoningEffort: "high",
openAiCustomModelInfo: {
contextWindow: 1_000_000,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "high", "xhigh"],
reasoningEffort: "high",
},
}
const deepSeekV4Handler = new OpenAiHandler(deepSeekV4Options)
const stream = deepSeekV4Handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
}
const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs.thinking).toEqual({ type: "enabled" })
expect(callArgs.reasoning_effort).toBe("high")
})
it("should map DeepSeek V4 xhigh reasoning effort to max for OpenAI-compatible endpoints", async () => {
const deepSeekV4Options: ApiHandlerOptions = {
...mockOptions,
openAiBaseUrl: "https://api.deepseek.com",
openAiModelId: "deepseek-v4-pro",
enableReasoningEffort: true,
reasoningEffort: "xhigh",
openAiCustomModelInfo: {
contextWindow: 1_000_000,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "high", "xhigh"],
reasoningEffort: "high",
},
}
const deepSeekV4Handler = new OpenAiHandler(deepSeekV4Options)
const stream = deepSeekV4Handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
}
const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs.thinking).toEqual({ type: "enabled" })
expect(callArgs.reasoning_effort).toBe("max")
})
it("should disable DeepSeek V4 thinking for OpenAI-compatible endpoints", async () => {
const deepSeekV4Options: ApiHandlerOptions = {
...mockOptions,
openAiBaseUrl: "https://api.deepseek.com",
openAiModelId: "deepseek-v4-flash",
enableReasoningEffort: true,
reasoningEffort: "disable",
openAiCustomModelInfo: {
contextWindow: 1_000_000,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "high", "xhigh"],
reasoningEffort: "high",
},
}
const deepSeekV4Handler = new OpenAiHandler(deepSeekV4Options)
const stream = deepSeekV4Handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
}
const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs.thinking).toEqual({ type: "disabled" })
expect(callArgs.reasoning_effort).toBeUndefined()
})
it("should include max_tokens when includeMaxTokens is true", async () => {
const optionsWithMaxTokens: ApiHandlerOptions = {
...mockOptions,

View file

@ -53,10 +53,18 @@ export class DeepSeekHandler extends OpenAiHandler {
metadata?: ApiHandlerCreateMessageMetadata,
): ApiStream {
const modelId = this.options.apiModelId ?? deepSeekDefaultModelId
const { info: modelInfo } = this.getModel()
const { info: modelInfo, reasoning } = this.getModel()
// Check if this is a thinking-enabled model (deepseek-reasoner)
const isThinkingModel = modelId.includes("deepseek-reasoner")
const isDeepSeekV4Model = modelId.includes("deepseek-v4")
const isThinkingModel = modelId.includes("deepseek-reasoner") || isDeepSeekV4Model
const thinkingType =
isDeepSeekV4Model &&
(this.options.reasoningEffort === "disable" || this.options.enableReasoningEffort === false)
? "disabled"
: "enabled"
const selectedReasoningEffort = (reasoning as { reasoning_effort?: string } | undefined)?.reasoning_effort
const reasoningEffort =
selectedReasoningEffort === "xhigh" ? "max" : selectedReasoningEffort === "high" ? "high" : undefined
// Convert messages to R1 format (merges consecutive same-role messages)
// This is required for DeepSeek which does not support successive messages with the same role
@ -74,8 +82,12 @@ export class DeepSeekHandler extends OpenAiHandler {
messages: convertedMessages,
stream: true as const,
stream_options: { include_usage: true },
// Enable thinking mode for deepseek-reasoner or when tools are used with thinking model
...(isThinkingModel && { thinking: { type: "enabled" } }),
// Enable thinking mode for DeepSeek reasoning models. DeepSeek V4 also supports explicit disabling.
...(isThinkingModel && { thinking: { type: thinkingType } }),
...(reasoningEffort && {
// DeepSeek accepts "max"; the OpenAI SDK type has not caught up yet.
reasoning_effort: reasoningEffort as OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"],
}),
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? true,

View file

@ -25,6 +25,22 @@ import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from ".
import { getApiRequestTimeout } from "./utils/timeout-config"
import { handleOpenAIError } from "./utils/openai-error-handler"
type OpenAiCompatibleChatCompletionParamsStreaming = Omit<
OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming,
"reasoning_effort"
> & {
thinking?: { type: "enabled" | "disabled" }
reasoning_effort?: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"]
}
type OpenAiCompatibleChatCompletionParamsNonStreaming = Omit<
OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming,
"reasoning_effort"
> & {
thinking?: { type: "enabled" | "disabled" }
reasoning_effort?: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"]
}
// TODO: Rename this to OpenAICompatibleHandler. Also, I think the
// `OpenAINativeHandler` can subclass from this, since it's obviously
// compatible with the OpenAI API. We can also rename it to `OpenAIHandler`.
@ -89,7 +105,22 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
const modelId = this.options.openAiModelId ?? ""
const enabledR1Format = this.options.openAiR1FormatEnabled ?? false
const isAzureAiInference = this._isAzureAiInference(modelUrl)
const deepseekReasoner = modelId.includes("deepseek-reasoner") || enabledR1Format
const isDeepSeekV4Model = modelId.includes("deepseek-v4")
const usesDeepSeekThinkingParam = modelId.includes("deepseek-reasoner") || isDeepSeekV4Model
const deepseekReasoner = usesDeepSeekThinkingParam || enabledR1Format
const deepseekThinkingType =
isDeepSeekV4Model &&
(this.options.reasoningEffort === "disable" || this.options.enableReasoningEffort === false)
? "disabled"
: "enabled"
const selectedReasoningEffort = (reasoning as { reasoning_effort?: string } | undefined)?.reasoning_effort
const providerReasoning =
isDeepSeekV4Model && selectedReasoningEffort === "xhigh"
? {
// DeepSeek accepts "max"; the OpenAI SDK type has not caught up yet.
reasoning_effort: "max" as OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"],
}
: reasoning
if (modelId.includes("o1") || modelId.includes("o3") || modelId.includes("o4")) {
yield* this.handleO3FamilyMessage(modelId, systemPrompt, messages, metadata)
@ -152,13 +183,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
const isGrokXAI = this._isGrokXAI(this.options.openAiBaseUrl)
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
const requestOptions: OpenAiCompatibleChatCompletionParamsStreaming = {
model: modelId,
temperature: this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
messages: convertedMessages,
stream: true as const,
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
...(reasoning && reasoning),
...(usesDeepSeekThinkingParam && { thinking: { type: deepseekThinkingType } }),
...(providerReasoning && providerReasoning),
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
@ -221,11 +253,13 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
yield this.processUsageMetrics(lastUsage, modelInfo)
}
} else {
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
const requestOptions: OpenAiCompatibleChatCompletionParamsNonStreaming = {
model: modelId,
messages: deepseekReasoner
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
: [systemMessage, ...convertToOpenAiMessages(messages)],
...(usesDeepSeekThinkingParam && { thinking: { type: deepseekThinkingType } }),
...(providerReasoning && providerReasoning),
// Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS)
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,

View file

@ -67,6 +67,7 @@ export const shouldUseReasoningEffort = ({
| "low"
| "medium"
| "high"
| "xhigh"
| undefined
// "disable" explicitly omits reasoning
@ -92,6 +93,7 @@ export const shouldUseReasoningEffort = ({
| "low"
| "medium"
| "high"
| "xhigh"
| undefined
return !!modelDefaultEffort
}

View file

@ -1,9 +1,9 @@
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
import { useEvent } from "react-use"
import DynamicTextArea from "react-textarea-autosize"
import { VolumeX, Image, WandSparkles, SendHorizontal, X, ListEnd, Square } from "lucide-react"
import { VolumeX, Image, WandSparkles, SendHorizontal, X, ListEnd, Square, Brain } from "lucide-react"
import type { ExtensionMessage } from "@roo-code/types"
import { reasoningEfforts, type ExtensionMessage, type ModelInfo, type ProviderSettings } from "@roo-code/types"
import { mentionRegex, mentionRegexGlobal, commandRegexGlobal, unescapeSpaces } from "@roo/context-mentions"
import { WebviewMessage } from "@roo/WebviewMessage"
@ -22,7 +22,8 @@ import {
} from "@src/utils/context-mentions"
import { cn } from "@src/lib/utils"
import { convertToMentionPath } from "@src/utils/path-mentions"
import { StandardTooltip } from "@src/components/ui"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StandardTooltip } from "@src/components/ui"
import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
import Thumbnails from "../common/Thumbnails"
import { ModeSelector } from "./ModeSelector"
@ -34,6 +35,146 @@ import { IndexingStatusBadge } from "./IndexingStatusBadge"
import { usePromptHistory } from "./hooks/usePromptHistory"
import { CloudAccountSwitcher } from "../cloud/CloudAccountSwitcher"
type ReasoningEffortOption = "disable" | "none" | "minimal" | "low" | "medium" | "high" | "xhigh"
interface ReasoningEffortSelectorProps {
apiConfiguration?: ProviderSettings
currentApiConfigName?: string
modelId?: string
modelInfo?: ModelInfo
disabled?: boolean
}
const ReasoningEffortSelector = ({
apiConfiguration,
currentApiConfigName,
modelId,
modelInfo,
disabled,
}: ReasoningEffortSelectorProps) => {
const { t } = useAppTranslation()
const supports = modelInfo?.supportsReasoningEffort
const isSupported = !!supports
const availableOptions = useMemo<ReadonlyArray<ReasoningEffortOption>>(() => {
if (!isSupported) {
return []
}
const baseOptions =
supports === true
? (reasoningEfforts as readonly ReasoningEffortOption[])
: Array.isArray(supports)
? (supports as ReadonlyArray<ReasoningEffortOption>)
: (reasoningEfforts as readonly ReasoningEffortOption[])
if (!modelInfo?.requiredReasoningEffort && supports === true && !baseOptions.includes("disable")) {
return ["disable", ...baseOptions]
}
return baseOptions
}, [isSupported, modelInfo?.requiredReasoningEffort, supports])
const currentReasoningEffort = useMemo<ReasoningEffortOption>(() => {
const storedReasoningEffort = apiConfiguration?.reasoningEffort as ReasoningEffortOption | undefined
const defaultReasoningEffort = modelInfo?.requiredReasoningEffort
? ((modelInfo.reasoningEffort as ReasoningEffortOption | undefined) ?? "medium")
: "disable"
if (storedReasoningEffort && availableOptions.includes(storedReasoningEffort)) {
return storedReasoningEffort
}
if (defaultReasoningEffort && availableOptions.includes(defaultReasoningEffort)) {
return defaultReasoningEffort
}
return availableOptions[0] ?? "disable"
}, [
apiConfiguration?.reasoningEffort,
availableOptions,
modelInfo?.reasoningEffort,
modelInfo?.requiredReasoningEffort,
])
const configuredModelId = apiConfiguration?.apiModelId ?? apiConfiguration?.openAiModelId ?? modelId
const isDeepSeekV4Model = configuredModelId?.startsWith("deepseek-v4-") ?? false
const getOptionLabel = useCallback(
(value: ReasoningEffortOption) => {
if (isDeepSeekV4Model) {
if (value === "disable" || value === "none") {
return "off"
}
if (value === "xhigh") {
return "max"
}
return value
}
return value === "none" || value === "disable"
? t("settings:providers.reasoningEffort.none")
: t(`settings:providers.reasoningEffort.${value}`)
},
[isDeepSeekV4Model, t],
)
const handleReasoningEffortChange = useCallback(
(value: ReasoningEffortOption) => {
if (!apiConfiguration || !currentApiConfigName) {
return
}
const updatedConfiguration: ProviderSettings = {
...apiConfiguration,
enableReasoningEffort: value !== "disable",
reasoningEffort: value,
}
vscode.postMessage({
type: "upsertApiConfiguration",
text: currentApiConfigName,
apiConfiguration: updatedConfiguration,
})
},
[apiConfiguration, currentApiConfigName],
)
if (!isSupported || availableOptions.length === 0) {
return null
}
return (
<div className="flex-shrink-0" data-testid="quick-reasoning-effort-selector">
<Select
value={currentReasoningEffort}
onValueChange={(value) => handleReasoningEffortChange(value as ReasoningEffortOption)}
disabled={disabled}>
<SelectTrigger
aria-label={t("settings:providers.reasoningEffort.label")}
className={cn(
"h-[24px] min-w-[56px] max-w-[150px] rounded-md px-1.5 py-1 text-xs gap-1 bg-transparent",
"border-[rgba(255,255,255,0.08)] hover:bg-[rgba(255,255,255,0.03)]",
"[&_svg]:size-3.5",
)}>
<Brain className="size-3.5 flex-shrink-0 opacity-70" />
<SelectValue>{getOptionLabel(currentReasoningEffort)}</SelectValue>
</SelectTrigger>
<SelectContent align="start" sideOffset={4} className="min-w-[150px]">
{availableOptions.map((value) => (
<SelectItem key={value} value={value}>
{getOptionLabel(value)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)
}
interface ChatTextAreaProps {
inputValue: string
setInputValue: (value: string) => void
@ -99,7 +240,9 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
cloudUserInfo,
enterBehavior,
lockApiConfigAcrossModes,
apiConfiguration,
} = useExtensionState()
const { id: selectedModelId, info: selectedModelInfo } = useSelectedModel(apiConfiguration)
// Find the ID and display text for the currently selected API configuration.
const { currentConfigId, displayName } = useMemo(() => {
@ -1320,6 +1463,13 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
lockApiConfigAcrossModes={!!lockApiConfigAcrossModes}
onToggleLockApiConfig={handleToggleLockApiConfig}
/>
<ReasoningEffortSelector
apiConfiguration={apiConfiguration}
currentApiConfigName={currentApiConfigName}
modelId={selectedModelId}
modelInfo={selectedModelInfo}
disabled={selectApiConfigDisabled}
/>
<AutoApproveDropdown triggerClassName="min-w-[28px] text-ellipsis overflow-hidden flex-shrink" />
</div>
<div

View file

@ -4,6 +4,7 @@ import { render, fireEvent, screen } from "@src/utils/test-utils"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { vscode } from "@src/utils/vscode"
import * as pathMentions from "@src/utils/path-mentions"
import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
import { ChatTextArea } from "../ChatTextArea"
@ -32,6 +33,13 @@ const mockConvertToMentionPath = pathMentions.convertToMentionPath as ReturnType
// Mock ExtensionStateContext
vi.mock("@src/context/ExtensionStateContext")
vi.mock("@src/components/ui/hooks/useSelectedModel")
beforeAll(() => {
if (!HTMLElement.prototype.hasPointerCapture) {
HTMLElement.prototype.hasPointerCapture = vi.fn()
}
})
// Custom query function to get the enhance prompt button
const getEnhancePromptButton = () => {
@ -73,6 +81,7 @@ describe("ChatTextArea", () => {
taskHistory: [],
cwd: "/test/workspace",
})
;(useSelectedModel as ReturnType<typeof vi.fn>).mockReturnValue({ id: "claude-test", info: undefined })
})
describe("enhance prompt button", () => {
@ -182,6 +191,49 @@ describe("ChatTextArea", () => {
})
})
describe("quick reasoning effort selector", () => {
it("does not render when the selected model does not support reasoning effort", () => {
render(<ChatTextArea {...defaultProps} />)
expect(screen.queryByTestId("quick-reasoning-effort-selector")).not.toBeInTheDocument()
})
it("renders when the selected model supports reasoning effort", () => {
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
filePaths: [],
openedTabs: [],
apiConfiguration: {
apiProvider: "deepseek",
apiModelId: "deepseek-v4-pro",
reasoningEffort: "xhigh",
enableReasoningEffort: true,
},
currentApiConfigName: "DeepSeek",
listApiConfigMeta: [{ id: "deepseek", name: "DeepSeek", modelId: "deepseek-v4-pro" }],
taskHistory: [],
cwd: "/test/workspace",
})
;(useSelectedModel as ReturnType<typeof vi.fn>).mockReturnValue({
id: "deepseek-v4-pro",
info: {
contextWindow: 1_000_000,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "high", "xhigh"],
reasoningEffort: "high",
},
})
render(<ChatTextArea {...defaultProps} />)
expect(screen.getByTestId("quick-reasoning-effort-selector")).toBeInTheDocument()
expect(
screen.getByRole("combobox", { name: "settings:providers.reasoningEffort.label" }),
).toBeInTheDocument()
expect(screen.getByText("max")).toBeInTheDocument()
expect(screen.queryByText("settings:providers.reasoningEffort.xhigh")).not.toBeInTheDocument()
})
})
describe("enhanced prompt response", () => {
it("should update input value using native browser methods when receiving enhanced prompt", () => {
const setInputValue = vi.fn()