feat(cli): expose reasoning effort through ACP (#8526)

* feat(cli): expose reasoning effort to ACP clients

* fix(cli): address ACP reasoning effort review

* fix(cli): address ACP reasoning effort review round 2

Co-authored-by: Qwen-Coder <[email protected]>

* fix(cli): migrate /effort dialog to applyReasoningEffort helper

Co-authored-by: Qwen-Coder <[email protected]>

* fix(acp): harden set_config_option routing and rejection messages (#8526)

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-08-11 10:32:33 +00:00
1 parent e8182a3b3f
commit e6a3272271
13 files changed
+427 -27

No files matched your search

+1
View File
@@ -8,6 +8,7 @@
- **Agent Client Protocol**: Full support for ACP enabling advanced IDE interactions
- **Symbol management**: #-mention files to add them to the conversation context
- **Conversation history**: Access to past conversations within the IDE
- **Reasoning effort**: Choose Default, Low, Medium, High, Extra high, or Max from the agent's session options; each provider maps or clamps the requested tier for the active model
- **Context usage**: See the current context-window occupancy while Qwen Code works
### Requirements
+69 -1
View File
@@ -487,7 +487,7 @@ function setupAcpTest(
}
});
it('supports session/set_config_option for mode and model', async () => {
it('supports session/set_config_option for mode, model, and reasoning effort', async () => {
const rig = new TestRig();
// Inject a deterministic openai provider model so `availableModels` always
// contains a settable openai entry. The previous version relied on the
@@ -534,9 +534,26 @@ function setupAcpTest(
models: {
availableModels: Array<{ modelId: string }>;
};
configOptions: Array<{
id: string;
category?: string;
currentValue: string;
options: Array<{ value: string; name: string }>;
}>;
};
expect(newSession.sessionId).toBeTruthy();
const initialReasoningOption = newSession.configOptions.find(
(opt) => opt.id === 'reasoning_effort',
);
expect(initialReasoningOption).toMatchObject({
category: 'thought_level',
currentValue: 'default',
});
expect(
initialReasoningOption?.options.map((option) => option.value),
).toEqual(['default', 'low', 'medium', 'high', 'xhigh', 'max']);
// Test: Set mode using set_config_option
const setModeResult = (await sendRequest('session/set_config_option', {
sessionId: newSession.sessionId,
@@ -599,6 +616,57 @@ function setupAcpTest(
);
expect(updatedModelOption).toBeDefined();
expect(updatedModelOption!.currentValue).toBe(openaiModel!.modelId);
expect(
setModelResult.configOptions.find(
(opt) => opt.id === 'reasoning_effort',
)?.currentValue,
).toBe('default');
const setReasoningResult = (await sendRequest(
'session/set_config_option',
{
sessionId: newSession.sessionId,
configId: 'reasoning_effort',
value: 'xhigh',
},
)) as {
configOptions: Array<{ id: string; currentValue: string }>;
};
expect(
setReasoningResult.configOptions.find(
(opt) => opt.id === 'reasoning_effort',
)?.currentValue,
).toBe('xhigh');
const resetReasoningResult = (await sendRequest(
'session/set_config_option',
{
sessionId: newSession.sessionId,
configId: 'reasoning_effort',
value: 'default',
},
)) as {
configOptions: Array<{ id: string; currentValue: string }>;
};
expect(
resetReasoningResult.configOptions.find(
(opt) => opt.id === 'reasoning_effort',
)?.currentValue,
).toBe('default');
await expect(
sendRequest('session/set_config_option', {
sessionId: newSession.sessionId,
configId: 'reasoning_effort',
value: 'ultra',
}),
).rejects.toMatchObject({
response: {
code: -32602,
message:
'Invalid params: Unknown reasoning effort: ultra. Choose one of: default, low, medium, high, xhigh, max',
},
});
} catch (e) {
if (stderr.length) {
console.error('Agent stderr:', stderr.join(''));
@@ -231,6 +231,17 @@ vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => ({
})),
APPROVAL_MODE_INFO: {},
APPROVAL_MODES: [],
applyReasoningEffort: (
config: {
setReasoningEffort(effort: string | undefined): void;
getReasoningEffort(): string | undefined;
},
effort: string | undefined,
) => {
config.setReasoningEffort(effort);
return config.getReasoningEffort() === effort;
},
REASONING_EFFORT_TIERS: ['low', 'medium', 'high', 'xhigh', 'max'],
ApprovalMode: { YOLO: 'yolo' },
isGatedMcpScope: (scope: unknown) =>
scope === 'project' || scope === 'workspace',
@@ -1871,6 +1882,7 @@ describe('QwenAgent MCP SSE/HTTP support', () => {
type AgentLike = {
initialize: (args: Record<string, unknown>) => Promise<unknown>;
newSession: (args: Record<string, unknown>) => Promise<unknown>;
setSessionConfigOption: (args: Record<string, unknown>) => Promise<unknown>;
beginManagedShutdown: () => {
configs: Config[];
writerShutdown: Promise<void>;
@@ -3339,6 +3351,8 @@ describe('QwenAgent MCP SSE/HTTP support', () => {
getAvailableModels: vi.fn().mockReturnValue([]),
getModes: vi.fn().mockReturnValue([]),
getApprovalMode: vi.fn().mockReturnValue('default'),
getReasoningEffort: vi.fn().mockReturnValue(undefined),
setReasoningEffort: vi.fn(),
getSessionId: vi.fn().mockReturnValue('test-session-id'),
getAuthType: vi.fn().mockReturnValue('api-key'),
getAllConfiguredModels: vi.fn().mockReturnValue([]),
@@ -6353,6 +6367,107 @@ describe('QwenAgent MCP SSE/HTTP support', () => {
await agentPromise;
});
it('exposes and applies the ACP reasoning effort selector', async () => {
const sessionId = 'reasoning-effort-session';
const innerConfig = await setupSessionMocks(sessionId);
let currentEffort: string | undefined;
innerConfig.getReasoningEffort = vi.fn(() => currentEffort);
innerConfig.setReasoningEffort = vi.fn((effort: string | undefined) => {
currentEffort = effort;
});
const { agent, agentPromise } = await bootAcpAgent();
try {
const session = (await agent.newSession({
cwd: '/tmp',
mcpServers: [],
})) as {
configOptions: Array<{
id: string;
category?: string;
currentValue: string;
options: Array<{
value: string;
name: string;
description: string;
}>;
}>;
};
expect(
session.configOptions.find(
(option) => option.id === 'reasoning_effort',
),
).toMatchObject({
category: 'thought_level',
currentValue: 'default',
options: [
{ value: 'default', name: 'Default' },
{ value: 'low', name: 'Low', description: expect.any(String) },
{ value: 'medium', name: 'Medium', description: expect.any(String) },
{ value: 'high', name: 'High', description: expect.any(String) },
{
value: 'xhigh',
name: 'Extra high',
description: expect.any(String),
},
{ value: 'max', name: 'Max', description: expect.any(String) },
],
});
const selected = (await agent.setSessionConfigOption({
sessionId,
configId: 'reasoning_effort',
value: 'xhigh',
})) as { configOptions: typeof session.configOptions };
expect(innerConfig.setReasoningEffort).toHaveBeenCalledWith('xhigh');
expect(
selected.configOptions.find(
(option) => option.id === 'reasoning_effort',
)?.currentValue,
).toBe('xhigh');
const reset = (await agent.setSessionConfigOption({
sessionId,
configId: 'reasoning_effort',
value: 'default',
})) as { configOptions: typeof session.configOptions };
expect(innerConfig.setReasoningEffort).toHaveBeenLastCalledWith(
undefined,
);
expect(
reset.configOptions.find((option) => option.id === 'reasoning_effort')
?.currentValue,
).toBe('default');
await expect(
agent.setSessionConfigOption({
sessionId,
configId: 'reasoning_effort',
value: 'ultra',
}),
).rejects.toThrow(
'Unknown reasoning effort: ultra. Choose one of: default, low, medium, high, xhigh, max',
);
expect(innerConfig.setReasoningEffort).toHaveBeenCalledTimes(2);
innerConfig.setReasoningEffort.mockImplementation(() => {});
await expect(
agent.setSessionConfigOption({
sessionId,
configId: 'reasoning_effort',
value: 'xhigh',
}),
).rejects.toThrow(
'Reasoning effort cannot be applied while thinking is disabled',
);
expect(innerConfig.setReasoningEffort).toHaveBeenCalledTimes(3);
} finally {
mockConnectionState.resolve();
await agentPromise;
}
});
it.each([
{
description: 'hide discontinued qwen-oauth for other auth types',
+54 -1
View File
@@ -92,6 +92,8 @@ import {
normalizeSnapshotPayload,
startEventLoopLagMonitor,
refreshMemoryInstruction,
applyReasoningEffort,
REASONING_EFFORT_TIERS,
extractDaemonTraceContext,
withDaemonSpan,
type AgentParams,
@@ -109,6 +111,7 @@ import {
type ProviderConfig,
type ProviderModelConfig,
type ProviderSetupInputs,
type ReasoningEffort,
type ResumedSessionData,
type SendSdkMcpMessage,
type SessionArtifactEventRecordPayload,
@@ -376,6 +379,14 @@ const POSIX_TMP_LOCAL_READ_ROOT = '/tmp';
const BTW_CHILD_TIMEOUT_MS = 55_000;
const MCP_OAUTH_START_TIMEOUT_MS = 30_000;
const SESSION_DRAIN_TIMEOUT_MS = 30_000;
const ACP_REASONING_EFFORT_DEFAULT = 'default';
const ACP_REASONING_EFFORT_NAMES: Record<ReasoningEffort, string> = {
low: 'Low',
medium: 'Medium',
high: 'High',
xhigh: 'Extra high',
max: 'Max',
};
// Must be less than WORKSPACE_MEMORY_REMEMBER_TIMEOUT_MS (300s) in bridge.ts.
const WORKSPACE_MEMORY_REMEMBER_CHILD_TIMEOUT_MS = 295_000;
@@ -5426,6 +5437,25 @@ class QwenAgent implements Agent {
);
break;
}
case 'reasoning_effort': {
const effort =
value === ACP_REASONING_EFFORT_DEFAULT
? undefined
: REASONING_EFFORT_TIERS.find((tier) => tier === value);
if (value !== ACP_REASONING_EFFORT_DEFAULT && effort === undefined) {
throw RequestError.invalidParams(
undefined,
`Unknown reasoning effort: ${value}. Choose one of: ${ACP_REASONING_EFFORT_DEFAULT}, ${REASONING_EFFORT_TIERS.join(', ')}`,
);
}
if (!applyReasoningEffort(session.getConfig(), effort)) {
throw RequestError.invalidParams(
undefined,
'Reasoning effort cannot be applied while thinking is disabled',
);
}
break;
}
default:
throw RequestError.invalidParams(
undefined,
@@ -12264,7 +12294,30 @@ class QwenAgent implements Agent {
options: configModelOptions,
};
return [modeConfigOption, modelConfigOption];
const reasoningEffortConfigOption: SessionConfigOption = {
id: 'reasoning_effort',
name: 'Reasoning effort',
description: 'How hard reasoning-capable models should think',
category: 'thought_level',
type: 'select' as const,
currentValue:
config.getReasoningEffort?.() ?? ACP_REASONING_EFFORT_DEFAULT,
options: [
{
value: ACP_REASONING_EFFORT_DEFAULT,
name: 'Default',
description: 'Use the model or provider default',
},
...REASONING_EFFORT_TIERS.map((effort) => ({
value: effort,
name: ACP_REASONING_EFFORT_NAMES[effort],
description:
'Providers map or clamp the requested tier for the active model',
})),
],
};
return [modeConfigOption, modelConfigOption, reasoningEffortConfigOption];
}
private buildSelectableModelOptions(config: Config) {
@@ -125,6 +125,17 @@ vi.mock('@qwen-code/qwen-code-core', () => ({
})),
APPROVAL_MODE_INFO: {},
APPROVAL_MODES: [],
applyReasoningEffort: (
config: {
setReasoningEffort(effort: string | undefined): void;
getReasoningEffort(): string | undefined;
},
effort: string | undefined,
) => {
config.setReasoningEffort(effort);
return config.getReasoningEffort() === effort;
},
REASONING_EFFORT_TIERS: ['low', 'medium', 'high', 'xhigh', 'max'],
DEFAULT_STOP_HOOK_BLOCK_CAP: 8,
DEFAULT_MAX_SUBAGENT_DEPTH: 5,
DEFAULT_MAX_TOOL_CALLS_PER_TURN: 100,
@@ -28,6 +28,7 @@ import {
createDebugLogger,
MCPServerConfig,
AuthProviderType,
applyReasoningEffort,
normalizeReasoningEffort,
loadUsageDashboard,
type MCPOAuthConfig,
@@ -176,16 +177,14 @@ export class SystemController extends BaseController {
const normalized = normalizeReasoningEffort(payload.effort);
if (normalized) {
try {
this.context.config.setReasoningEffort(normalized);
if (this.context.config.getReasoningEffort() !== normalized) {
debugLogger.warn(
`[SystemController] Effort '${normalized}' was not applied (thinking may be disabled)`,
);
} else {
if (applyReasoningEffort(this.context.config, normalized)) {
debugLogger.info(
`[SystemController] Set reasoning effort to: ${normalized}`,
);
} else {
debugLogger.warn(
`[SystemController] Effort '${normalized}' was not applied (thinking may be disabled)`,
);
}
} catch (error) {
debugLogger.error(
@@ -533,9 +532,7 @@ export class SystemController extends BaseController {
}
try {
this.context.config.setReasoningEffort(normalized);
const applied = this.context.config.getReasoningEffort() === normalized;
const applied = applyReasoningEffort(this.context.config, normalized);
debugLogger.info(
`[SystemController] Reasoning effort set to: ${normalized} (applied: ${applied})`,
+26 -5
View File
@@ -406,6 +406,14 @@ const DEFAULT_FILE_GLOB_MAX_RESULTS = 5000;
const MAX_FILE_GLOB_MAX_RESULTS = 50_000;
const MAX_FILE_LINE_LIMIT = 2000;
/**
* Config-option ids this HTTP transport's `session/set_config_option` can
* route. Shared by `configOptionsFor`'s advertisement filter and the
* setter's rejection message so the advertised set and the set reported as
* supported cannot drift apart.
*/
const HTTP_ACP_CONFIG_OPTION_IDS: readonly string[] = ['model', 'mode'];
class AcpParamError extends Error {}
class InvalidRequestedSessionIdError extends Error {}
@@ -1145,9 +1153,14 @@ export class AcpDispatcher {
}
/**
* The session's ACP-shaped config options (model/mode/…), read from the
* child's own session state. Returned in `session/new` and as the result
* of `session/set_config_option`. Best-effort — `undefined` on error.
* The session's ACP-shaped config options supported by this HTTP transport
* (currently model and mode), read from the child's own session state.
* Every response built from this helper (`session/new`,
* `session/load`/`session/resume`, `session/fork`, and the
* `session/set_config_option` result) is gated to the ids the transport
* can route. Raw-state surfaces (context status, REST load/resume state)
* intentionally carry the child's full unfiltered set — they report session
* state. Best-effort — `undefined` on error.
*/
private async configOptionsFor(
sessionId: string,
@@ -1158,7 +1171,13 @@ export class AcpDispatcher {
state?: { configOptions?: unknown };
};
const co = ctx?.state?.configOptions;
return Array.isArray(co) ? co : undefined;
return Array.isArray(co)
? co.filter(
(option) =>
isObject(option) &&
HTTP_ACP_CONFIG_OPTION_IDS.includes(option['id'] as string),
)
: undefined;
} catch (err) {
writeStderrLine(
`qwen serve: /acp configOptionsFor(${logSafe(sessionId)}) failed: ${logSafe(errMsg(err))}`,
@@ -2522,6 +2541,8 @@ export class AcpDispatcher {
ctx,
);
} else {
// Ids advertised by raw-state surfaces but unroutable here
// (e.g. reasoning_effort) must fail loud, not fall into mode.
if (id !== undefined) {
this.replySession(
conn,
@@ -2531,7 +2552,7 @@ export class AcpDispatcher {
error(
id,
RPC.INVALID_PARAMS,
`Unknown configId: ${configId}`,
`ConfigId not supported by this transport: ${configId} (supported: ${HTTP_ACP_CONFIG_OPTION_IDS.join(', ')})`,
),
);
}
@@ -339,6 +339,22 @@ class FakeBridge {
currentValue: 'qwen-max',
options: [],
},
{
id: 'reasoning_effort',
name: 'Reasoning effort',
category: 'thought_level',
type: 'select',
currentValue: 'default',
options: [],
},
{
id: 'mode',
name: 'Mode',
category: 'mode',
type: 'select',
currentValue: 'default',
options: [],
},
],
},
};
@@ -1318,10 +1334,17 @@ describe('ACP Streamable HTTP transport (over the wire)', () => {
expect(ack.status).toBe(202);
const [frame] = (await got) as Array<{
id: number;
result: { sessionId: string };
result: {
sessionId: string;
configOptions: Array<{ id: string }>;
};
}>;
expect(frame.id).toBe(2);
expect(frame.result.sessionId).toBe('sess-1');
expect(frame.result.configOptions.map((option) => option.id)).toEqual([
'model',
'mode',
]);
});
it('session/new rejects the daemon-owned Live Voice source namespace', async () => {
@@ -4813,6 +4836,56 @@ describe('ACP Streamable HTTP transport (over the wire)', () => {
expect(bridge.lastApprovalMode).toBeUndefined();
});
it('session/set_config_option rejects ids outside the routable set', async () => {
const connId = await initialize();
await newSession(connId);
const sessStream = await openStream(connId, 'sess-1');
const got = takeFrames(sessStream, 1);
await new Promise((r) => setTimeout(r, 50));
await post(connId, {
jsonrpc: '2.0',
id: 44,
method: 'session/set_config_option',
params: {
sessionId: 'sess-1',
configId: 'reasoning_effort',
value: 'high',
},
});
const [frame] = (await got) as Array<{
id: number;
error: { code: number; message: string };
}>;
expect(frame.error.code).toBe(-32602);
expect(frame.error.message).toContain(
'ConfigId not supported by this transport: reasoning_effort',
);
expect(frame.error.message).toContain('(supported: model, mode)');
expect(bridge.lastSetModel).toBeUndefined();
expect(bridge.lastApprovalMode).toBeUndefined();
});
it('session/set_config_option without an id writes no response for an unroutable configId', async () => {
const connId = await initialize();
await newSession(connId);
const sessStream = await openStream(connId, 'sess-1');
await new Promise((r) => setTimeout(r, 50));
const ack = await post(connId, {
jsonrpc: '2.0',
method: 'session/set_config_option',
params: {
sessionId: 'sess-1',
configId: 'reasoning_effort',
value: 'high',
},
});
expect(ack.status).toBe(202);
const frames = await takeFrames(sessStream, 1, 300);
expect(frames).toHaveLength(0);
expect(bridge.lastSetModel).toBeUndefined();
expect(bridge.lastApprovalMode).toBeUndefined();
});
it('session/new always uses thread scope (ACP standard compliance)', async () => {
// ACP standard: session/new MUST create a new isolated session.
// sessionScope param is ignored; bridge always gets 'thread'.
@@ -14,6 +14,7 @@ import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
import { getPersistScopeForModelSelection } from '../../config/modelProvidersScope.js';
import {
applyReasoningEffort,
normalizeReasoningEffort,
REASONING_EFFORT_TIERS,
} from '@qwen-code/qwen-code-core';
@@ -97,7 +98,7 @@ export const effortCommand: SlashCommand = {
// Apply at runtime (takes effect next turn) and persist for future sessions.
// Provider adapters clamp the tier to what the active model supports.
config.setReasoningEffort(tier);
const applied = applyReasoningEffort(config, tier);
settings.setValue(
getPersistScopeForModelSelection(settings),
'model.reasoningEffort',
@@ -108,7 +109,7 @@ export const effortCommand: SlashCommand = {
// (`reasoning: false`), so effort cannot silently re-enable it. The tier is
// still persisted for future sessions, but report that it won't take effect
// yet instead of a misleading success message.
if (config.getReasoningEffort() !== tier) {
if (!applied) {
return {
type: 'message',
messageType: 'info',
@@ -12,14 +12,16 @@ import { useEffortCommand } from './use-effort-command.js';
describe('useEffortCommand', () => {
let setReasoningEffort: ReturnType<typeof vi.fn>;
let getReasoningEffort: ReturnType<typeof vi.fn>;
let setValue: ReturnType<typeof vi.fn>;
let config: Config;
let settings: LoadedSettings;
beforeEach(() => {
setReasoningEffort = vi.fn();
getReasoningEffort = vi.fn();
setValue = vi.fn();
config = { setReasoningEffort } as unknown as Config;
config = { setReasoningEffort, getReasoningEffort } as unknown as Config;
settings = {
setValue,
isTrusted: true,
@@ -6,6 +6,7 @@
import { useState, useCallback } from 'react';
import type { Config, ReasoningEffort } from '@qwen-code/qwen-code-core';
import { applyReasoningEffort } from '@qwen-code/qwen-code-core';
import type { LoadedSettings } from '../../config/settings.js';
import { getPersistScopeForModelSelection } from '../../config/modelProvidersScope.js';
import { MessageType, type HistoryItemWithoutId } from '../types.js';
@@ -37,20 +38,19 @@ export const useEffortCommand = (
}
// Apply at runtime (next turn) and persist for future sessions; provider
// adapters clamp the tier to what the active model supports.
config.setReasoningEffort(effort);
const applied = applyReasoningEffort(config, effort);
loadedSettings.setValue(
getPersistScopeForModelSelection(loadedSettings),
'model.reasoningEffort',
effort,
);
// Mirror the slash-command path's read-back so the dialog reports the
// outcome in-chat instead of silently closing (the status line is the
// only other signal). `setReasoningEffort` is a no-op when thinking is
// Report the outcome in-chat instead of silently closing (the status
// line is the only other signal). The setter no-ops when thinking is
// explicitly disabled (`reasoning: false`): the tier is still persisted
// for future sessions, but say it won't take effect until thinking is
// re-enabled; otherwise confirm the requested tier.
// re-enabled.
if (addItem) {
if (config.getReasoningEffort() !== effort) {
if (!applied) {
addItem(
{
type: MessageType.INFO,
@@ -5,8 +5,10 @@
*/
import { describe, it, expect } from 'vitest';
import type { Config } from '../config/config.js';
import {
REASONING_EFFORT_TIERS,
applyReasoningEffort,
clampReasoningEffort,
normalizeReasoningEffort,
type ReasoningEffort,
@@ -86,3 +88,41 @@ describe('clampReasoningEffort', () => {
expect(clampReasoningEffort('xhigh', [])).toBe('xhigh');
});
});
describe('applyReasoningEffort', () => {
function makeConfig(thinkingDisabled = false) {
let stored: ReasoningEffort | undefined;
return {
setReasoningEffort(effort: ReasoningEffort | undefined) {
// Mirrors Config: a no-op when thinking is explicitly disabled.
if (thinkingDisabled) return;
stored = effort;
},
getReasoningEffort() {
return stored;
},
} as unknown as Config;
}
it('applies the tier and reports true when the config accepts it', () => {
const config = makeConfig();
expect(applyReasoningEffort(config, 'high')).toBe(true);
expect(config.getReasoningEffort()).toBe('high');
});
it('reports false when setReasoningEffort no-ops (thinking disabled)', () => {
const config = makeConfig(true);
expect(applyReasoningEffort(config, 'high')).toBe(false);
expect(config.getReasoningEffort()).toBeUndefined();
});
it('clearing the override always reports true', () => {
const active = makeConfig();
applyReasoningEffort(active, 'max');
expect(applyReasoningEffort(active, undefined)).toBe(true);
expect(active.getReasoningEffort()).toBeUndefined();
const disabled = makeConfig(true);
expect(applyReasoningEffort(disabled, undefined)).toBe(true);
});
});
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type { Config } from '../config/config.js';
/**
* Unified reasoning-effort ladder exposed to users (e.g. via `/effort`).
*
@@ -106,3 +108,19 @@ export function clampReasoningEffort(
// Nothing at or above the request: fall back to the strongest available.
return ranked[ranked.length - 1]!;
}
/**
* Set `effort` and read it back to confirm the config actually accepted it.
* `Config.setReasoningEffort` is a documented no-op when thinking is
* explicitly disabled (`reasoning: false`); returns false when the requested
* tier did not land so each surface can report the discard its own way
* instead of reporting success. Clearing the override (`undefined`) always
* reports true.
*/
export function applyReasoningEffort(
config: Config,
effort: ReasoningEffort | undefined,
): boolean {
config.setReasoningEffort(effort);
return config.getReasoningEffort() === effort;
}