From 43792a9c43a93a42e29b05696d55d443a50063e4 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Thu, 16 Jul 2026 14:48:35 +0800 Subject: [PATCH 1/6] feat: warn about context-cache loss when switching model or thinking effort --- .changeset/mid-conversation-switch-warning.md | 5 ++++ apps/kimi-code/src/tui/commands/config.ts | 17 ++++++++++++ .../tui/components/dialogs/effort-selector.ts | 8 +++++- .../tui/components/dialogs/model-selector.ts | 8 +++++- .../dialogs/tabbed-model-selector.ts | 25 ++++++++--------- .../dialogs/effort-selector.test.ts | 27 +++++++++++++++++++ .../components/dialogs/model-selector.test.ts | 18 +++++++++++++ .../dialogs/tabbed-model-selector.test.ts | 22 +++++++++++++++ 8 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 .changeset/mid-conversation-switch-warning.md diff --git a/.changeset/mid-conversation-switch-warning.md b/.changeset/mid-conversation-switch-warning.md new file mode 100644 index 0000000000..4ada5f4ecd --- /dev/null +++ b/.changeset/mid-conversation-switch-warning.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Warn when switching the model or thinking effort would clear the context cache. diff --git a/apps/kimi-code/src/tui/commands/config.ts b/apps/kimi-code/src/tui/commands/config.ts index e3d6ad852c..2d7600a488 100644 --- a/apps/kimi-code/src/tui/commands/config.ts +++ b/apps/kimi-code/src/tui/commands/config.ts @@ -35,6 +35,21 @@ import type { SlashCommandHost } from './dispatch'; const MODEL_PICKER_REFRESH_TIMEOUT_MS = 2_000; +const MODEL_SWITCH_CACHE_WARNING = + 'Switching models clears context cache. Use /new to avoid extra costs.'; +const EFFORT_SWITCH_CACHE_WARNING = + 'Switching effort clears context cache. Use /new to avoid extra costs.'; + +/** True once the conversation has at least one user message: a switch from + * then on resends the accumulated context, losing the cache. Shell-command + * echoes are also 'user' transcript entries but carry an empty `bullet`, so + * they're excluded. */ +function hasConversationHistory(host: SlashCommandHost): boolean { + return host.state.transcriptEntries.some( + (entry) => entry.kind === 'user' && entry.bullet !== '', + ); +} + function currentTuiConfig(host: SlashCommandHost): TuiConfig { return { theme: host.state.appState.theme, @@ -276,6 +291,7 @@ function showEffortPicker( new EffortSelectorComponent({ efforts: segments, currentValue, + warning: hasConversationHistory(host) ? EFFORT_SWITCH_CACHE_WARNING : undefined, onSelect: (effort) => { host.restoreEditor(); void performModelSwitch(host, alias, effort, true); @@ -392,6 +408,7 @@ export function showModelPicker(host: SlashCommandHost, selectedValue: string = currentValue: host.state.appState.model, selectedValue, currentThinkingEffort: host.state.appState.thinkingEffort, + warning: hasConversationHistory(host) ? MODEL_SWITCH_CACHE_WARNING : undefined, onSelect: ({ alias, thinking }) => { host.restoreEditor(); void performModelSwitch(host, alias, thinking, true); diff --git a/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts b/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts index 2678899ad9..6e41c69d84 100644 --- a/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts +++ b/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts @@ -22,6 +22,9 @@ export interface EffortSelectorOptions { /** When provided, Alt+S applies the choice to the current session only. */ readonly onSessionOnlySelect?: (effort: ThinkingEffort) => void; readonly onCancel: () => void; + /** When set, rendered as a warning-colored line directly below the key-hint + * line (e.g. the mid-conversation switch cost notice). */ + readonly warning?: string; } /** @@ -76,8 +79,11 @@ export class EffortSelectorComponent extends Container implements Focusable { currentTheme.fg('primary', '─'.repeat(width)), currentTheme.boldFg('primary', ` ${this.opts.title ?? 'Select thinking effort'}`), currentTheme.fg('textMuted', ` ${hintParts.join(' · ')}`), - '', ]; + if (this.opts.warning !== undefined) { + lines.push(currentTheme.fg('warning', ` ${this.opts.warning}`)); + } + lines.push(''); const segments = this.opts.efforts.map((effort, index) => { const label = effortLabel(effort); diff --git a/apps/kimi-code/src/tui/components/dialogs/model-selector.ts b/apps/kimi-code/src/tui/components/dialogs/model-selector.ts index 82906d1d50..970616b2b1 100644 --- a/apps/kimi-code/src/tui/components/dialogs/model-selector.ts +++ b/apps/kimi-code/src/tui/components/dialogs/model-selector.ts @@ -73,6 +73,9 @@ export interface ModelSelectorOptions { /** When true, the hint line mentions the Tab provider switch — set by * TabbedModelSelectorComponent so the inner list advertises the tab keys. */ readonly providerSwitchHint?: boolean; + /** When set, rendered as a warning-colored line directly below the key-hint + * line (e.g. the mid-conversation switch cost notice). */ + readonly warning?: string; readonly onSelect: (selection: ModelSelection) => void; /** When provided, Alt+S invokes this instead of onSelect — used to apply the * choice to the current session only, without persisting it as the default. */ @@ -286,8 +289,11 @@ export class ModelSelectorComponent extends Container implements Focusable { currentTheme.fg('primary', '─'.repeat(width)), currentTheme.boldFg('primary', ' Select a model') + titleSuffix, currentTheme.fg('textMuted', ' ' + hintParts.join(' · ')), - '', ]; + if (this.opts.warning !== undefined) { + lines.push(currentTheme.fg('warning', ` ${this.opts.warning}`)); + } + lines.push(''); if (searchable && view.query.length > 0) { lines.push(currentTheme.fg('primary', ' Search: ') + currentTheme.fg('text', view.query)); diff --git a/apps/kimi-code/src/tui/components/dialogs/tabbed-model-selector.ts b/apps/kimi-code/src/tui/components/dialogs/tabbed-model-selector.ts index 9a986b0962..c21897a113 100644 --- a/apps/kimi-code/src/tui/components/dialogs/tabbed-model-selector.ts +++ b/apps/kimi-code/src/tui/components/dialogs/tabbed-model-selector.ts @@ -43,6 +43,10 @@ export interface TabbedModelSelectorOptions { /** When set, the tab for this provider id is initially active instead of the * tab derived from `currentValue`. */ readonly initialTabId?: string; + /** Forwarded to each inner selector; when set, a warning-colored line is + * rendered directly below the key-hint line (e.g. the mid-conversation + * switch cost notice). */ + readonly warning?: string; readonly onSelect: (selection: ModelSelection) => void; /** Forwarded to each inner selector; when set, Alt+S applies the choice to * the current session only without persisting it as the default. */ @@ -100,24 +104,20 @@ export class TabbedModelSelectorComponent extends Container implements Focusable if (this.tabs.length <= 1) { return inner.map((line) => truncateToWidth(line, width)); } - // Layout: divider, title, hint, blank, tab strip, blank, then the model - // list. The inner selector's blank line (inner[3]) separates the hint from - // the tab strip; an extra blank separates the tabs from their list. + // Layout: divider, title, hint, optional warning, blank, tab strip, blank, + // then the model list. The header ends at its first blank line — keep that + // blank above the strip, and separate the tabs from the list with another + // blank. const stripLine = renderTabStrip({ labels: this.tabs.map((tab) => tab.label), activeIndex: this.activeIndex, width, colors: currentTheme.palette, }); - const out: string[] = [ - inner[0] ?? '', - inner[1] ?? '', - inner[2] ?? '', - inner[3] ?? '', - stripLine, - '', - ]; - for (let i = 4; i < inner.length; i++) out.push(inner[i]!); + const headerEnd = inner.findIndex((line) => line === ''); + const splitAt = headerEnd === -1 ? 3 : headerEnd; + const out: string[] = [...inner.slice(0, splitAt + 1), stripLine, '']; + for (let i = splitAt + 1; i < inner.length; i++) out.push(inner[i]!); return out.map((line) => truncateToWidth(line, width)); } @@ -182,6 +182,7 @@ function makeSelector( currentThinkingEffort: opts.currentThinkingEffort, searchable: true, providerSwitchHint: true, + warning: opts.warning, onSelect: opts.onSelect, onSessionOnlySelect: opts.onSessionOnlySelect, onCancel: opts.onCancel, diff --git a/apps/kimi-code/test/tui/components/dialogs/effort-selector.test.ts b/apps/kimi-code/test/tui/components/dialogs/effort-selector.test.ts index e74fa7aa11..0c083e260b 100644 --- a/apps/kimi-code/test/tui/components/dialogs/effort-selector.test.ts +++ b/apps/kimi-code/test/tui/components/dialogs/effort-selector.test.ts @@ -102,4 +102,31 @@ describe('EffortSelectorComponent', () => { picker.handleInput(ESC); expect(onCancel).toHaveBeenCalledTimes(1); }); + + it('renders the warning line directly below the key-hint line when provided', () => { + const picker = new EffortSelectorComponent({ + efforts: ['off', 'low', 'high', 'max'], + currentValue: 'high', + warning: 'Switching may increase token usage.', + onSelect: vi.fn(), + onCancel: vi.fn(), + }); + const lines = picker.render(120).map(strip); + const hintIdx = lines.findIndex((l) => l.includes('←→ switch')); + expect(hintIdx).toBeGreaterThanOrEqual(0); + expect(lines[hintIdx + 1]).toContain('Switching may increase token usage.'); + }); + + it('renders no warning line without the warning option', () => { + const picker = new EffortSelectorComponent({ + efforts: ['off', 'low', 'high', 'max'], + currentValue: 'high', + onSelect: vi.fn(), + onCancel: vi.fn(), + }); + const lines = picker.render(120).map(strip); + const hintIdx = lines.findIndex((l) => l.includes('←→ switch')); + expect(hintIdx).toBeGreaterThanOrEqual(0); + expect(lines[hintIdx + 1]).toBe(''); + }); }); diff --git a/apps/kimi-code/test/tui/components/dialogs/model-selector.test.ts b/apps/kimi-code/test/tui/components/dialogs/model-selector.test.ts index f40cc4093b..8deaae0f6d 100644 --- a/apps/kimi-code/test/tui/components/dialogs/model-selector.test.ts +++ b/apps/kimi-code/test/tui/components/dialogs/model-selector.test.ts @@ -470,6 +470,24 @@ describe('ModelSelectorComponent', () => { // middle entry (medium), not a hardcoded level. expect(text(picker)).toContain('[ Medium ]'); }); + + it('renders the warning line directly below the key-hint line when provided', () => { + const picker = new ModelSelectorComponent({ + models: { kimi: model('Kimi K2') }, + currentValue: 'kimi', + currentThinkingEffort: 'on', + warning: 'Switching may increase token usage.', + onSelect: vi.fn(), + onCancel: vi.fn(), + }); + + const lines = picker.render(120).map(strip); + const hintIdx = lines.findIndex((l) => l.includes('↑↓ navigate')); + expect(hintIdx).toBeGreaterThanOrEqual(0); + expect(lines[hintIdx + 1]).toContain('Switching may increase token usage.'); + // Model list is pushed below the inserted warning line, not overlapped. + expect(lines.findIndex((l) => l.includes('Kimi K2'))).toBeGreaterThan(hintIdx + 1); + }); }); describe('ModelSelectorComponent overrides', () => { diff --git a/apps/kimi-code/test/tui/components/dialogs/tabbed-model-selector.test.ts b/apps/kimi-code/test/tui/components/dialogs/tabbed-model-selector.test.ts index 28fc4210fc..3f42874861 100644 --- a/apps/kimi-code/test/tui/components/dialogs/tabbed-model-selector.test.ts +++ b/apps/kimi-code/test/tui/components/dialogs/tabbed-model-selector.test.ts @@ -131,4 +131,26 @@ describe('TabbedModelSelectorComponent', () => { // It comes first, before the navigation hint. expect(hint!.indexOf('Tab toggle provider')).toBeLessThan(hint!.indexOf('↑↓ navigate')); }); + + it('keeps the tab strip between hint and list when a warning line is present', () => { + const component = new TabbedModelSelectorComponent({ + models: { + k2: model('Kimi K2', 'managed:kimi-code'), + gpt: model('GPT-5', 'openai'), + }, + currentValue: 'k2', + currentThinkingEffort: 'off', + warning: 'Switching may increase token usage.', + onSelect: vi.fn(), + onCancel: vi.fn(), + }); + const lines = component.render(120).map(strip); + const hintIdx = lines.findIndex((l) => l.includes('navigate') && l.includes('Esc cancel')); + expect(lines[hintIdx + 1]).toContain('Switching may increase token usage.'); + expect(lines[hintIdx + 2]).toBe(''); // blank between warning and tabs + const stripIdx = lines.findIndex((l) => l.includes('All') && l.includes('openai')); + expect(stripIdx).toBe(hintIdx + 3); + expect(lines[stripIdx + 1]).toBe(''); // blank between tabs and list + expect(lines.findIndex((l) => l.includes('Kimi K2'))).toBeGreaterThan(stripIdx); + }); }); From c9baea93f5dc64c7424e7d7f48e4f04417cb3b10 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Thu, 16 Jul 2026 15:05:05 +0800 Subject: [PATCH 2/6] chore: polish the switch warning copy --- apps/kimi-code/src/tui/commands/config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/kimi-code/src/tui/commands/config.ts b/apps/kimi-code/src/tui/commands/config.ts index 2d7600a488..61b68b955a 100644 --- a/apps/kimi-code/src/tui/commands/config.ts +++ b/apps/kimi-code/src/tui/commands/config.ts @@ -36,9 +36,9 @@ import type { SlashCommandHost } from './dispatch'; const MODEL_PICKER_REFRESH_TIMEOUT_MS = 2_000; const MODEL_SWITCH_CACHE_WARNING = - 'Switching models clears context cache. Use /new to avoid extra costs.'; + 'Note: Switching models invalidates the existing prompt cache. Use /new to avoid extra token costs.'; const EFFORT_SWITCH_CACHE_WARNING = - 'Switching effort clears context cache. Use /new to avoid extra costs.'; + 'Note: Switching effort invalidates the existing prompt cache. Use /new to avoid extra token costs.'; /** True once the conversation has at least one user message: a switch from * then on resends the accumulated context, losing the cache. Shell-command From f601b9d7b1cf36b08df0882129d1d6966f0c7a5f Mon Sep 17 00:00:00 2001 From: liruifengv Date: Thu, 16 Jul 2026 15:23:44 +0800 Subject: [PATCH 3/6] feat: wrap the switch warning instead of truncating it --- .../tui/components/dialogs/effort-selector.ts | 10 ++++++--- .../tui/components/dialogs/model-selector.ts | 10 ++++++--- .../dialogs/tabbed-model-selector.ts | 6 +++--- .../dialogs/effort-selector.test.ts | 19 +++++++++++++++++ .../components/dialogs/model-selector.test.ts | 21 +++++++++++++++++++ 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts b/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts index 6e41c69d84..bba3243062 100644 --- a/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts +++ b/apps/kimi-code/src/tui/components/dialogs/effort-selector.ts @@ -3,6 +3,7 @@ import { Key, matchesKey, truncateToWidth, + wrapTextWithAnsi, type Focusable, } from '@moonshot-ai/pi-tui'; @@ -22,8 +23,9 @@ export interface EffortSelectorOptions { /** When provided, Alt+S applies the choice to the current session only. */ readonly onSessionOnlySelect?: (effort: ThinkingEffort) => void; readonly onCancel: () => void; - /** When set, rendered as a warning-colored line directly below the key-hint - * line (e.g. the mid-conversation switch cost notice). */ + /** When set, rendered as warning-colored lines directly below the key-hint + * line; wraps instead of truncating when it exceeds the width (e.g. the + * mid-conversation switch cost notice). */ readonly warning?: string; } @@ -81,7 +83,9 @@ export class EffortSelectorComponent extends Container implements Focusable { currentTheme.fg('textMuted', ` ${hintParts.join(' · ')}`), ]; if (this.opts.warning !== undefined) { - lines.push(currentTheme.fg('warning', ` ${this.opts.warning}`)); + for (const line of wrapTextWithAnsi(this.opts.warning, Math.max(1, width - 1))) { + lines.push(currentTheme.fg('warning', ` ${line}`)); + } } lines.push(''); diff --git a/apps/kimi-code/src/tui/components/dialogs/model-selector.ts b/apps/kimi-code/src/tui/components/dialogs/model-selector.ts index 970616b2b1..64646e0229 100644 --- a/apps/kimi-code/src/tui/components/dialogs/model-selector.ts +++ b/apps/kimi-code/src/tui/components/dialogs/model-selector.ts @@ -5,6 +5,7 @@ import { matchesKey, truncateToWidth, visibleWidth, + wrapTextWithAnsi, type Focusable, } from '@moonshot-ai/pi-tui'; @@ -73,8 +74,9 @@ export interface ModelSelectorOptions { /** When true, the hint line mentions the Tab provider switch — set by * TabbedModelSelectorComponent so the inner list advertises the tab keys. */ readonly providerSwitchHint?: boolean; - /** When set, rendered as a warning-colored line directly below the key-hint - * line (e.g. the mid-conversation switch cost notice). */ + /** When set, rendered as warning-colored lines directly below the key-hint + * line; wraps instead of truncating when it exceeds the width (e.g. the + * mid-conversation switch cost notice). */ readonly warning?: string; readonly onSelect: (selection: ModelSelection) => void; /** When provided, Alt+S invokes this instead of onSelect — used to apply the @@ -291,7 +293,9 @@ export class ModelSelectorComponent extends Container implements Focusable { currentTheme.fg('textMuted', ' ' + hintParts.join(' · ')), ]; if (this.opts.warning !== undefined) { - lines.push(currentTheme.fg('warning', ` ${this.opts.warning}`)); + for (const line of wrapTextWithAnsi(this.opts.warning, Math.max(1, width - 1))) { + lines.push(currentTheme.fg('warning', ` ${line}`)); + } } lines.push(''); diff --git a/apps/kimi-code/src/tui/components/dialogs/tabbed-model-selector.ts b/apps/kimi-code/src/tui/components/dialogs/tabbed-model-selector.ts index c21897a113..8adc5efa14 100644 --- a/apps/kimi-code/src/tui/components/dialogs/tabbed-model-selector.ts +++ b/apps/kimi-code/src/tui/components/dialogs/tabbed-model-selector.ts @@ -43,9 +43,9 @@ export interface TabbedModelSelectorOptions { /** When set, the tab for this provider id is initially active instead of the * tab derived from `currentValue`. */ readonly initialTabId?: string; - /** Forwarded to each inner selector; when set, a warning-colored line is - * rendered directly below the key-hint line (e.g. the mid-conversation - * switch cost notice). */ + /** Forwarded to each inner selector; when set, warning-colored lines are + * rendered directly below the key-hint line, wrapping as needed (e.g. the + * mid-conversation switch cost notice). */ readonly warning?: string; readonly onSelect: (selection: ModelSelection) => void; /** Forwarded to each inner selector; when set, Alt+S applies the choice to diff --git a/apps/kimi-code/test/tui/components/dialogs/effort-selector.test.ts b/apps/kimi-code/test/tui/components/dialogs/effort-selector.test.ts index 0c083e260b..53ff5b1702 100644 --- a/apps/kimi-code/test/tui/components/dialogs/effort-selector.test.ts +++ b/apps/kimi-code/test/tui/components/dialogs/effort-selector.test.ts @@ -129,4 +129,23 @@ describe('EffortSelectorComponent', () => { expect(hintIdx).toBeGreaterThanOrEqual(0); expect(lines[hintIdx + 1]).toBe(''); }); + + it('wraps a warning longer than the width instead of truncating it', () => { + const warning = + 'Note: Switching effort invalidates the existing prompt cache. Use /new to avoid extra token costs.'; + const picker = new EffortSelectorComponent({ + efforts: ['off', 'low', 'high', 'max'], + currentValue: 'high', + warning, + onSelect: vi.fn(), + onCancel: vi.fn(), + }); + const lines = picker.render(40).map(strip); + const hintIdx = lines.findIndex((l) => l.includes('←→ switch')); + expect(lines[hintIdx + 1]).not.toBe(''); + expect(lines[hintIdx + 2]).not.toBe(''); + // Word-wrapped: nothing dropped — the full warning survives across lines. + const squashed = lines.join('').replaceAll(/\s+/g, ''); + expect(squashed).toContain(warning.replaceAll(/\s+/g, '')); + }); }); diff --git a/apps/kimi-code/test/tui/components/dialogs/model-selector.test.ts b/apps/kimi-code/test/tui/components/dialogs/model-selector.test.ts index 8deaae0f6d..8fced41768 100644 --- a/apps/kimi-code/test/tui/components/dialogs/model-selector.test.ts +++ b/apps/kimi-code/test/tui/components/dialogs/model-selector.test.ts @@ -488,6 +488,27 @@ describe('ModelSelectorComponent', () => { // Model list is pushed below the inserted warning line, not overlapped. expect(lines.findIndex((l) => l.includes('Kimi K2'))).toBeGreaterThan(hintIdx + 1); }); + + it('wraps a warning longer than the width instead of truncating it', () => { + const warning = + 'Note: Switching models invalidates the existing prompt cache. Use /new to avoid extra token costs.'; + const picker = new ModelSelectorComponent({ + models: { kimi: model('Kimi K2') }, + currentValue: 'kimi', + currentThinkingEffort: 'on', + warning, + onSelect: vi.fn(), + onCancel: vi.fn(), + }); + + const lines = picker.render(50).map(strip); + const hintIdx = lines.findIndex((l) => l.includes('↑↓ navigate')); + expect(lines[hintIdx + 1]).not.toBe(''); + expect(lines[hintIdx + 2]).not.toBe(''); + // Word-wrapped: nothing dropped — the full warning survives across lines. + const squashed = lines.join('').replaceAll(/\s+/g, ''); + expect(squashed).toContain(warning.replaceAll(/\s+/g, '')); + }); }); describe('ModelSelectorComponent overrides', () => { From a438e87cda543202a6d4726c96cdabd73b8dcf6f Mon Sep 17 00:00:00 2001 From: liruifengv Date: Thu, 16 Jul 2026 15:28:57 +0800 Subject: [PATCH 4/6] chore: bold /new in the switch warning --- apps/kimi-code/src/tui/commands/config.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/kimi-code/src/tui/commands/config.ts b/apps/kimi-code/src/tui/commands/config.ts index 61b68b955a..7e758cb417 100644 --- a/apps/kimi-code/src/tui/commands/config.ts +++ b/apps/kimi-code/src/tui/commands/config.ts @@ -35,10 +35,11 @@ import type { SlashCommandHost } from './dispatch'; const MODEL_PICKER_REFRESH_TIMEOUT_MS = 2_000; -const MODEL_SWITCH_CACHE_WARNING = - 'Note: Switching models invalidates the existing prompt cache. Use /new to avoid extra token costs.'; -const EFFORT_SWITCH_CACHE_WARNING = - 'Note: Switching effort invalidates the existing prompt cache. Use /new to avoid extra token costs.'; +/** Build the switch-cost warning at mount time (bold is generated on the + * render path, not cached at module level). */ +function switchCacheWarning(subject: 'models' | 'effort'): string { + return `Note: Switching ${subject} invalidates the existing prompt cache. Use ${currentTheme.bold('/new')} to avoid extra token costs.`; +} /** True once the conversation has at least one user message: a switch from * then on resends the accumulated context, losing the cache. Shell-command @@ -291,7 +292,7 @@ function showEffortPicker( new EffortSelectorComponent({ efforts: segments, currentValue, - warning: hasConversationHistory(host) ? EFFORT_SWITCH_CACHE_WARNING : undefined, + warning: hasConversationHistory(host) ? switchCacheWarning('effort') : undefined, onSelect: (effort) => { host.restoreEditor(); void performModelSwitch(host, alias, effort, true); @@ -408,7 +409,7 @@ export function showModelPicker(host: SlashCommandHost, selectedValue: string = currentValue: host.state.appState.model, selectedValue, currentThinkingEffort: host.state.appState.thinkingEffort, - warning: hasConversationHistory(host) ? MODEL_SWITCH_CACHE_WARNING : undefined, + warning: hasConversationHistory(host) ? switchCacheWarning('models') : undefined, onSelect: ({ alias, thinking }) => { host.restoreEditor(); void performModelSwitch(host, alias, thinking, true); From ec310b62274f34212000948ebf1e400ce4d6b8f7 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Thu, 16 Jul 2026 15:30:29 +0800 Subject: [PATCH 5/6] Revert "chore: bold /new in the switch warning" This reverts commit a438e87cda543202a6d4726c96cdabd73b8dcf6f. --- apps/kimi-code/src/tui/commands/config.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/kimi-code/src/tui/commands/config.ts b/apps/kimi-code/src/tui/commands/config.ts index 7e758cb417..61b68b955a 100644 --- a/apps/kimi-code/src/tui/commands/config.ts +++ b/apps/kimi-code/src/tui/commands/config.ts @@ -35,11 +35,10 @@ import type { SlashCommandHost } from './dispatch'; const MODEL_PICKER_REFRESH_TIMEOUT_MS = 2_000; -/** Build the switch-cost warning at mount time (bold is generated on the - * render path, not cached at module level). */ -function switchCacheWarning(subject: 'models' | 'effort'): string { - return `Note: Switching ${subject} invalidates the existing prompt cache. Use ${currentTheme.bold('/new')} to avoid extra token costs.`; -} +const MODEL_SWITCH_CACHE_WARNING = + 'Note: Switching models invalidates the existing prompt cache. Use /new to avoid extra token costs.'; +const EFFORT_SWITCH_CACHE_WARNING = + 'Note: Switching effort invalidates the existing prompt cache. Use /new to avoid extra token costs.'; /** True once the conversation has at least one user message: a switch from * then on resends the accumulated context, losing the cache. Shell-command @@ -292,7 +291,7 @@ function showEffortPicker( new EffortSelectorComponent({ efforts: segments, currentValue, - warning: hasConversationHistory(host) ? switchCacheWarning('effort') : undefined, + warning: hasConversationHistory(host) ? EFFORT_SWITCH_CACHE_WARNING : undefined, onSelect: (effort) => { host.restoreEditor(); void performModelSwitch(host, alias, effort, true); @@ -409,7 +408,7 @@ export function showModelPicker(host: SlashCommandHost, selectedValue: string = currentValue: host.state.appState.model, selectedValue, currentThinkingEffort: host.state.appState.thinkingEffort, - warning: hasConversationHistory(host) ? switchCacheWarning('models') : undefined, + warning: hasConversationHistory(host) ? MODEL_SWITCH_CACHE_WARNING : undefined, onSelect: ({ alias, thinking }) => { host.restoreEditor(); void performModelSwitch(host, alias, thinking, true); From 71096db22d06a9165431baaaabf14854e698a0cf Mon Sep 17 00:00:00 2001 From: liruifengv Date: Thu, 16 Jul 2026 15:37:31 +0800 Subject: [PATCH 6/6] chore: align the changeset with the warning copy --- .changeset/mid-conversation-switch-warning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/mid-conversation-switch-warning.md b/.changeset/mid-conversation-switch-warning.md index 4ada5f4ecd..620003eabb 100644 --- a/.changeset/mid-conversation-switch-warning.md +++ b/.changeset/mid-conversation-switch-warning.md @@ -2,4 +2,4 @@ "@moonshot-ai/kimi-code": patch --- -Warn when switching the model or thinking effort would clear the context cache. +Warn in the /model and /effort pickers that switching invalidates the existing prompt cache, and hint to use /new to avoid extra token costs.