Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
retiredProviderIdentifiers,
type ProviderSettings,
type ModelInfo,
type ReasoningEffortExtended,
} from "@roo-code/types"

import { getRouterRemovalMessage } from "../core/config/routerRemoval"
Expand Down Expand Up @@ -115,6 +116,14 @@ export interface ApiHandlerCreateMessageMetadata {
* when the user clicks stop, preventing wasted API tokens/compute on the provider side.
*/
abortSignal?: AbortSignal
/**
* Per-request thinking effort override (DTE series 2/5).
* When defined, takes precedence over the settings-derived `reasoningEffort`
* wherever the effective effort is resolved (see `resolveEffectiveReasoningEffort`).
* Task-scoped and transient: it applies to this request only (the next request
* after being set — no mid-stream effect) and is never persisted to settings.
*/
reasoningEffort?: ReasoningEffortExtended
}

export interface ApiHandler {
Expand Down Expand Up @@ -236,7 +245,7 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
case providerIdentifiers.poe:
return new PoeHandler(options)
case providerIdentifiers.geminiCli:
// Intentionally falls through to the Anthropic handler pending a dedicated Gemini CLI handler implementation.
// Intentionally falls through to the Anthropic handler pending a dedicated Gemini CLI handler implementation.
default:
return new AnthropicHandler(options)
}
Expand Down
58 changes: 58 additions & 0 deletions src/api/transform/__tests__/dte-effective-reasoning-effort.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// npx vitest run src/api/transform/__tests__/dte-effective-reasoning-effort.spec.ts

import { ADAPTIVE_OUTPUT_CONFIG_EFFORTS, resolveEffectiveReasoningEffort } from "../reasoning"

describe("DTE series 2/5 — resolveEffectiveReasoningEffort", () => {
const settingsEffort = "high"
const modelDefault = "medium"

it("returns the per-request override when present (strongest precedence)", () => {
expect(
resolveEffectiveReasoningEffort({
override: "xhigh",
settingsReasoningEffort: settingsEffort,
modelDefaultEffort: modelDefault,
}),
).toBe("xhigh")
})

it("lets the override win even when it is out-of-range for the adaptive envelope", () => {
// "minimal" is a valid override value but outside the adaptive envelope set;
// resolution still returns it — envelope gating is the caller's concern.
expect(
resolveEffectiveReasoningEffort({
override: "minimal",
settingsReasoningEffort: settingsEffort,
modelDefaultEffort: modelDefault,
}),
).toBe("minimal")
})

it("falls back to the settings value when no override is present", () => {
expect(
resolveEffectiveReasoningEffort({ settingsReasoningEffort: "low", modelDefaultEffort: modelDefault }),
).toBe("low")
})

it("preserves the settings 'disable' sentinel when no override is present", () => {
expect(
resolveEffectiveReasoningEffort({ settingsReasoningEffort: "disable", modelDefaultEffort: modelDefault }),
).toBe("disable")
})

it("an explicit override wins over a settings 'disable' sentinel", () => {
expect(resolveEffectiveReasoningEffort({ override: "low", settingsReasoningEffort: "disable" })).toBe("low")
})

it("falls back to the model default when neither override nor settings is set", () => {
expect(resolveEffectiveReasoningEffort({ modelDefaultEffort: "low" })).toBe("low")
})

it("returns undefined when nothing is set", () => {
expect(resolveEffectiveReasoningEffort({})).toBeUndefined()
})

it("exposes exactly the in-range adaptive envelope efforts", () => {
expect([...ADAPTIVE_OUTPUT_CONFIG_EFFORTS]).toEqual(["low", "medium", "high", "xhigh", "max"])
})
})
45 changes: 45 additions & 0 deletions src/api/transform/reasoning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,51 @@ export type AnthropicProviderReasoningParams = AnthropicReasoningParams | { type

export type OpenAiReasoningParams = { reasoning_effort: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"] }

/**
* DTE series 2/5 — effort levels accepted by the Claude 4.7+ adaptive-thinking
* `output_config.effort` envelope. Efforts outside this set (e.g. "none",
* "minimal", "disable") omit the envelope so the API applies its own default.
*/
export const ADAPTIVE_OUTPUT_CONFIG_EFFORTS: readonly ReasoningEffortExtended[] = [
"low",
"medium",
"high",
"xhigh",
"max",
]

/**
* DTE series 2/5 — resolves the effective thinking effort for a single request.
*
* Resolution order (strongest first):
* 1. `override` — the per-request task-local effort
* (`ApiHandlerCreateMessageMetadata.reasoningEffort`),
* 2. `settingsReasoningEffort` — the settings-derived value,
* 3. `modelDefaultEffort` — the model's default effort.
*
* This is the single shared resolution point for the per-request override:
* providers that resolve the effective effort through it inherit the override
* without duplicating precedence logic. The override is transient (next request
* only) and never persisted to settings.
*/
export const resolveEffectiveReasoningEffort = ({
override,
settingsReasoningEffort,
modelDefaultEffort,
}: {
override?: ReasoningEffortExtended
settingsReasoningEffort?: ReasoningEffortExtended | "disable"
modelDefaultEffort?: ReasoningEffortExtended
}): ReasoningEffortExtended | "disable" | undefined => {
if (override !== undefined) {
return override
}
if (settingsReasoningEffort !== undefined) {
return settingsReasoningEffort
}
return modelDefaultEffort
}

// Valid Gemini thinking levels for effort-based reasoning
const GEMINI_THINKING_LEVELS = ["minimal", "low", "medium", "high"] as const

Expand Down
Loading