mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
* feat(subscription): add development login, models and web search * feat(subscription): refine account flows and host integration Align desktop and VS Code subscription UI, add account logout and confirmed plan display, and map subscription thinking parameters. Remove simulation code and obsolete native authentication paths, consolidate design documentation, and cover cancellation and recovery boundaries. * fix(subscription): preserve request errors and remove unused compatibility * fix(subscription): refine command visibility and client logout flows * fix(subscription): simplify account actions and desktop sign-in feedback * docs(subscription): clarify sign-in and provider guidance * feat(subscription): support switching accounts across clients * fix(subscription): align account actions and open plans on demand * fix(subscription): restore model selection after logout Filter subscription-only commands consistently across terminal renderers, preserve model catalog error classification, and display the subscription authentication name consistently. * fix(subscription): unify status authentication labels across clients * fix(subscription): preserve provider connections across client logout Restore configured providers after subscription logout across CLI, Desktop, and VS Code. Keep unrelated connections and conversation history, synchronize model state silently, and return to an unconfigured chat when no provider remains. * fix(subscription): correct native client logout state * feat(subscription): enable sign-in without development mode * refactor(ui): 优化会员登录成功提示和界面交互体验 - 移除订阅欢迎提示相关状态和行为,简化 UI 状态管理 - 替换登录成功提示为四行摘要,包含邮箱、套餐、模型及常用命令提示 - 更新国际化文本,支持新的登录成功摘要内容 - 取消订阅模式下输入框下方的命令提示行,改为在登录成功摘要中展示 - 移除多余的测试用例,新增登录成功消息的单元测试校验 - 优化 AppHeader 组件,不再显示订阅启动重复提示 - 调整 Composer 组件逻辑,删除订阅提示相关副作用及渲染条件 - 修改历史消息显示组件以适应新的成功提示格式 * fix(subscription): enable Flash reasoning controls in shared UI * fix(subscription): prioritize authorized models and simplify login dialogs * fix(subscription): preserve gateway errors and request IDs * fix(subscription): synchronize account state across clients Preserve model selections after sign-out, synchronize account commands and model catalogs, and retain explicit browser sign-in flows across CLI, Web Shell, and VS Code. Cancel subscription background agents on logout and normalize expired refresh credentials without SDK error wrapping. * fix(subscription): reset default model on explicit sign-in Choose the default served model when explicitly signing in to Qwen Subscription, preferring Max over a previously saved Flash selection. Preserve normal startup behavior and other provider flows, and cover CLI and workspace activation with regression tests. * fix(vscode): refresh models after successful sign-in * fix(web-shell): hide account actions during automatic sign-in * fix(web-shell): align model details and auth keyboard navigation * docs(subscription): revert previous guide changes * fix(core): reuse subscription catalog for same-login model switches * docs(subscription): document Qwen membership sign-in * fix(subscription): unify quota notices and preserve provider switching Carry structured subscription quota details through chat and search errors for localized CLI, Desktop, and VS Code display. Keep other providers available when subscription discovery fails and refine sidebar account actions. * fix(web-shell): align Chinese subscription entry title with CLI * fix(subscription): open plans for saved non-member accounts * fix(web-shell): show subscription commands after desktop sign-in * fix(subscription): reauthorize explicit account sign-ins * fix(auth): add model switching hint to subscription success * docs(auth): list Qwen OAuth as the first option * docs(models): simplify subscription sign-in guidance * docs(models): remove legacy OAuth from supported auth types * docs(subscription): link membership references and remove obsolete allowance note * fix(subscription): preserve session until reauthorization succeeds * fix(subscription): use pre for browser authorization and plans * fix(subscription): use production endpoints * test(vscode): fix editor message test formatting * fix(subscription): preserve unauthenticated session startup * fix(cli): classify subscription auth during ACP preflight * fix(subscription): scope activation to the current project --------- Co-authored-by: luoyun <[email protected]>
772 lines
23 KiB
TypeScript
772 lines
23 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {
|
|
parseModelReasoningCapabilities,
|
|
REASONING_EFFORT_TIERS,
|
|
getGptReasoningCapabilities,
|
|
isReasoningEffortPlaceholder,
|
|
isOpenRouterHostname,
|
|
clampReasoningEffort,
|
|
reasoningEffortsForCapability,
|
|
type Config,
|
|
type ContentGeneratorConfig,
|
|
type ReasoningEffort,
|
|
} from '@qwen-code/qwen-code-core';
|
|
import {
|
|
resolveReasoningForModel,
|
|
getEffectiveReasoning,
|
|
type ReasoningProfile,
|
|
type ResolvedReasoning,
|
|
} from '@qwen-code/qwen-code-core/core/reasoning-overrides.js';
|
|
import {
|
|
getBuiltinDeepSeekReasoning,
|
|
getDeepSeekReasoningState,
|
|
} from '@qwen-code/qwen-code-core/core/deepseek-reasoning.js';
|
|
import { getRequestModel } from '@qwen-code/qwen-code-core/core/request-model.js';
|
|
import { isTieredEffortQwenPrefix } from '@qwen-code/qwen-code-core/core/modalityDefaults.js';
|
|
import type { SessionConfigOption } from '@agentclientprotocol/sdk';
|
|
import type { LoadedSettings } from '../config/settings.js';
|
|
import { ACP_ROUTE_ID_PREFIX } from '../utils/acpModelUtils.js';
|
|
|
|
export type ModelReasoningConfiguration = (
|
|
| {
|
|
readonly thinking: true;
|
|
readonly toggleOnly: true;
|
|
readonly canDisable?: false;
|
|
}
|
|
| {
|
|
readonly thinking: true;
|
|
readonly toggleOnly?: false;
|
|
readonly efforts: readonly ReasoningEffort[];
|
|
readonly defaultEffort?: ReasoningEffort;
|
|
readonly defaultEnabled?: boolean;
|
|
readonly canDisable?: false;
|
|
}
|
|
) & { readonly profile?: ReasoningProfile };
|
|
|
|
const MODEL_CONFIGURATIONS: Readonly<
|
|
Record<string, { readonly reasoning?: ModelReasoningConfiguration }>
|
|
> = {
|
|
'qwen3.5-plus': {
|
|
reasoning: {
|
|
thinking: true,
|
|
toggleOnly: true,
|
|
},
|
|
},
|
|
'qwen3.6-plus': {
|
|
reasoning: {
|
|
thinking: true,
|
|
toggleOnly: true,
|
|
},
|
|
},
|
|
'qwen3.6-flash': {
|
|
reasoning: {
|
|
thinking: true,
|
|
toggleOnly: true,
|
|
},
|
|
},
|
|
'qwen3.7-plus': {
|
|
reasoning: {
|
|
thinking: true,
|
|
toggleOnly: true,
|
|
},
|
|
},
|
|
'qwen3.7-max': {
|
|
reasoning: {
|
|
thinking: true,
|
|
toggleOnly: true,
|
|
},
|
|
},
|
|
'qwen3.8-max': {
|
|
reasoning: {
|
|
thinking: true,
|
|
efforts: ['low', 'medium', 'xhigh'],
|
|
defaultEffort: 'xhigh',
|
|
},
|
|
},
|
|
'qwen3.8-flash': {
|
|
reasoning: {
|
|
thinking: true,
|
|
efforts: ['low', 'medium', 'xhigh'],
|
|
defaultEffort: 'xhigh',
|
|
},
|
|
},
|
|
};
|
|
|
|
export const REASONING_EFFORT_DEFAULT = 'default';
|
|
export const REASONING_EFFORT_NONE = 'none';
|
|
|
|
export type ReasoningSelection =
|
|
| ReasoningEffort
|
|
| typeof REASONING_EFFORT_NONE
|
|
| typeof REASONING_EFFORT_DEFAULT;
|
|
|
|
export const PERSIST_REASONING_SELECTION_META_KEY =
|
|
'qwenCode/persistReasoningSelection';
|
|
export const REASONING_SELECTION_PERSISTED_META_KEY =
|
|
'qwenCode/reasoningSelectionPersisted';
|
|
|
|
export const REASONING_EFFORT_NAMES: Record<ReasoningEffort, string> = {
|
|
low: 'Low',
|
|
medium: 'Medium',
|
|
high: 'High',
|
|
xhigh: 'Extra high',
|
|
max: 'Max',
|
|
};
|
|
|
|
export type ModelReasoningConfigState = {
|
|
enabled?: boolean;
|
|
effort?: ReasoningEffort;
|
|
thinkingMandatory?: boolean;
|
|
enableValue?: typeof REASONING_EFFORT_DEFAULT;
|
|
canEnable?: false;
|
|
};
|
|
|
|
export function getDefaultReasoningConfig(
|
|
config: Config,
|
|
settings: LoadedSettings,
|
|
): ContentGeneratorConfig['reasoning'] {
|
|
// Runtime snapshots already include the persisted selection, not its defaults.
|
|
const authType = config.getAuthType?.();
|
|
const model =
|
|
authType && !config.getActiveRuntimeModelSnapshot?.()
|
|
? config.getResolvedModelConfig?.(
|
|
authType,
|
|
config.getModel(),
|
|
config.getCurrentModelRegistryBaseUrl?.() ?? undefined,
|
|
)
|
|
: undefined;
|
|
if (model) return model.generationConfig.reasoning;
|
|
return (
|
|
settings.merged.model?.generationConfig as
|
|
| Partial<ContentGeneratorConfig>
|
|
| undefined
|
|
)?.reasoning;
|
|
}
|
|
|
|
export function getGptReasoningOverrideState(
|
|
generation: ContentGeneratorConfig,
|
|
configuredReasoning?: ModelReasoningConfiguration,
|
|
):
|
|
| {
|
|
enabled: boolean;
|
|
effort?: ReasoningEffort;
|
|
useDefaultEffort: boolean;
|
|
blocksTierChange: true;
|
|
}
|
|
| undefined {
|
|
const model = getRequestModel(generation);
|
|
const capabilities = getGptReasoningCapabilities(model);
|
|
const profile = configuredReasoning?.profile;
|
|
if ((!capabilities && !profile) || generation.reasoning === false) {
|
|
return undefined;
|
|
}
|
|
if (
|
|
profile &&
|
|
profile !== 'openai-effort' &&
|
|
profile !== 'openai-reasoning'
|
|
) {
|
|
return undefined;
|
|
}
|
|
const reasoning = getModelConfiguration(
|
|
model,
|
|
configuredReasoning,
|
|
)?.reasoning;
|
|
const defaultEnabled =
|
|
!reasoning || reasoning.toggleOnly || reasoning.defaultEnabled !== false;
|
|
const efforts =
|
|
reasoning && !reasoning.toggleOnly
|
|
? reasoning.efforts
|
|
: (capabilities?.efforts ?? REASONING_EFFORT_TIERS);
|
|
const raw = { ...generation.samplingParams, ...generation.extra_body };
|
|
const flat = raw['reasoning_effort'];
|
|
const nested = raw['reasoning'] as
|
|
| { enabled?: boolean; effort?: unknown; max_tokens?: number }
|
|
| false
|
|
| null
|
|
| undefined;
|
|
const openRouter =
|
|
profile === 'openai-reasoning' || isOpenRouterHostname(generation);
|
|
const removedFlatNone =
|
|
((model === generation.model && generation.thinkingMandatory === true) ||
|
|
reasoning?.canDisable === false ||
|
|
capabilities?.thinkingMandatory) &&
|
|
flat === REASONING_EFFORT_NONE;
|
|
if (!openRouter && !isReasoningEffortPlaceholder(flat) && !removedFlatNone) {
|
|
const effort = efforts.find((tier) => tier === flat);
|
|
return {
|
|
enabled: flat !== REASONING_EFFORT_NONE,
|
|
...(effort ? { effort } : {}),
|
|
useDefaultEffort: effort === undefined,
|
|
blocksTierChange: true,
|
|
};
|
|
}
|
|
if (nested === undefined) {
|
|
const suppressedConfiguredTier = openRouter
|
|
? !parseModelReasoningCapabilities(configuredReasoning) &&
|
|
!isReasoningEffortPlaceholder(
|
|
generation.samplingParams?.['reasoning_effort'],
|
|
) &&
|
|
!isReasoningEffortPlaceholder(flat)
|
|
: removedFlatNone;
|
|
const effort = removedFlatNone
|
|
? undefined
|
|
: efforts.find((tier) => tier === flat);
|
|
return suppressedConfiguredTier
|
|
? {
|
|
enabled: removedFlatNone
|
|
? defaultEnabled
|
|
: flat !== REASONING_EFFORT_NONE,
|
|
...(effort ? { effort } : {}),
|
|
useDefaultEffort: effort === undefined,
|
|
blocksTierChange: true,
|
|
}
|
|
: undefined;
|
|
}
|
|
if (!openRouter || nested === null) {
|
|
return {
|
|
enabled: defaultEnabled,
|
|
useDefaultEffort: true,
|
|
blocksTierChange: true,
|
|
};
|
|
}
|
|
const effort =
|
|
nested === false
|
|
? undefined
|
|
: efforts.find((tier) => tier === nested.effort);
|
|
const enabled =
|
|
nested === false ||
|
|
nested.enabled === false ||
|
|
nested.effort === REASONING_EFFORT_NONE
|
|
? false
|
|
: nested.enabled === true ||
|
|
(typeof nested.effort === 'string' && nested.effort.length > 0) ||
|
|
(nested.max_tokens ?? 0) > 0
|
|
? true
|
|
: defaultEnabled;
|
|
return {
|
|
enabled,
|
|
...(effort ? { effort } : {}),
|
|
useDefaultEffort: effort === undefined,
|
|
blocksTierChange: true,
|
|
};
|
|
}
|
|
|
|
export function resolvePersistedReasoningConfigState(
|
|
modelId: string | undefined,
|
|
value: unknown,
|
|
thinkingMandatory = false,
|
|
reasoning?: ModelReasoningConfiguration,
|
|
): ModelReasoningConfigState {
|
|
const gptReasoning = getGptReasoningCapabilities(modelId);
|
|
thinkingMandatory ||=
|
|
getModelConfiguration(modelId, reasoning)?.reasoning?.canDisable === false;
|
|
let selection = parseReasoningSelection(value);
|
|
if (
|
|
((reasoning?.profile && !reasoning.toggleOnly) ||
|
|
(gptReasoning && !parseModelReasoningCapabilities(reasoning))) &&
|
|
selection &&
|
|
selection !== REASONING_EFFORT_NONE &&
|
|
selection !== REASONING_EFFORT_DEFAULT
|
|
) {
|
|
selection = clampReasoningEffort(
|
|
selection,
|
|
reasoning && !reasoning.toggleOnly
|
|
? reasoning.efforts
|
|
: gptReasoning!.efforts,
|
|
);
|
|
}
|
|
if (
|
|
!selection ||
|
|
selection === REASONING_EFFORT_DEFAULT ||
|
|
!isReasoningSelectionSupported(
|
|
modelId,
|
|
selection,
|
|
thinkingMandatory,
|
|
reasoning,
|
|
)
|
|
) {
|
|
return { thinkingMandatory };
|
|
}
|
|
return selection === REASONING_EFFORT_NONE
|
|
? { enabled: false, thinkingMandatory }
|
|
: { enabled: true, effort: selection, thinkingMandatory };
|
|
}
|
|
|
|
export function getModelConfiguration(
|
|
modelId: string | undefined,
|
|
reasoning?: ModelReasoningConfiguration,
|
|
):
|
|
| {
|
|
readonly reasoning?: ModelReasoningConfiguration;
|
|
}
|
|
| undefined {
|
|
const configured = parseModelReasoningCapabilities(reasoning);
|
|
const gpt = getGptReasoningCapabilities(modelId);
|
|
return configured
|
|
? {
|
|
reasoning:
|
|
!reasoning?.profile && gpt?.thinkingMandatory
|
|
? { ...configured, canDisable: false }
|
|
: configured,
|
|
}
|
|
: gpt
|
|
? {
|
|
reasoning: {
|
|
thinking: true,
|
|
efforts: gpt.efforts,
|
|
defaultEffort: gpt.defaultEffort,
|
|
defaultEnabled: gpt.defaultEnabled,
|
|
...(gpt.thinkingMandatory ? { canDisable: false } : {}),
|
|
},
|
|
}
|
|
: modelId
|
|
? (MODEL_CONFIGURATIONS[modelId] ??
|
|
(isTieredEffortQwenPrefix(modelId)
|
|
? MODEL_CONFIGURATIONS['qwen3.8-max']
|
|
: undefined))
|
|
: undefined;
|
|
}
|
|
|
|
/**
|
|
* Routed (opaque) selectors keep their effort controls when the manifest
|
|
* carries a tiered contract for the model: the new Qwen prefixes and the
|
|
* stable tiered family they inherit their ladder from. Toggle-only manifest
|
|
* entries stay on the generic route guard.
|
|
*/
|
|
export function hasTieredManifestReasoning(
|
|
modelId: string | undefined,
|
|
): boolean {
|
|
if (!modelId) return false;
|
|
if (isTieredEffortQwenPrefix(modelId)) return true;
|
|
const entry = MODEL_CONFIGURATIONS[modelId]?.reasoning;
|
|
return !!entry && !entry.toggleOnly;
|
|
}
|
|
|
|
export function getConfiguredModelReasoning(
|
|
config: Config,
|
|
modelId = config.getModel?.(),
|
|
fallbackToManifest = true,
|
|
): ModelReasoningConfiguration | undefined {
|
|
if (
|
|
!modelId ||
|
|
config.getActiveRuntimeModelSnapshot?.() ||
|
|
config.getModel?.().startsWith(ACP_ROUTE_ID_PREFIX)
|
|
) {
|
|
return undefined;
|
|
}
|
|
const active = config.getContentGeneratorConfig?.();
|
|
const generation = {
|
|
...active,
|
|
model: active?.model ?? modelId,
|
|
authType: active?.authType ?? config.getAuthType?.(),
|
|
baseUrl:
|
|
active?.baseUrl ?? config.getCurrentModelRegistryBaseUrl?.() ?? undefined,
|
|
};
|
|
const reasoning = resolveReasoningForModel(config, generation, modelId);
|
|
const requestModel = getRequestModel(generation, modelId);
|
|
return (
|
|
reasoning ??
|
|
(fallbackToManifest
|
|
? getModelConfiguration(requestModel)?.reasoning
|
|
: undefined)
|
|
);
|
|
}
|
|
|
|
export function getReasoningEffortsForConfig(
|
|
config: Config,
|
|
): readonly ReasoningEffort[] {
|
|
const modelId = config.getModel?.();
|
|
// One tier rule for `/effort` and a workflow agent's per-call effort: this
|
|
// site keeps its own capability lookup and delegates only the rule.
|
|
return reasoningEffortsForCapability(
|
|
getConfiguredModelReasoning(config, modelId, false),
|
|
);
|
|
}
|
|
|
|
export function parseReasoningSelection(
|
|
value: unknown,
|
|
): ReasoningSelection | undefined {
|
|
if (value === REASONING_EFFORT_NONE || value === REASONING_EFFORT_DEFAULT) {
|
|
return value;
|
|
}
|
|
return REASONING_EFFORT_TIERS.find((tier) => tier === value);
|
|
}
|
|
|
|
export function isReasoningSelectionSupported(
|
|
modelId: string | undefined,
|
|
selection: ReasoningSelection,
|
|
thinkingMandatory = false,
|
|
configuredReasoning?: ModelReasoningConfiguration,
|
|
): boolean {
|
|
if (!modelId) return false;
|
|
const reasoning = getModelConfiguration(
|
|
modelId,
|
|
configuredReasoning,
|
|
)?.reasoning;
|
|
if (!reasoning?.thinking) {
|
|
const normalized = modelId.toLowerCase();
|
|
if (normalized.startsWith('qwen') || normalized === 'coder-model')
|
|
return false;
|
|
}
|
|
if (selection === REASONING_EFFORT_DEFAULT) return true;
|
|
if (selection === REASONING_EFFORT_NONE) {
|
|
return reasoning?.canDisable !== false && !thinkingMandatory;
|
|
}
|
|
return reasoning?.thinking
|
|
? !reasoning.toggleOnly && reasoning.efforts?.includes(selection) === true
|
|
: REASONING_EFFORT_TIERS.includes(selection);
|
|
}
|
|
|
|
export function clearReasoningRequestOverrides(
|
|
generation: ContentGeneratorConfig,
|
|
config?: Pick<Config, 'getResolvedModelConfig'>,
|
|
): void {
|
|
const deepSeek = getDeepSeekReasoningState(
|
|
generation,
|
|
resolveReasoningForModel(config, generation),
|
|
);
|
|
if (
|
|
getGptReasoningCapabilities(getRequestModel(generation)) ||
|
|
getGptReasoningOverrideState(
|
|
generation,
|
|
resolveReasoningForModel(config, generation),
|
|
)?.blocksTierChange
|
|
)
|
|
return;
|
|
for (const source of ['extra_body', 'samplingParams'] as const) {
|
|
const layer = generation[source];
|
|
if (!layer) continue;
|
|
const next = { ...layer };
|
|
delete next['enable_thinking'];
|
|
delete next['reasoning_effort'];
|
|
delete next['thinking_budget'];
|
|
if (deepSeek) {
|
|
delete next['thinking'];
|
|
const reasoning = next['reasoning'];
|
|
if (
|
|
reasoning &&
|
|
typeof reasoning === 'object' &&
|
|
!Array.isArray(reasoning)
|
|
) {
|
|
const { effort: _effort, ...rest } = reasoning as Record<
|
|
string,
|
|
unknown
|
|
>;
|
|
if (Object.keys(rest).length) next['reasoning'] = rest;
|
|
else delete next['reasoning'];
|
|
}
|
|
}
|
|
generation[source] = next;
|
|
}
|
|
}
|
|
|
|
export function applyReasoningSelection(
|
|
config: Config,
|
|
selection: ReasoningSelection,
|
|
defaultReasoning?: ContentGeneratorConfig['reasoning'],
|
|
): void {
|
|
const apply = (
|
|
generation: Partial<ContentGeneratorConfig> | undefined,
|
|
): void => {
|
|
if (!generation) return;
|
|
if (selection === REASONING_EFFORT_NONE) {
|
|
generation.reasoning = false;
|
|
return;
|
|
}
|
|
if (selection === REASONING_EFFORT_DEFAULT) {
|
|
if (defaultReasoning !== undefined) {
|
|
generation.reasoning = defaultReasoning
|
|
? { ...defaultReasoning }
|
|
: false;
|
|
return;
|
|
}
|
|
if (!generation.reasoning) {
|
|
generation.reasoning = undefined;
|
|
return;
|
|
}
|
|
const next = { ...generation.reasoning };
|
|
delete next.effort;
|
|
generation.reasoning = Object.keys(next).length > 0 ? next : undefined;
|
|
return;
|
|
}
|
|
generation.reasoning = {
|
|
...(generation.reasoning || defaultReasoning || {}),
|
|
effort: selection,
|
|
};
|
|
};
|
|
|
|
const live = config.getContentGeneratorConfig?.();
|
|
apply(live);
|
|
const modelsConfig = config.getModelsConfig?.();
|
|
const rebuildable = modelsConfig?.getGenerationConfig?.();
|
|
if (rebuildable !== live) apply(rebuildable);
|
|
if (
|
|
live &&
|
|
getBuiltinDeepSeekReasoning({ ...live, model: getRequestModel(live) })
|
|
) {
|
|
for (const sources of [
|
|
config.getContentGeneratorConfigSources?.(),
|
|
modelsConfig?.getGenerationConfigSources?.(),
|
|
]) {
|
|
if (!sources) continue;
|
|
if (selection === REASONING_EFFORT_NONE) {
|
|
sources['reasoning'] = {
|
|
kind: 'programmatic',
|
|
detail: 'reasoning selection',
|
|
};
|
|
} else {
|
|
delete sources['reasoning'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function buildModelReasoningConfigOption(
|
|
modelId: string | undefined,
|
|
state: ModelReasoningConfigState = {},
|
|
configuredReasoning?: ModelReasoningConfiguration,
|
|
): SessionConfigOption | undefined {
|
|
const reasoning = getModelConfiguration(
|
|
modelId,
|
|
configuredReasoning,
|
|
)?.reasoning;
|
|
if (!reasoning?.thinking) return undefined;
|
|
const canDisable =
|
|
reasoning.canDisable !== false && state.thinkingMandatory !== true;
|
|
const enabled =
|
|
state.enabled ??
|
|
(state.effort !== undefined ||
|
|
reasoning.toggleOnly ||
|
|
reasoning.defaultEnabled !== false);
|
|
const effort =
|
|
state.effort &&
|
|
!reasoning.toggleOnly &&
|
|
(configuredReasoning?.profile ||
|
|
(getGptReasoningCapabilities(modelId) &&
|
|
!parseModelReasoningCapabilities(configuredReasoning)))
|
|
? clampReasoningEffort(state.effort, reasoning.efforts)
|
|
: state.effort;
|
|
const currentValue =
|
|
!enabled && canDisable
|
|
? REASONING_EFFORT_NONE
|
|
: reasoning.toggleOnly
|
|
? REASONING_EFFORT_DEFAULT
|
|
: (reasoning.efforts.find((candidate) => candidate === effort) ??
|
|
reasoning.defaultEffort ??
|
|
REASONING_EFFORT_DEFAULT);
|
|
|
|
return {
|
|
id: 'reasoning_effort',
|
|
name: 'Reasoning effort',
|
|
description: `Thinking and reasoning effort for ${modelId}`,
|
|
category: 'thought_level',
|
|
type: 'select',
|
|
currentValue,
|
|
options: [
|
|
...(canDisable
|
|
? [
|
|
{
|
|
value: REASONING_EFFORT_NONE,
|
|
name: 'Thinking off',
|
|
description: 'Disable thinking for this session',
|
|
},
|
|
]
|
|
: []),
|
|
...(reasoning.toggleOnly
|
|
? [
|
|
{
|
|
value: REASONING_EFFORT_DEFAULT,
|
|
name: 'Thinking on',
|
|
description: 'Use the model or provider thinking default',
|
|
},
|
|
]
|
|
: [
|
|
...(reasoning.defaultEffort
|
|
? []
|
|
: [
|
|
{
|
|
value: REASONING_EFFORT_DEFAULT,
|
|
name: 'Default',
|
|
description: 'Use the model or provider default',
|
|
},
|
|
]),
|
|
...reasoning.efforts.map((effort) => ({
|
|
value: effort,
|
|
name: REASONING_EFFORT_NAMES[effort],
|
|
description: 'Apply this effort to the next request',
|
|
})),
|
|
]),
|
|
],
|
|
_meta: {
|
|
'qwenCode/reasoning': reasoning.toggleOnly
|
|
? {
|
|
toggleOnly: true,
|
|
...(state.canEnable === false ? { canEnable: false } : {}),
|
|
...(canDisable ? {} : { thinkingMandatory: true }),
|
|
}
|
|
: {
|
|
...(state.enableValue ? { enableValue: state.enableValue } : {}),
|
|
...(state.canEnable === false ? { canEnable: false } : {}),
|
|
...(reasoning.defaultEffort
|
|
? { defaultEffort: reasoning.defaultEffort }
|
|
: {}),
|
|
...(canDisable ? {} : { thinkingMandatory: true }),
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
export function buildModelReasoningConfigPreview(
|
|
modelId: string | undefined,
|
|
state: ModelReasoningConfigState = {},
|
|
configuredReasoning?: ModelReasoningConfiguration,
|
|
generation?: ContentGeneratorConfig,
|
|
): SessionConfigOption[] | undefined {
|
|
const reasoning = getModelConfiguration(
|
|
modelId,
|
|
configuredReasoning,
|
|
)?.reasoning;
|
|
if (!reasoning?.thinking) return undefined;
|
|
const effectiveReasoning =
|
|
state.enabled === false
|
|
? false
|
|
: state.enabled === true
|
|
? { effort: state.effort }
|
|
: generation?.reasoning;
|
|
const override =
|
|
generation &&
|
|
getGptReasoningOverrideState(
|
|
{
|
|
...generation,
|
|
reasoning: effectiveReasoning,
|
|
},
|
|
configuredReasoning,
|
|
);
|
|
if (
|
|
configuredReasoning?.profile &&
|
|
override?.enabled &&
|
|
override.useDefaultEffort
|
|
) {
|
|
return undefined;
|
|
}
|
|
const enableOverride =
|
|
generation &&
|
|
getGptReasoningOverrideState(
|
|
{ ...generation, reasoning: undefined },
|
|
configuredReasoning,
|
|
);
|
|
const option = buildModelReasoningConfigOption(
|
|
modelId,
|
|
{
|
|
...state,
|
|
...(override
|
|
? {
|
|
enabled: override.enabled,
|
|
effort: override.useDefaultEffort ? undefined : override.effort,
|
|
}
|
|
: {}),
|
|
...(enableOverride?.blocksTierChange
|
|
? {
|
|
...(effectiveReasoning === false ? { enabled: false } : {}),
|
|
...(enableOverride.enabled && generation?.reasoning !== false
|
|
? { enableValue: REASONING_EFFORT_DEFAULT }
|
|
: { canEnable: false as const }),
|
|
}
|
|
: {}),
|
|
},
|
|
configuredReasoning,
|
|
);
|
|
return option ? [option] : undefined;
|
|
}
|
|
|
|
export function buildModelReasoningRoutePreview(
|
|
generation: ContentGeneratorConfig,
|
|
reasoning: ResolvedReasoning | undefined,
|
|
selection: unknown,
|
|
opaqueRoute = false,
|
|
): SessionConfigOption[] | undefined {
|
|
const model = getRequestModel(generation);
|
|
const thinkingMandatory =
|
|
model === generation.model && generation.thinkingMandatory === true;
|
|
// The new tiered Qwen families resolve no core capability without a
|
|
// declaration; on routed (opaque) selectors the manifest fallback is their
|
|
// known contract, so the controls must survive the route guard. The same
|
|
// holds for the stable tiered family the new prefixes inherit from. Both
|
|
// only ever map to tiered (non-toggle) manifest entries.
|
|
const qwenManifest =
|
|
reasoning === undefined && hasTieredManifestReasoning(model)
|
|
? (getModelConfiguration(model)?.reasoning as
|
|
| ResolvedReasoning
|
|
| undefined)
|
|
: undefined;
|
|
const routeReasoning = reasoning ?? qwenManifest;
|
|
if (
|
|
opaqueRoute &&
|
|
!routeReasoning?.profile &&
|
|
!qwenManifest &&
|
|
!getDeepSeekReasoningState(generation, routeReasoning)
|
|
)
|
|
return undefined;
|
|
const parsedSelection = parseReasoningSelection(selection);
|
|
const usesConfiguredState =
|
|
!parsedSelection ||
|
|
parsedSelection === REASONING_EFFORT_DEFAULT ||
|
|
!isReasoningSelectionSupported(
|
|
model,
|
|
parsedSelection,
|
|
thinkingMandatory,
|
|
routeReasoning,
|
|
);
|
|
const deepSeekState = usesConfiguredState
|
|
? getDeepSeekReasoningState(
|
|
{
|
|
...generation,
|
|
...(parsedSelection &&
|
|
parsedSelection !== REASONING_EFFORT_DEFAULT &&
|
|
parsedSelection !== REASONING_EFFORT_NONE &&
|
|
generation.reasoning !== false
|
|
? {
|
|
reasoning: { ...generation.reasoning, effort: parsedSelection },
|
|
}
|
|
: {}),
|
|
},
|
|
routeReasoning,
|
|
)
|
|
: undefined;
|
|
return buildModelReasoningConfigPreview(
|
|
model,
|
|
{
|
|
enabled: generation.reasoning === false ? false : undefined,
|
|
effort: generation.reasoning ? generation.reasoning.effort : undefined,
|
|
...resolvePersistedReasoningConfigState(
|
|
model,
|
|
selection,
|
|
thinkingMandatory,
|
|
routeReasoning,
|
|
),
|
|
...deepSeekState,
|
|
},
|
|
routeReasoning,
|
|
generation,
|
|
);
|
|
}
|
|
|
|
export function getReasoningForDisplay(
|
|
config: Config,
|
|
generation: ContentGeneratorConfig,
|
|
) {
|
|
const resolved = resolveReasoningForModel(config, generation);
|
|
if (!resolved) return generation.reasoning;
|
|
const deepSeek = getDeepSeekReasoningState(generation, resolved);
|
|
if (deepSeek) return deepSeek.enabled ? { effort: deepSeek.effort } : false;
|
|
const override = getGptReasoningOverrideState(generation, resolved);
|
|
if (override) return override.enabled ? { effort: override.effort } : false;
|
|
if (config.getReasoningEffortOverride?.()) return generation.reasoning;
|
|
return getEffectiveReasoning(generation, resolved);
|
|
}
|