mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-09 03:17:58 +00:00
parent
31f7372897
commit
161345c6fa
6 changed files with 121 additions and 92 deletions
|
|
@ -511,17 +511,16 @@ describe("RooHandler", () => {
|
|||
// Consume stream
|
||||
}
|
||||
|
||||
const firstCallBody = mockCreate.mock.calls[0][0]
|
||||
expect(firstCallBody).toEqual(
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: mockOptions.apiModelId,
|
||||
messages: expect.any(Array),
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
reasoning: { enabled: false },
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
expect(firstCallBody.reasoning).toBeUndefined()
|
||||
expect(mockCreate.mock.calls[0][1]).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should include reasoning with enabled: false when explicitly disabled", async () => {
|
||||
|
|
@ -596,7 +595,7 @@ describe("RooHandler", () => {
|
|||
)
|
||||
})
|
||||
|
||||
it("should include reasoning for minimal", async () => {
|
||||
it("should not include reasoning for minimal (treated as none)", async () => {
|
||||
handler = new RooHandler({
|
||||
...mockOptions,
|
||||
reasoningEffort: "minimal",
|
||||
|
|
@ -606,8 +605,9 @@ describe("RooHandler", () => {
|
|||
// Consume stream
|
||||
}
|
||||
|
||||
// minimal should result in no reasoning parameter
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs.reasoning).toEqual({ enabled: true, effort: "minimal" })
|
||||
expect(callArgs.reasoning).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should handle enableReasoningEffort: false overriding reasoningEffort setting", async () => {
|
||||
|
|
|
|||
|
|
@ -826,10 +826,10 @@ describe("reasoning.ts", () => {
|
|||
}
|
||||
|
||||
const result = getRooReasoning(options)
|
||||
expect(result).toBeUndefined()
|
||||
expect(result).toEqual({ enabled: false })
|
||||
})
|
||||
|
||||
it("should include reasoning params for minimal effort", () => {
|
||||
it("should omit reasoning params for minimal effort", () => {
|
||||
const modelWithSupported: ModelInfo = {
|
||||
...baseModel,
|
||||
supportsReasoningEffort: true,
|
||||
|
|
@ -847,7 +847,7 @@ describe("reasoning.ts", () => {
|
|||
}
|
||||
|
||||
const result = getRooReasoning(options)
|
||||
expect(result).toEqual({ enabled: true, effort: "minimal" })
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should handle all valid reasoning effort values", () => {
|
||||
|
|
@ -889,7 +889,7 @@ describe("reasoning.ts", () => {
|
|||
}
|
||||
|
||||
const result = getRooReasoning(options)
|
||||
expect(result).toBeUndefined()
|
||||
expect(result).toEqual({ enabled: false })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -52,23 +52,34 @@ export const getRooReasoning = ({
|
|||
// Check if model supports reasoning effort
|
||||
if (!model.supportsReasoningEffort) return undefined
|
||||
|
||||
// If disabled via toggle, send explicit disabled flag for back-compat
|
||||
// Explicit off switch from settings: always send disabled for back-compat and to
|
||||
// prevent automatic reasoning when the toggle is turned off.
|
||||
if (settings.enableReasoningEffort === false) {
|
||||
return { enabled: false }
|
||||
}
|
||||
|
||||
// If the selection is "disable", omit the field entirely (no reasoning param)
|
||||
// For Roo models that support reasoning effort, absence of a selection should be
|
||||
// treated as an explicit "off" signal so that the backend does not auto-enable
|
||||
// reasoning. This aligns with the default behavior in tests.
|
||||
if (!reasoningEffort) {
|
||||
return { enabled: false }
|
||||
}
|
||||
|
||||
// "disable" is a legacy sentinel that means "omit the reasoning field entirely"
|
||||
// and let the server decide any defaults.
|
||||
if (reasoningEffort === "disable") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// When an effort is provided (including "none" and "minimal"), enable with effort
|
||||
if (reasoningEffort) {
|
||||
return { enabled: true, effort: reasoningEffort as ReasoningEffortExtended }
|
||||
// For Roo, "minimal" is treated as "none" for effort-based reasoning – we omit
|
||||
// the reasoning field entirely instead of sending an explicit effort.
|
||||
if (reasoningEffort === "minimal") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// No explicit selection -> omit field
|
||||
return undefined
|
||||
// When an effort is provided (e.g. "low" | "medium" | "high" | "none"), enable
|
||||
// with the selected effort.
|
||||
return { enabled: true, effort: reasoningEffort as ReasoningEffortExtended }
|
||||
}
|
||||
|
||||
export const getAnthropicReasoning = ({
|
||||
|
|
|
|||
|
|
@ -1,36 +1,3 @@
|
|||
/*
|
||||
Semantics for Reasoning Effort (SimpleThinkingBudget)
|
||||
|
||||
Capability surface:
|
||||
- modelInfo.supportsReasoningEffort: boolean | Array<"disable" | "none" | "minimal" | "low" | "medium" | "high">
|
||||
- true → UI shows ["low","medium","high"]
|
||||
- array → UI shows exactly the provided values
|
||||
|
||||
Selection behavior:
|
||||
- "disable":
|
||||
- Label: t("settings:providers.reasoningEffort.none")
|
||||
- set enableReasoningEffort = false
|
||||
- persist reasoningEffort = "disable"
|
||||
- request builders omit any reasoning parameter/body sections
|
||||
- "none":
|
||||
- Label: t("settings:providers.reasoningEffort.none")
|
||||
- set enableReasoningEffort = true
|
||||
- persist reasoningEffort = "none"
|
||||
- request builders include reasoning with value "none"
|
||||
- "minimal" | "low" | "medium" | "high":
|
||||
- set enableReasoningEffort = true
|
||||
- persist the selected value
|
||||
- request builders include reasoning with the selected effort
|
||||
|
||||
Required:
|
||||
- If modelInfo.requiredReasoningEffort is true, do not synthesize a "None" choice. Only show values from the capability.
|
||||
- On mount, if unset and a default exists, set enableReasoningEffort = true and use modelInfo.reasoningEffort.
|
||||
|
||||
Notes:
|
||||
- Current selection is normalized to the capability: unsupported persisted values are not shown.
|
||||
- Both "disable" and "none" display as the "None" label per UX, but are wired differently as above.
|
||||
- "minimal" uses t("settings:providers.reasoningEffort.minimal").
|
||||
*/
|
||||
import { useEffect } from "react"
|
||||
|
||||
import { type ProviderSettings, type ModelInfo, type ReasoningEffort, reasoningEfforts } from "@roo-code/types"
|
||||
|
|
@ -48,8 +15,8 @@ interface SimpleThinkingBudgetProps {
|
|||
modelInfo?: ModelInfo
|
||||
}
|
||||
|
||||
// Reasoning selection values including control values
|
||||
type ReasoningSelectValue = "disable" | "none" | "minimal" | ReasoningEffort
|
||||
// Extended type to include "none" option
|
||||
type ReasoningEffortWithNone = ReasoningEffort | "none"
|
||||
|
||||
export const SimpleThinkingBudget = ({
|
||||
apiConfiguration,
|
||||
|
|
@ -58,46 +25,57 @@ export const SimpleThinkingBudget = ({
|
|||
}: SimpleThinkingBudgetProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const isSupported = !!modelInfo?.supportsReasoningEffort
|
||||
// Check model capabilities
|
||||
const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort
|
||||
const isReasoningEffortRequired = !!modelInfo && modelInfo.requiredReasoningEffort
|
||||
|
||||
const isReasoningEffortRequired = !!modelInfo?.requiredReasoningEffort
|
||||
// Build available reasoning efforts list
|
||||
// Include "none" option unless reasoning effort is required
|
||||
const baseEfforts = [...reasoningEfforts] as ReasoningEffort[]
|
||||
const availableReasoningEfforts: ReadonlyArray<ReasoningEffortWithNone> = isReasoningEffortRequired
|
||||
? baseEfforts
|
||||
: (["none", ...baseEfforts] as ReasoningEffortWithNone[])
|
||||
|
||||
// Compute available options from capability
|
||||
const supports = modelInfo?.supportsReasoningEffort
|
||||
const availableOptions: readonly ReasoningSelectValue[] =
|
||||
supports === true ? ([...reasoningEfforts] as const) : (supports as any)
|
||||
// Default reasoning effort - use model's default if available, otherwise "medium"
|
||||
const modelDefaultReasoningEffort = modelInfo?.reasoningEffort as ReasoningEffort | undefined
|
||||
const defaultReasoningEffort: ReasoningEffortWithNone = isReasoningEffortRequired
|
||||
? modelDefaultReasoningEffort || "medium"
|
||||
: "none"
|
||||
|
||||
// Helper for labels
|
||||
const labelFor = (v: ReasoningSelectValue) => {
|
||||
if (v === "disable" || v === "none") return t("settings:providers.reasoningEffort.none")
|
||||
if (v === "minimal") return t("settings:providers.reasoningEffort.minimal")
|
||||
return t(`settings:providers.reasoningEffort.${v}`)
|
||||
}
|
||||
// Current reasoning effort - treat undefined/null as "none"
|
||||
const currentReasoningEffort: ReasoningEffortWithNone =
|
||||
(apiConfiguration.reasoningEffort as ReasoningEffort | undefined) || defaultReasoningEffort
|
||||
|
||||
// Determine current selection (normalize to capability)
|
||||
let current: ReasoningSelectValue | undefined = apiConfiguration.reasoningEffort as ReasoningSelectValue | undefined
|
||||
if (!current && isReasoningEffortRequired && modelInfo.reasoningEffort) {
|
||||
current = modelInfo.reasoningEffort as ReasoningSelectValue
|
||||
}
|
||||
// If persisted value isn't supported by capability (e.g., "minimal" while supports=true), don't show it
|
||||
const normalizedCurrent: ReasoningSelectValue | undefined =
|
||||
current && (availableOptions as readonly any[]).includes(current) ? current : undefined
|
||||
|
||||
// Default when required: set to model default on mount (no synthetic "None")
|
||||
// Set default reasoning effort when model supports it and no value is set
|
||||
useEffect(() => {
|
||||
if (!isReasoningEffortRequired) return
|
||||
if (!apiConfiguration.reasoningEffort && modelInfo?.reasoningEffort) {
|
||||
setApiConfigurationField("enableReasoningEffort", true, false)
|
||||
setApiConfigurationField("reasoningEffort", modelInfo?.reasoningEffort as any, false)
|
||||
if (isReasoningEffortSupported && !apiConfiguration.reasoningEffort) {
|
||||
// Only set a default if reasoning is required, otherwise leave as undefined (which maps to "none")
|
||||
if (isReasoningEffortRequired && defaultReasoningEffort !== "none") {
|
||||
setApiConfigurationField("reasoningEffort", defaultReasoningEffort as ReasoningEffort, false)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
isReasoningEffortSupported,
|
||||
isReasoningEffortRequired,
|
||||
apiConfiguration.reasoningEffort,
|
||||
modelInfo?.reasoningEffort,
|
||||
defaultReasoningEffort,
|
||||
setApiConfigurationField,
|
||||
])
|
||||
|
||||
if (!isSupported) {
|
||||
useEffect(() => {
|
||||
if (!isReasoningEffortSupported) return
|
||||
const shouldEnable = isReasoningEffortRequired || currentReasoningEffort !== "none"
|
||||
if (shouldEnable && apiConfiguration.enableReasoningEffort !== true) {
|
||||
setApiConfigurationField("enableReasoningEffort", true, false)
|
||||
}
|
||||
}, [
|
||||
isReasoningEffortSupported,
|
||||
isReasoningEffortRequired,
|
||||
currentReasoningEffort,
|
||||
apiConfiguration.enableReasoningEffort,
|
||||
setApiConfigurationField,
|
||||
])
|
||||
if (!modelInfo || !isReasoningEffortSupported) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
@ -107,25 +85,32 @@ export const SimpleThinkingBudget = ({
|
|||
<label className="block font-medium mb-1">{t("settings:providers.reasoningEffort.label")}</label>
|
||||
</div>
|
||||
<Select
|
||||
value={normalizedCurrent}
|
||||
onValueChange={(value: ReasoningSelectValue) => {
|
||||
if (value === "disable") {
|
||||
setApiConfigurationField("enableReasoningEffort", false, true)
|
||||
setApiConfigurationField("reasoningEffort", "disable" as any, true)
|
||||
value={currentReasoningEffort}
|
||||
onValueChange={(value: ReasoningEffortWithNone) => {
|
||||
// If "none" is selected, clear the reasoningEffort field
|
||||
if (value === "none") {
|
||||
setApiConfigurationField("reasoningEffort", undefined)
|
||||
} else {
|
||||
setApiConfigurationField("enableReasoningEffort", true, true)
|
||||
setApiConfigurationField("reasoningEffort", value as any, true)
|
||||
setApiConfigurationField("reasoningEffort", value as ReasoningEffort)
|
||||
}
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue
|
||||
placeholder={normalizedCurrent ? labelFor(normalizedCurrent) : t("settings:common.select")}
|
||||
placeholder={
|
||||
currentReasoningEffort
|
||||
? currentReasoningEffort === "none"
|
||||
? t("settings:providers.reasoningEffort.none")
|
||||
: t(`settings:providers.reasoningEffort.${currentReasoningEffort}`)
|
||||
: t("settings:common.select")
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableOptions.map((value) => (
|
||||
{availableReasoningEfforts.map((value) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{labelFor(value)}
|
||||
{value === "none"
|
||||
? t("settings:providers.reasoningEffort.none")
|
||||
: t(`settings:providers.reasoningEffort.${value}`)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,37 @@
|
|||
/*
|
||||
Semantics for Reasoning Effort (ThinkingBudget)
|
||||
|
||||
Capability surface:
|
||||
- modelInfo.supportsReasoningEffort: boolean | Array<"disable" | "none" | "minimal" | "low" | "medium" | "high">
|
||||
- true → UI shows ["low","medium","high"]
|
||||
- array → UI shows exactly the provided values
|
||||
|
||||
Selection behavior:
|
||||
- "disable":
|
||||
- Label: t("settings:providers.reasoningEffort.none")
|
||||
- set enableReasoningEffort = false
|
||||
- persist reasoningEffort = "disable"
|
||||
- request builders omit any reasoning parameter/body sections
|
||||
- "none":
|
||||
- Label: t("settings:providers.reasoningEffort.none")
|
||||
- set enableReasoningEffort = true
|
||||
- persist reasoningEffort = "none"
|
||||
- request builders include reasoning with value "none"
|
||||
- "minimal" | "low" | "medium" | "high":
|
||||
- set enableReasoningEffort = true
|
||||
- persist the selected value
|
||||
- request builders include reasoning with the selected effort
|
||||
|
||||
Required:
|
||||
- If modelInfo.requiredReasoningEffort is true, do not synthesize a "None" choice. Only show values from the capability.
|
||||
- On mount, if unset and a default exists, set enableReasoningEffort = true and use modelInfo.reasoningEffort.
|
||||
|
||||
Notes:
|
||||
- Current selection is normalized to the capability: unsupported persisted values are not shown.
|
||||
- Both "disable" and "none" display as the "None" label per UX, but are wired differently as above.
|
||||
- "minimal" uses t("settings:providers.reasoningEffort.minimal").
|
||||
*/
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { Checkbox } from "vscrui"
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ vi.mock("@src/i18n/TranslationContext", () => ({
|
|||
const translations: Record<string, string> = {
|
||||
"settings:providers.reasoningEffort.label": "Model Reasoning Effort",
|
||||
"settings:providers.reasoningEffort.none": "None",
|
||||
"settings:providers.reasoningEffort.minimal": "Minimal",
|
||||
"settings:providers.reasoningEffort.low": "Low",
|
||||
"settings:providers.reasoningEffort.medium": "Medium",
|
||||
"settings:providers.reasoningEffort.high": "High",
|
||||
|
|
@ -190,7 +189,7 @@ describe("SimpleThinkingBudget", () => {
|
|||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText("Select")).toBeInTheDocument()
|
||||
expect(screen.getByText("None")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should use model default reasoning effort when required and available", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue