diff --git a/.changeset/effort-command.md b/.changeset/effort-command.md new file mode 100644 index 00000000..0d9cd46f --- /dev/null +++ b/.changeset/effort-command.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": minor +--- + +Add a `/effort` slash command (alias `/thinking`) to adjust the model's thinking effort at runtime. `/effort ` sets one of `off`, `low`, `medium`, `high`, `xhigh`, `max` directly; `/effort` with no argument opens a picker highlighting the current level. Invalid levels are rejected with the list of valid values. diff --git a/apps/kimi-code/src/tui/commands/config.ts b/apps/kimi-code/src/tui/commands/config.ts index 2f512d03..4bbd4cc4 100644 --- a/apps/kimi-code/src/tui/commands/config.ts +++ b/apps/kimi-code/src/tui/commands/config.ts @@ -1,6 +1,12 @@ import type { PermissionMode, Session } from '@moonshot-ai/kimi-code-sdk'; import { EditorSelectorComponent } from '../components/dialogs/editor-selector'; +import { + EffortSelectorComponent, + isThinkingEffortLevel, + THINKING_EFFORT_LEVELS, + type ThinkingEffortLevel, +} from '../components/dialogs/effort-selector'; import { ModelSelectorComponent } from '../components/dialogs/model-selector'; import { PermissionSelectorComponent } from '../components/dialogs/permission-selector'; import { SettingsSelectorComponent, type SettingsSelection } from '../components/dialogs/settings-selector'; @@ -195,10 +201,64 @@ export function handleModelCommand(host: SlashCommandHost, args: string): void { showModelPicker(host, alias); } +export async function handleEffortCommand(host: SlashCommandHost, args: string): Promise { + if (host.session === undefined) { + host.showError(NO_ACTIVE_SESSION_MESSAGE); + return; + } + + const requested = args.trim().toLowerCase(); + if (requested.length === 0) { + showEffortPicker(host); + return; + } + if (!isThinkingEffortLevel(requested)) { + host.showError( + `Unknown thinking effort: ${requested}. Valid levels: ${THINKING_EFFORT_LEVELS.join(', ')}.`, + ); + return; + } + await applyEffortChoice(host, requested); +} + // --------------------------------------------------------------------------- // Pickers & config apply // --------------------------------------------------------------------------- +function showEffortPicker(host: SlashCommandHost): void { + host.mountEditorReplacement( + new EffortSelectorComponent({ + currentValue: host.state.appState.thinkingLevel, + colors: host.state.theme.colors, + onSelect: (level) => { + host.restoreEditor(); + void applyEffortChoice(host, level); + }, + onCancel: () => { + host.restoreEditor(); + }, + }), + ); +} + +async function applyEffortChoice(host: SlashCommandHost, level: ThinkingEffortLevel): Promise { + if (level === host.state.appState.thinkingLevel) { + host.showStatus(`Thinking effort unchanged: ${level}.`); + return; + } + + try { + await host.requireSession().setThinking(level); + } catch (error) { + host.showError(`Failed to set thinking effort: ${formatErrorMessage(error)}`); + return; + } + + host.setAppState({ thinkingLevel: level, thinking: level !== 'off' }); + host.track('thinking_toggle', { enabled: level !== 'off' }); + host.showStatus(`Thinking effort set to ${level}.`, host.state.theme.colors.success); +} + function showEditorPicker(host: SlashCommandHost): void { const currentValue = host.state.appState.editorCommand ?? ''; host.mountEditorReplacement( diff --git a/apps/kimi-code/src/tui/commands/dispatch.ts b/apps/kimi-code/src/tui/commands/dispatch.ts index 3bd878b0..4ba53019 100644 --- a/apps/kimi-code/src/tui/commands/dispatch.ts +++ b/apps/kimi-code/src/tui/commands/dispatch.ts @@ -25,6 +25,7 @@ import { handleAutoCommand, handleCompactCommand, handleEditorCommand, + handleEffortCommand, handleModelCommand, handlePlanCommand, handleThemeCommand, @@ -56,6 +57,7 @@ export { handleAutoCommand, handleCompactCommand, handleEditorCommand, + handleEffortCommand, handleModelCommand, handlePlanCommand, handleThemeCommand, @@ -228,6 +230,9 @@ async function handleBuiltInSlashCommand( case 'model': handleModelCommand(host, args); return; + case 'effort': + await handleEffortCommand(host, args); + return; case 'permission': showPermissionPicker(host); return; diff --git a/apps/kimi-code/src/tui/commands/registry.ts b/apps/kimi-code/src/tui/commands/registry.ts index faf76b57..a009825b 100644 --- a/apps/kimi-code/src/tui/commands/registry.ts +++ b/apps/kimi-code/src/tui/commands/registry.ts @@ -42,6 +42,13 @@ export const BUILTIN_SLASH_COMMANDS = [ description: 'Switch LLM model', priority: 100, }, + { + name: 'effort', + aliases: ['thinking'], + description: 'Set model thinking effort (off/low/medium/high/xhigh/max)', + priority: 100, + availability: 'always', + }, { name: 'help', aliases: ['h', '?'], diff --git a/apps/kimi-code/src/tui/components/chrome/footer.ts b/apps/kimi-code/src/tui/components/chrome/footer.ts index 506c05d1..8e8147f5 100644 --- a/apps/kimi-code/src/tui/components/chrome/footer.ts +++ b/apps/kimi-code/src/tui/components/chrome/footer.ts @@ -51,6 +51,7 @@ export interface ToolbarTip { const TOOLBAR_TIPS: readonly ToolbarTip[] = [ { text: 'shift+tab: plan mode' }, { text: '/model: switch model' }, + { text: '/effort: set thinking effort' }, { text: 'ctrl+s: steer mid-turn', priority: 2 }, { text: '/compact: compact context', priority: 2 }, { text: 'ctrl+o: expand tool output' }, diff --git a/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts b/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts new file mode 100644 index 00000000..4072e999 --- /dev/null +++ b/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts @@ -0,0 +1,58 @@ +import { ChoicePickerComponent, type ChoiceOption } from './choice-picker'; + +import type { ColorPalette } from '#/tui/theme/colors'; + +/** Thinking effort levels exposed to the user, in ascending order. */ +export const THINKING_EFFORT_LEVELS = [ + 'off', + 'low', + 'medium', + 'high', + 'xhigh', + 'max', +] as const; + +export type ThinkingEffortLevel = (typeof THINKING_EFFORT_LEVELS)[number]; + +export function isThinkingEffortLevel(value: string): value is ThinkingEffortLevel { + return (THINKING_EFFORT_LEVELS as readonly string[]).includes(value); +} + +const EFFORT_OPTIONS: readonly ChoiceOption[] = [ + { value: 'off', label: 'Off', description: 'Disable extended thinking — respond directly.' }, + { value: 'low', label: 'Low', description: 'Brief reasoning before responding.' }, + { value: 'medium', label: 'Medium', description: 'Moderate reasoning for everyday tasks.' }, + { value: 'high', label: 'High', description: 'Thorough reasoning. Recommended default.' }, + { + value: 'xhigh', + label: 'Extra high', + description: 'Extended reasoning. Provider/model-specific; clamps to high when unsupported.', + }, + { + value: 'max', + label: 'Max', + description: 'Maximum reasoning. Provider/model-specific; clamps to high when unsupported.', + }, +]; + +export interface EffortSelectorOptions { + readonly currentValue: string; + readonly colors: ColorPalette; + readonly onSelect: (level: ThinkingEffortLevel) => void; + readonly onCancel: () => void; +} + +export class EffortSelectorComponent extends ChoicePickerComponent { + constructor(opts: EffortSelectorOptions) { + super({ + title: 'Set thinking effort', + options: [...EFFORT_OPTIONS], + currentValue: opts.currentValue, + colors: opts.colors, + onSelect: (value) => { + if (isThinkingEffortLevel(value)) opts.onSelect(value); + }, + onCancel: opts.onCancel, + }); + } +} diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts index 68258aaa..ce80e358 100644 --- a/apps/kimi-code/src/tui/kimi-tui.ts +++ b/apps/kimi-code/src/tui/kimi-tui.ts @@ -157,6 +157,7 @@ function createInitialAppState(input: KimiTUIStartupInput): AppState { permissionMode: startupPermission, planMode: input.cliOptions.plan, thinking: false, + thinkingLevel: 'off', contextUsage: 0, contextTokens: 0, maxContextTokens: 0, @@ -951,7 +952,7 @@ export class KimiTUI { workDir: this.state.appState.workDir, model, thinking: - this.session === undefined ? undefined : this.state.appState.thinking ? 'on' : 'off', + this.session === undefined ? undefined : this.state.appState.thinkingLevel || 'off', permission: this.state.appState.permissionMode, planMode: this.state.appState.planMode ? true : undefined, }); @@ -971,6 +972,7 @@ export class KimiTUI { sessionId: session.id, model: status.model ?? '', thinking: status.thinkingLevel !== 'off', + thinkingLevel: status.thinkingLevel, permissionMode: status.permission, planMode: status.planMode, contextTokens: status.contextTokens, diff --git a/apps/kimi-code/src/tui/types.ts b/apps/kimi-code/src/tui/types.ts index fe73a884..b078c32e 100644 --- a/apps/kimi-code/src/tui/types.ts +++ b/apps/kimi-code/src/tui/types.ts @@ -18,6 +18,7 @@ export interface AppState { permissionMode: PermissionMode; planMode: boolean; thinking: boolean; + thinkingLevel: string; contextUsage: number; contextTokens: number; maxContextTokens: number; diff --git a/apps/kimi-code/test/tui/commands/registry.test.ts b/apps/kimi-code/test/tui/commands/registry.test.ts index 74737fb5..9784df11 100644 --- a/apps/kimi-code/test/tui/commands/registry.test.ts +++ b/apps/kimi-code/test/tui/commands/registry.test.ts @@ -38,6 +38,14 @@ describe('built-in slash command registry', () => { expect(findBuiltInSlashCommand('unknown')).toBeUndefined(); }); + it('exposes /effort with a thinking alias, always available', () => { + const effort = findBuiltInSlashCommand('effort'); + expect(effort?.name).toBe('effort'); + expect(effort?.aliases).toContain('thinking'); + expect(findBuiltInSlashCommand('thinking')?.name).toBe('effort'); + expect(resolveSlashCommandAvailability(effort!, '')).toBe('always'); + }); + it('marks plan clear as idle-only while normal plan toggles are always available', () => { const plan = findBuiltInSlashCommand('plan'); expect(plan).toBeDefined(); @@ -80,6 +88,7 @@ describe('built-in slash command registry', () => { expect.arrayContaining([ 'compact', 'editor', + 'effort', 'exit', 'export-debug-zip', 'fork', diff --git a/apps/kimi-code/test/tui/create-tui-state.test.ts b/apps/kimi-code/test/tui/create-tui-state.test.ts index f82d93fe..b7f4b2e9 100644 --- a/apps/kimi-code/test/tui/create-tui-state.test.ts +++ b/apps/kimi-code/test/tui/create-tui-state.test.ts @@ -12,6 +12,7 @@ function fakeInitialAppState(): AppState { permissionMode: 'manual', planMode: false, thinking: false, + thinkingLevel: 'off', contextUsage: 0, contextTokens: 0, maxContextTokens: 0, diff --git a/docs/en/reference/slash-commands.md b/docs/en/reference/slash-commands.md index 26968e25..c2e8d85c 100644 --- a/docs/en/reference/slash-commands.md +++ b/docs/en/reference/slash-commands.md @@ -16,6 +16,7 @@ Some commands are only available in the idle state. Running them while the sessi | `/logout` | — | Clear the credentials of the currently selected account (Kimi Code OAuth credentials, or the corresponding open platform provider config). | No | | `/connect [--refresh] [--url=]` | — | Configure a provider and model from a model catalog. The default catalog is bundled with the CLI; pass `--refresh` to fetch the latest catalog from models.dev, or `--url` to read it from a custom URL. See [Providers and models — `/connect` and the model catalog](../configuration/providers.md#connect-and-the-model-catalog). | No | | `/model` | — | Switch the LLM model used by the current session. | No | +| `/effort []` | `/thinking` | Set the model thinking effort. Pass one of `off`, `low`, `medium`, `high`, `xhigh`, `max`; with no argument, open a picker highlighting the current level. | Yes | | `/settings` | `/config` | Open the settings panel inside the TUI. | Yes | | `/permission` | — | Choose a permission mode. | Yes | | `/editor` | — | Configure the external editor launched by `Ctrl-G`. | Yes | diff --git a/docs/zh/reference/slash-commands.md b/docs/zh/reference/slash-commands.md index 88712880..b53bcf9d 100644 --- a/docs/zh/reference/slash-commands.md +++ b/docs/zh/reference/slash-commands.md @@ -16,6 +16,7 @@ | `/logout` | — | 清除当前所选账号的凭据(Kimi Code OAuth 凭据,或对应开放平台的供应商配置)。 | 否 | | `/connect [--refresh] [--url=]` | — | 从模型目录中选择并配置供应商与模型。CLI 已内置默认目录;传入 `--refresh` 可从 models.dev 拉取最新目录,传入 `--url` 可指向自定义目录地址。详见 [平台与模型 — `/connect` 与模型目录](../configuration/providers.md#connect-与模型目录)。 | 否 | | `/model` | — | 切换当前会话使用的 LLM 模型。 | 否 | +| `/effort []` | `/thinking` | 设置模型的思考强度(thinking effort)。可传入 `off`、`low`、`medium`、`high`、`xhigh`、`max` 之一;不带参数时弹出选择器并高亮当前等级。 | 是 | | `/settings` | `/config` | 打开 TUI 内的设置面板。 | 是 | | `/permission` | — | 选择权限模式(permission mode)。 | 是 | | `/editor` | — | 配置 `Ctrl-G` 调起的外部编辑器。 | 是 |