From 0ec2b48afb0cda52c436bfc54783aa6143473e57 Mon Sep 17 00:00:00 2001 From: Zhong Date: Sun, 31 May 2026 13:15:43 +0800 Subject: [PATCH 1/2] feat(tui): add /effort command to set model thinking effort Add a /effort slash command (alias /thinking) to change 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 that highlights the current level. Invalid levels are rejected with the list of valid values. Tracks the active level on AppState (thinkingLevel) so the picker and footer reflect runtime state. Closes #244 --- .changeset/effort-command.md | 5 ++ apps/kimi-code/src/tui/commands/config.ts | 60 +++++++++++++++++++ apps/kimi-code/src/tui/commands/dispatch.ts | 5 ++ apps/kimi-code/src/tui/commands/registry.ts | 7 +++ .../src/tui/components/chrome/footer.ts | 1 + .../tui/components/dialogs/effort-selector.ts | 58 ++++++++++++++++++ apps/kimi-code/src/tui/kimi-tui.ts | 2 + apps/kimi-code/src/tui/types.ts | 1 + .../test/tui/commands/registry.test.ts | 9 +++ .../test/tui/create-tui-state.test.ts | 1 + docs/en/reference/slash-commands.md | 1 + docs/zh/reference/slash-commands.md | 1 + 12 files changed, 151 insertions(+) create mode 100644 .changeset/effort-command.md create mode 100644 apps/kimi-code/src/tui/components/dialogs/effort-selector.ts 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..3d14d425 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, @@ -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` 调起的外部编辑器。 | 是 | From d1b52967985f7e182f7e9aaab043ae2e6d69742f Mon Sep 17 00:00:00 2001 From: Zhong Date: Sun, 31 May 2026 23:59:53 +0800 Subject: [PATCH 2/2] fix(tui): pass thinkingLevel to new session instead of generic 'on' When the user has changed the effort level (e.g. /effort low), /new was silently reverting to the default effort because it only passed 'on', which agent-core resolves back to the configured default. Pass the actual level so new sessions inherit the effort the user selected. Reported by Codex review on PR #253. --- apps/kimi-code/src/tui/kimi-tui.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts index 3d14d425..ce80e358 100644 --- a/apps/kimi-code/src/tui/kimi-tui.ts +++ b/apps/kimi-code/src/tui/kimi-tui.ts @@ -952,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, });