mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-17 23:51:08 +00:00
- Add useAdaptiveThinking and adaptiveThinkingEffort settings to provider config
- Add supportsAdaptiveThinking and supportsAdaptiveThinkingMaxEffort model flags
- Implement getAnthropicReasoning() with {type: 'adaptive'} mode
- Implement getAnthropicOutputConfig() with effort parameter (low/medium/high/max)
- Max effort only on Opus 4.6; falls back to high on Sonnet 4.6
- Add adaptive thinking checkbox and effort selector to ThinkingBudget UI
- Fix: Max Output Tokens slider always visible (independent of adaptive thinking)
- Add i18n translations for all 17 supported locales
- Add comprehensive test coverage for reasoning transforms
Fixes #11732
1442 lines
39 KiB
TypeScript
1442 lines
39 KiB
TypeScript
// npx vitest run src/api/transform/__tests__/reasoning.spec.ts
|
|
|
|
import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@roo-code/types"
|
|
|
|
import {
|
|
getOpenRouterReasoning,
|
|
getAnthropicReasoning,
|
|
getAnthropicOutputConfig,
|
|
getOpenAiReasoning,
|
|
getRooReasoning,
|
|
getGeminiReasoning,
|
|
GetModelReasoningOptions,
|
|
OpenRouterReasoningParams,
|
|
AnthropicReasoningParams,
|
|
OpenAiReasoningParams,
|
|
RooReasoningParams,
|
|
GeminiReasoningParams,
|
|
GeminiThinkingLevel,
|
|
} from "../reasoning"
|
|
|
|
describe("reasoning.ts", () => {
|
|
const baseModel: ModelInfo = {
|
|
contextWindow: 16000,
|
|
supportsPromptCache: true,
|
|
}
|
|
|
|
const baseSettings: ProviderSettings = {}
|
|
|
|
const baseOptions: GetModelReasoningOptions = {
|
|
model: baseModel,
|
|
reasoningBudget: 1000,
|
|
reasoningEffort: "medium",
|
|
settings: baseSettings,
|
|
}
|
|
|
|
describe("getOpenRouterReasoning", () => {
|
|
it("should return reasoning budget params when model has requiredReasoningBudget", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const options = { ...baseOptions, model: modelWithRequired }
|
|
const result = getOpenRouterReasoning(options)
|
|
|
|
expect(result).toEqual({ max_tokens: 1000 })
|
|
})
|
|
|
|
it("should return reasoning budget params when model supports reasoning budget and setting is enabled", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningBudget: true,
|
|
}
|
|
|
|
const settingsWithEnabled: ProviderSettings = {
|
|
enableReasoningEffort: true,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithEnabled,
|
|
}
|
|
|
|
const result = getOpenRouterReasoning(options)
|
|
|
|
expect(result).toEqual({ max_tokens: 1000 })
|
|
})
|
|
|
|
it("should return reasoning effort params when model supports reasoning effort and has effort in settings", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const settingsWithEffort: ProviderSettings = {
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithEffort,
|
|
reasoningEffort: "high" as const,
|
|
}
|
|
|
|
const result = getOpenRouterReasoning(options)
|
|
|
|
expect(result).toEqual({ effort: "high" })
|
|
})
|
|
|
|
it("should return reasoning effort params when model has reasoningEffort property", () => {
|
|
const modelWithEffort: ModelInfo = {
|
|
...baseModel,
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const options = { ...baseOptions, model: modelWithEffort }
|
|
const result = getOpenRouterReasoning(options)
|
|
|
|
expect(result).toEqual({ effort: "medium" })
|
|
})
|
|
|
|
it("should return undefined when model has no reasoning capabilities", () => {
|
|
const result = getOpenRouterReasoning(baseOptions)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should prioritize reasoning budget over reasoning effort", () => {
|
|
const hybridModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningBudget: true,
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const settingsWithBoth: ProviderSettings = {
|
|
enableReasoningEffort: true,
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: hybridModel,
|
|
settings: settingsWithBoth,
|
|
}
|
|
|
|
const result = getOpenRouterReasoning(options)
|
|
|
|
expect(result).toEqual({ max_tokens: 1000 })
|
|
})
|
|
|
|
it("should handle undefined reasoningBudget", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const optionsWithoutBudget = {
|
|
...baseOptions,
|
|
model: modelWithRequired,
|
|
reasoningBudget: undefined,
|
|
}
|
|
|
|
const result = getOpenRouterReasoning(optionsWithoutBudget)
|
|
|
|
expect(result).toEqual({ max_tokens: undefined })
|
|
})
|
|
|
|
it("should handle undefined reasoningEffort", () => {
|
|
const modelWithEffort: ModelInfo = {
|
|
...baseModel,
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const optionsWithoutEffort = {
|
|
...baseOptions,
|
|
model: modelWithEffort,
|
|
reasoningEffort: undefined,
|
|
}
|
|
|
|
const result = getOpenRouterReasoning(optionsWithoutEffort)
|
|
|
|
// When reasoningEffort is undefined, the function should return undefined
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should handle all reasoning effort values including minimal", () => {
|
|
const efforts: Array<ReasoningEffortWithMinimal> = ["minimal", "low", "medium", "high"]
|
|
|
|
efforts.forEach((effort) => {
|
|
const modelWithEffort: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const settingsWithEffort: ProviderSettings = {
|
|
reasoningEffort: effort,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithEffort,
|
|
settings: settingsWithEffort,
|
|
reasoningEffort: effort,
|
|
}
|
|
const result = getOpenRouterReasoning(options)
|
|
// All effort values including "minimal" should be passed through
|
|
expect(result).toEqual({ effort })
|
|
})
|
|
})
|
|
|
|
it("should handle minimal reasoning effort specifically", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const settingsWithEffort: ProviderSettings = {
|
|
reasoningEffort: "minimal",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithEffort,
|
|
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
|
|
}
|
|
|
|
const result = getOpenRouterReasoning(options)
|
|
|
|
// "minimal" should be passed through to OpenRouter
|
|
expect(result).toEqual({ effort: "minimal" })
|
|
})
|
|
|
|
it("should handle minimal reasoning effort from settings", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const settingsWithMinimal: ProviderSettings = {
|
|
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithMinimal,
|
|
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
|
|
}
|
|
|
|
const result = getOpenRouterReasoning(options)
|
|
|
|
// "minimal" should be passed through to OpenRouter
|
|
expect(result).toEqual({ effort: "minimal" })
|
|
})
|
|
|
|
it("should handle zero reasoningBudget", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const optionsWithZeroBudget = {
|
|
...baseOptions,
|
|
model: modelWithRequired,
|
|
reasoningBudget: 0,
|
|
}
|
|
|
|
const result = getOpenRouterReasoning(optionsWithZeroBudget)
|
|
|
|
expect(result).toEqual({ max_tokens: 0 })
|
|
})
|
|
|
|
it("should not use reasoning budget when supportsReasoningBudget is true but enableReasoningEffort is false", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningBudget: true,
|
|
}
|
|
|
|
const settingsWithDisabled: ProviderSettings = {
|
|
enableReasoningEffort: false,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithDisabled,
|
|
}
|
|
|
|
const result = getOpenRouterReasoning(options)
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should not use reasoning effort when supportsReasoningEffort is true but no effort is specified", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: {},
|
|
reasoningEffort: undefined,
|
|
}
|
|
|
|
const result = getOpenRouterReasoning(options)
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe("getAnthropicReasoning", () => {
|
|
it("should return reasoning budget params when model has requiredReasoningBudget", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const options = { ...baseOptions, model: modelWithRequired }
|
|
const result = getAnthropicReasoning(options)
|
|
|
|
expect(result).toEqual({
|
|
type: "enabled",
|
|
budget_tokens: 1000,
|
|
})
|
|
})
|
|
|
|
it("should return reasoning budget params when model supports reasoning budget and setting is enabled", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningBudget: true,
|
|
}
|
|
|
|
const settingsWithEnabled: ProviderSettings = {
|
|
enableReasoningEffort: true,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithEnabled,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(options)
|
|
|
|
expect(result).toEqual({
|
|
type: "enabled",
|
|
budget_tokens: 1000,
|
|
})
|
|
})
|
|
|
|
it("should return undefined when model has no reasoning budget capability", () => {
|
|
const result = getAnthropicReasoning(baseOptions)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should return undefined when supportsReasoningBudget is true but enableReasoningEffort is false", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningBudget: true,
|
|
}
|
|
|
|
const settingsWithDisabled: ProviderSettings = {
|
|
enableReasoningEffort: false,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithDisabled,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(options)
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should handle undefined reasoningBudget with non-null assertion", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const optionsWithoutBudget = {
|
|
...baseOptions,
|
|
model: modelWithRequired,
|
|
reasoningBudget: undefined,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(optionsWithoutBudget)
|
|
|
|
expect(result).toEqual({
|
|
type: "enabled",
|
|
budget_tokens: undefined,
|
|
})
|
|
})
|
|
|
|
it("should handle zero reasoningBudget", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const optionsWithZeroBudget = {
|
|
...baseOptions,
|
|
model: modelWithRequired,
|
|
reasoningBudget: 0,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(optionsWithZeroBudget)
|
|
|
|
expect(result).toEqual({
|
|
type: "enabled",
|
|
budget_tokens: 0,
|
|
})
|
|
})
|
|
|
|
it("should handle large reasoningBudget values", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const optionsWithLargeBudget = {
|
|
...baseOptions,
|
|
model: modelWithRequired,
|
|
reasoningBudget: 100000,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(optionsWithLargeBudget)
|
|
|
|
expect(result).toEqual({
|
|
type: "enabled",
|
|
budget_tokens: 100000,
|
|
})
|
|
})
|
|
|
|
it("should not be affected by reasoningEffort parameter", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const optionsWithEffort = {
|
|
...baseOptions,
|
|
model: modelWithRequired,
|
|
reasoningEffort: "high" as const,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(optionsWithEffort)
|
|
|
|
expect(result).toEqual({
|
|
type: "enabled",
|
|
budget_tokens: 1000,
|
|
})
|
|
})
|
|
|
|
it("should ignore reasoning effort capabilities for Anthropic", () => {
|
|
const modelWithEffort: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const settingsWithEffort: ProviderSettings = {
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithEffort,
|
|
settings: settingsWithEffort,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(options)
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should return adaptive thinking params when useAdaptiveThinking is true and model supports it", () => {
|
|
const modelWithAdaptive: ModelInfo = {
|
|
...baseModel,
|
|
supportsAdaptiveThinking: true,
|
|
}
|
|
|
|
const settingsWithAdaptive: ProviderSettings = {
|
|
useAdaptiveThinking: true,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithAdaptive,
|
|
settings: settingsWithAdaptive,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(options)
|
|
|
|
expect(result).toEqual({ type: "adaptive" })
|
|
})
|
|
|
|
it("should return {type: 'adaptive'} regardless of effort (effort goes in output_config)", () => {
|
|
const modelWithAdaptive: ModelInfo = {
|
|
...baseModel,
|
|
supportsAdaptiveThinking: true,
|
|
}
|
|
|
|
const settingsWithEffort: ProviderSettings = {
|
|
useAdaptiveThinking: true,
|
|
adaptiveThinkingEffort: "high",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithAdaptive,
|
|
settings: settingsWithEffort,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(options)
|
|
|
|
// output_config is a SEPARATE top-level param; thinking only contains {type: "adaptive"}
|
|
expect(result).toEqual({ type: "adaptive" })
|
|
})
|
|
|
|
it("should not return adaptive thinking when model does not support it", () => {
|
|
const settingsWithAdaptive: ProviderSettings = {
|
|
useAdaptiveThinking: true,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
settings: settingsWithAdaptive,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(options)
|
|
|
|
// Falls through to manual mode, but base model has no reasoning budget support
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should not return adaptive thinking when useAdaptiveThinking is false", () => {
|
|
const modelWithAdaptive: ModelInfo = {
|
|
...baseModel,
|
|
supportsAdaptiveThinking: true,
|
|
}
|
|
|
|
const settingsWithDisabled: ProviderSettings = {
|
|
useAdaptiveThinking: false,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithAdaptive,
|
|
settings: settingsWithDisabled,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(options)
|
|
|
|
// Falls through to manual mode, but base model has no reasoning budget support
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should prioritize adaptive thinking over manual budget when both settings present", () => {
|
|
const modelWithBoth: ModelInfo = {
|
|
...baseModel,
|
|
supportsAdaptiveThinking: true,
|
|
supportsReasoningBudget: true,
|
|
}
|
|
|
|
const settingsWithBoth: ProviderSettings = {
|
|
useAdaptiveThinking: true,
|
|
enableReasoningEffort: true,
|
|
adaptiveThinkingEffort: "medium",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithBoth,
|
|
settings: settingsWithBoth,
|
|
}
|
|
|
|
const result = getAnthropicReasoning(options)
|
|
|
|
// output_config is separate; thinking only contains {type: "adaptive"}
|
|
expect(result).toEqual({ type: "adaptive" })
|
|
})
|
|
})
|
|
|
|
describe("getAnthropicOutputConfig", () => {
|
|
it("should return undefined when adaptive thinking is not enabled", () => {
|
|
const result = getAnthropicOutputConfig({ model: baseModel, settings: {} })
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should return undefined when model does not support adaptive thinking", () => {
|
|
const settings: ProviderSettings = { useAdaptiveThinking: true, adaptiveThinkingEffort: "high" }
|
|
const result = getAnthropicOutputConfig({ model: baseModel, settings })
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should return undefined when no effort is specified", () => {
|
|
const modelWithAdaptive: ModelInfo = { ...baseModel, supportsAdaptiveThinking: true }
|
|
const settings: ProviderSettings = { useAdaptiveThinking: true }
|
|
const result = getAnthropicOutputConfig({ model: modelWithAdaptive, settings })
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should return effort when adaptiveThinkingEffort is set", () => {
|
|
const modelWithAdaptive: ModelInfo = { ...baseModel, supportsAdaptiveThinking: true }
|
|
const settings: ProviderSettings = { useAdaptiveThinking: true, adaptiveThinkingEffort: "high" }
|
|
const result = getAnthropicOutputConfig({ model: modelWithAdaptive, settings })
|
|
expect(result).toEqual({ effort: "high" })
|
|
})
|
|
|
|
it("should fallback to high when max is set but model does not support it", () => {
|
|
const modelNoMax: ModelInfo = {
|
|
...baseModel,
|
|
supportsAdaptiveThinking: true,
|
|
supportsAdaptiveThinkingMaxEffort: false,
|
|
}
|
|
const settings: ProviderSettings = { useAdaptiveThinking: true, adaptiveThinkingEffort: "max" }
|
|
const result = getAnthropicOutputConfig({ model: modelNoMax, settings })
|
|
expect(result).toEqual({ effort: "high" })
|
|
})
|
|
|
|
it("should return max effort when model supports it", () => {
|
|
const modelWithMax: ModelInfo = {
|
|
...baseModel,
|
|
supportsAdaptiveThinking: true,
|
|
supportsAdaptiveThinkingMaxEffort: true,
|
|
}
|
|
const settings: ProviderSettings = { useAdaptiveThinking: true, adaptiveThinkingEffort: "max" }
|
|
const result = getAnthropicOutputConfig({ model: modelWithMax, settings })
|
|
expect(result).toEqual({ effort: "max" })
|
|
})
|
|
|
|
it("should return low effort", () => {
|
|
const modelWithAdaptive: ModelInfo = { ...baseModel, supportsAdaptiveThinking: true }
|
|
const settings: ProviderSettings = { useAdaptiveThinking: true, adaptiveThinkingEffort: "low" }
|
|
const result = getAnthropicOutputConfig({ model: modelWithAdaptive, settings })
|
|
expect(result).toEqual({ effort: "low" })
|
|
})
|
|
|
|
it("should return medium effort", () => {
|
|
const modelWithAdaptive: ModelInfo = { ...baseModel, supportsAdaptiveThinking: true }
|
|
const settings: ProviderSettings = { useAdaptiveThinking: true, adaptiveThinkingEffort: "medium" }
|
|
const result = getAnthropicOutputConfig({ model: modelWithAdaptive, settings })
|
|
expect(result).toEqual({ effort: "medium" })
|
|
})
|
|
})
|
|
|
|
describe("getOpenAiReasoning", () => {
|
|
it("should return reasoning effort params when model supports reasoning effort and has effort in settings", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const settingsWithEffort: ProviderSettings = {
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithEffort,
|
|
reasoningEffort: "high" as const,
|
|
}
|
|
|
|
const result = getOpenAiReasoning(options)
|
|
|
|
expect(result).toEqual({ reasoning_effort: "high" })
|
|
})
|
|
|
|
it("should return reasoning effort params when model has reasoningEffort property", () => {
|
|
const modelWithEffort: ModelInfo = {
|
|
...baseModel,
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const options = { ...baseOptions, model: modelWithEffort }
|
|
const result = getOpenAiReasoning(options)
|
|
|
|
expect(result).toEqual({ reasoning_effort: "medium" })
|
|
})
|
|
|
|
it("should return undefined when model has no reasoning effort capability", () => {
|
|
const result = getOpenAiReasoning(baseOptions)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should return undefined when supportsReasoningEffort is true but no effort is specified", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: {},
|
|
reasoningEffort: undefined,
|
|
}
|
|
|
|
const result = getOpenAiReasoning(options)
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should handle undefined reasoningEffort", () => {
|
|
const modelWithEffort: ModelInfo = {
|
|
...baseModel,
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const optionsWithoutEffort = {
|
|
...baseOptions,
|
|
model: modelWithEffort,
|
|
reasoningEffort: undefined,
|
|
}
|
|
|
|
const result = getOpenAiReasoning(optionsWithoutEffort)
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should handle all reasoning effort values", () => {
|
|
const efforts: Array<"low" | "medium" | "high"> = ["low", "medium", "high"]
|
|
|
|
efforts.forEach((effort) => {
|
|
const modelWithEffort: ModelInfo = {
|
|
...baseModel,
|
|
reasoningEffort: effort,
|
|
}
|
|
|
|
const options = { ...baseOptions, model: modelWithEffort, reasoningEffort: effort }
|
|
const result = getOpenAiReasoning(options)
|
|
expect(result).toEqual({ reasoning_effort: effort })
|
|
})
|
|
})
|
|
|
|
it("should not be affected by reasoningBudget parameter", () => {
|
|
const modelWithEffort: ModelInfo = {
|
|
...baseModel,
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const optionsWithBudget = {
|
|
...baseOptions,
|
|
model: modelWithEffort,
|
|
reasoningBudget: 5000,
|
|
}
|
|
|
|
const result = getOpenAiReasoning(optionsWithBudget)
|
|
|
|
expect(result).toEqual({ reasoning_effort: "medium" })
|
|
})
|
|
|
|
it("should ignore reasoning budget capabilities for OpenAI", () => {
|
|
const modelWithBudget: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningBudget: true,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const settingsWithEnabled: ProviderSettings = {
|
|
enableReasoningEffort: true,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithBudget,
|
|
settings: settingsWithEnabled,
|
|
}
|
|
|
|
const result = getOpenAiReasoning(options)
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe("Gemini reasoning (effort models)", () => {
|
|
it("should return thinkingLevel when effort is set to low or high and budget is not used", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
// Effort-only reasoning model (no budget fields)
|
|
supportsReasoningEffort: ["low", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
enableReasoningEffort: true,
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: 2048,
|
|
reasoningEffort: "high",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
|
|
// Budget should not be used for effort-only models
|
|
expect(result).toEqual({ thinkingLevel: "high", includeThoughts: true })
|
|
})
|
|
|
|
it("should still return thinkingLevel when enableReasoningEffort is false but effort is explicitly set", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
// Effort-only reasoning model
|
|
supportsReasoningEffort: ["low", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
// Even with this flag false, an explicit effort selection should win
|
|
enableReasoningEffort: false,
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: 2048,
|
|
reasoningEffort: "high",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
expect(result).toEqual({ thinkingLevel: "high", includeThoughts: true })
|
|
})
|
|
|
|
it("should return thinkingLevel for minimal effort", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
reasoningEffort: "minimal",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: "minimal",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
expect(result).toEqual({ thinkingLevel: "minimal", includeThoughts: true })
|
|
})
|
|
|
|
it("should return thinkingLevel for medium effort", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: "medium",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
expect(result).toEqual({ thinkingLevel: "medium", includeThoughts: true })
|
|
})
|
|
|
|
it("should handle all four Gemini thinking levels", () => {
|
|
const levels: GeminiThinkingLevel[] = ["minimal", "low", "medium", "high"]
|
|
|
|
levels.forEach((level) => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: [
|
|
"minimal",
|
|
"low",
|
|
"medium",
|
|
"high",
|
|
] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
reasoningEffort: level,
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: level,
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
expect(result).toEqual({ thinkingLevel: level, includeThoughts: true })
|
|
})
|
|
})
|
|
|
|
it("should return undefined for disable effort", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
reasoningEffort: "disable",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: "disable",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should return undefined for none effort (invalid for Gemini)", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
reasoningEffort: "none",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: "none",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options)
|
|
// "none" is not a valid GeminiThinkingLevel, so no fallback — returns undefined
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should use thinkingBudget for budget-based models", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningBudget: true,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
enableReasoningEffort: true,
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: 4096,
|
|
reasoningEffort: "high",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
expect(result).toEqual({ thinkingBudget: 4096, includeThoughts: true })
|
|
})
|
|
|
|
it("should prioritize budget over effort when model has requiredReasoningBudget", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningBudget: true,
|
|
requiredReasoningBudget: true,
|
|
supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
enableReasoningEffort: true,
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: 8192,
|
|
reasoningEffort: "high",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
// Budget should take precedence
|
|
expect(result).toEqual({ thinkingBudget: 8192, includeThoughts: true })
|
|
})
|
|
|
|
it("should fall back to model default effort when settings.reasoningEffort is undefined", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: undefined,
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
expect(result).toEqual({ thinkingLevel: "medium", includeThoughts: true })
|
|
})
|
|
|
|
it("should fall back to model default when settings effort is not in supportsReasoningEffort array", () => {
|
|
// Simulates gemini-3-pro-preview which only supports ["low", "high"]
|
|
// but user has reasoningEffort: "medium" from a different model
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: ["low", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: "medium",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
// "medium" is not in ["low", "high"], so falls back to model.reasoningEffort "low"
|
|
expect(result).toEqual({ thinkingLevel: "low", includeThoughts: true })
|
|
})
|
|
|
|
it("should return undefined when unsupported effort and model default is also invalid", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: ["low", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
// No reasoningEffort default set
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: "medium",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options)
|
|
// "medium" is not in ["low", "high"], fallback is undefined → returns undefined
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should pass through effort that IS in the supportsReasoningEffort array", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: ["low", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: "high",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
// "high" IS in ["low", "high"], so it should be used directly
|
|
expect(result).toEqual({ thinkingLevel: "high", includeThoughts: true })
|
|
})
|
|
|
|
it("should skip validation when supportsReasoningEffort is boolean (not array)", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: "medium",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
// boolean supportsReasoningEffort should not trigger array validation
|
|
expect(result).toEqual({ thinkingLevel: "medium", includeThoughts: true })
|
|
})
|
|
|
|
it("should fall back to model default when settings has 'minimal' but model only supports ['low', 'high']", () => {
|
|
const geminiModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: ["low", "high"] as ModelInfo["supportsReasoningEffort"],
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const settings: ProviderSettings = {
|
|
apiProvider: "gemini",
|
|
reasoningEffort: "minimal",
|
|
}
|
|
|
|
const options: GetModelReasoningOptions = {
|
|
model: geminiModel,
|
|
reasoningBudget: undefined,
|
|
reasoningEffort: "minimal",
|
|
settings,
|
|
}
|
|
|
|
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
|
|
// "minimal" is not in ["low", "high"], falls back to "low"
|
|
expect(result).toEqual({ thinkingLevel: "low", includeThoughts: true })
|
|
})
|
|
})
|
|
|
|
describe("Integration scenarios", () => {
|
|
it("should handle model with requiredReasoningBudget across all providers", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithRequired,
|
|
}
|
|
|
|
const openRouterResult = getOpenRouterReasoning(options)
|
|
const anthropicResult = getAnthropicReasoning(options)
|
|
const openAiResult = getOpenAiReasoning(options)
|
|
|
|
expect(openRouterResult).toEqual({ max_tokens: 1000 })
|
|
expect(anthropicResult).toEqual({ type: "enabled", budget_tokens: 1000 })
|
|
expect(openAiResult).toBeUndefined()
|
|
})
|
|
|
|
it("should handle model with supportsReasoningEffort across all providers", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const settingsWithEffort: ProviderSettings = {
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithEffort,
|
|
reasoningEffort: "high" as const,
|
|
}
|
|
|
|
const openRouterResult = getOpenRouterReasoning(options)
|
|
const anthropicResult = getAnthropicReasoning(options)
|
|
const openAiResult = getOpenAiReasoning(options)
|
|
|
|
expect(openRouterResult).toEqual({ effort: "high" })
|
|
expect(anthropicResult).toBeUndefined()
|
|
expect(openAiResult).toEqual({ reasoning_effort: "high" })
|
|
})
|
|
|
|
it("should handle model with both reasoning capabilities - budget takes precedence", () => {
|
|
const hybridModel: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningBudget: true,
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const settingsWithBoth: ProviderSettings = {
|
|
enableReasoningEffort: true,
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: hybridModel,
|
|
settings: settingsWithBoth,
|
|
}
|
|
|
|
const openRouterResult = getOpenRouterReasoning(options)
|
|
const anthropicResult = getAnthropicReasoning(options)
|
|
const openAiResult = getOpenAiReasoning(options)
|
|
|
|
// Budget should take precedence for OpenRouter and Anthropic
|
|
expect(openRouterResult).toEqual({ max_tokens: 1000 })
|
|
expect(anthropicResult).toEqual({ type: "enabled", budget_tokens: 1000 })
|
|
// OpenAI should still use effort since it doesn't support budget
|
|
expect(openAiResult).toEqual({ reasoning_effort: "medium" })
|
|
})
|
|
|
|
it("should handle empty settings", () => {
|
|
const options = {
|
|
...baseOptions,
|
|
settings: {},
|
|
}
|
|
|
|
const openRouterResult = getOpenRouterReasoning(options)
|
|
const anthropicResult = getAnthropicReasoning(options)
|
|
const openAiResult = getOpenAiReasoning(options)
|
|
|
|
expect(openRouterResult).toBeUndefined()
|
|
expect(anthropicResult).toBeUndefined()
|
|
expect(openAiResult).toBeUndefined()
|
|
})
|
|
|
|
it("should handle undefined settings", () => {
|
|
const options = {
|
|
...baseOptions,
|
|
settings: undefined as any,
|
|
}
|
|
|
|
const openRouterResult = getOpenRouterReasoning(options)
|
|
const anthropicResult = getAnthropicReasoning(options)
|
|
const openAiResult = getOpenAiReasoning(options)
|
|
|
|
expect(openRouterResult).toBeUndefined()
|
|
expect(anthropicResult).toBeUndefined()
|
|
expect(openAiResult).toBeUndefined()
|
|
})
|
|
|
|
it("should handle model with reasoningEffort property", () => {
|
|
const modelWithEffort: ModelInfo = {
|
|
...baseModel,
|
|
reasoningEffort: "low",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithEffort,
|
|
reasoningEffort: "low" as const, // Override the baseOptions reasoningEffort
|
|
}
|
|
|
|
const openRouterResult = getOpenRouterReasoning(options)
|
|
const anthropicResult = getAnthropicReasoning(options)
|
|
const openAiResult = getOpenAiReasoning(options)
|
|
|
|
expect(openRouterResult).toEqual({ effort: "low" })
|
|
expect(anthropicResult).toBeUndefined()
|
|
expect(openAiResult).toEqual({ reasoning_effort: "low" })
|
|
})
|
|
})
|
|
|
|
describe("Type safety", () => {
|
|
it("should return correct types for OpenRouter reasoning params", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const options = { ...baseOptions, model: modelWithRequired }
|
|
const result: OpenRouterReasoningParams | undefined = getOpenRouterReasoning(options)
|
|
|
|
expect(result).toBeDefined()
|
|
if (result) {
|
|
expect(typeof result).toBe("object")
|
|
expect("max_tokens" in result || "effort" in result || "exclude" in result).toBe(true)
|
|
}
|
|
})
|
|
|
|
it("should return correct types for Anthropic reasoning params", () => {
|
|
const modelWithRequired: ModelInfo = {
|
|
...baseModel,
|
|
requiredReasoningBudget: true,
|
|
}
|
|
|
|
const options = { ...baseOptions, model: modelWithRequired }
|
|
const result: AnthropicReasoningParams | undefined = getAnthropicReasoning(options)
|
|
|
|
expect(result).toBeDefined()
|
|
if (result) {
|
|
expect(result).toHaveProperty("type", "enabled")
|
|
expect(result).toHaveProperty("budget_tokens")
|
|
}
|
|
})
|
|
|
|
it("should return correct types for OpenAI reasoning params", () => {
|
|
const modelWithEffort: ModelInfo = {
|
|
...baseModel,
|
|
reasoningEffort: "medium",
|
|
}
|
|
|
|
const options = { ...baseOptions, model: modelWithEffort }
|
|
const result: OpenAiReasoningParams | undefined = getOpenAiReasoning(options)
|
|
|
|
expect(result).toBeDefined()
|
|
if (result) {
|
|
expect(result).toHaveProperty("reasoning_effort")
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("getRooReasoning", () => {
|
|
it("should return undefined when model does not support reasoning effort", () => {
|
|
const options = { ...baseOptions }
|
|
const result = getRooReasoning(options)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should return enabled: false when enableReasoningEffort is explicitly false", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const settingsWithDisabled: ProviderSettings = {
|
|
enableReasoningEffort: false,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithDisabled,
|
|
}
|
|
|
|
const result = getRooReasoning(options)
|
|
expect(result).toEqual({ enabled: false })
|
|
})
|
|
|
|
it("should return enabled: true with effort when reasoningEffort is provided", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const settingsWithEffort: ProviderSettings = {
|
|
reasoningEffort: "high",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithEffort,
|
|
reasoningEffort: "high" as const,
|
|
}
|
|
|
|
const result = getRooReasoning(options)
|
|
expect(result).toEqual({ enabled: true, effort: "high" })
|
|
})
|
|
|
|
it("should return enabled: false when reasoningEffort is undefined (None selected)", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: {},
|
|
reasoningEffort: undefined,
|
|
}
|
|
|
|
const result = getRooReasoning(options)
|
|
expect(result).toEqual({ enabled: false })
|
|
})
|
|
|
|
it("should omit reasoning params for minimal effort", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const settingsWithMinimal: ProviderSettings = {
|
|
reasoningEffort: "minimal",
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithMinimal,
|
|
reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
|
|
}
|
|
|
|
const result = getRooReasoning(options)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("should handle all valid reasoning effort values", () => {
|
|
const efforts: Array<"low" | "medium" | "high"> = ["low", "medium", "high"]
|
|
|
|
efforts.forEach((effort) => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const settingsWithEffort: ProviderSettings = {
|
|
reasoningEffort: effort,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: settingsWithEffort,
|
|
reasoningEffort: effort,
|
|
}
|
|
|
|
const result = getRooReasoning(options)
|
|
expect(result).toEqual({ enabled: true, effort })
|
|
})
|
|
})
|
|
|
|
it("should return enabled: false when model supports reasoning but no effort is provided", () => {
|
|
const modelWithSupported: ModelInfo = {
|
|
...baseModel,
|
|
supportsReasoningEffort: true,
|
|
}
|
|
|
|
const options = {
|
|
...baseOptions,
|
|
model: modelWithSupported,
|
|
settings: {},
|
|
reasoningEffort: undefined,
|
|
}
|
|
|
|
const result = getRooReasoning(options)
|
|
expect(result).toEqual({ enabled: false })
|
|
})
|
|
})
|
|
})
|