mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
fix: persist TUI model selection defaults (#24)
* Persist runtime model and default thinking
* fix: persist model defaults from TUI
* refactor: centralize default thinking resolution
* fix: wait for background shutdown notifications
* Revert "fix: wait for background shutdown notifications"
This reverts commit cf98059b8d.
* test: remove SDK default thinking case
* fix: respect thinking mode off defaults
* fix: normalize runtime thinking requests
This commit is contained in:
parent
bfbd522a71
commit
7858821f2f
5 changed files with 133 additions and 24 deletions
6
.changeset/persist-runtime-model-defaults.md
Normal file
6
.changeset/persist-runtime-model-defaults.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"@moonshot-ai/agent-core": patch
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Persist model selections from the terminal UI to the default configuration, and honor the configured default thinking state for new sessions.
|
||||
|
|
@ -4349,20 +4349,16 @@ export class KimiTUI {
|
|||
return;
|
||||
}
|
||||
|
||||
if (alias === this.state.appState.model && thinking === this.state.appState.thinking) {
|
||||
this.showStatus(`Already using ${alias} with thinking ${thinking ? 'on' : 'off'}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const level = thinking ? 'on' : 'off';
|
||||
const prevModel = this.state.appState.model;
|
||||
const prevThinking = this.state.appState.thinking;
|
||||
const runtimeChanged = alias !== prevModel || thinking !== prevThinking;
|
||||
|
||||
const session = this.session;
|
||||
try {
|
||||
const session = this.session;
|
||||
if (session === undefined) {
|
||||
if (session === undefined && runtimeChanged) {
|
||||
await this.activateModelAfterLogin(alias, thinking);
|
||||
} else {
|
||||
} else if (session !== undefined) {
|
||||
if (alias !== prevModel) {
|
||||
await session.setModel(alias);
|
||||
}
|
||||
|
|
@ -4370,23 +4366,50 @@ export class KimiTUI {
|
|||
await session.setThinking(level);
|
||||
}
|
||||
}
|
||||
this.setAppState({ model: alias, thinking });
|
||||
if (session === undefined) {
|
||||
if (alias !== prevModel) {
|
||||
this.track('model_switch', { model: alias });
|
||||
}
|
||||
if (thinking !== prevThinking) {
|
||||
this.track('thinking_toggle', { enabled: thinking });
|
||||
}
|
||||
}
|
||||
this.showStatus(
|
||||
`Switched to ${alias} with thinking ${level}.`,
|
||||
this.state.theme.colors.success,
|
||||
);
|
||||
} catch (error) {
|
||||
const msg = formatErrorMessage(error);
|
||||
this.showError(`Failed to switch model: ${msg}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setAppState({ model: alias, thinking });
|
||||
if (session === undefined && runtimeChanged) {
|
||||
if (alias !== prevModel) {
|
||||
this.track('model_switch', { model: alias });
|
||||
}
|
||||
if (thinking !== prevThinking) {
|
||||
this.track('thinking_toggle', { enabled: thinking });
|
||||
}
|
||||
}
|
||||
|
||||
let persisted = false;
|
||||
try {
|
||||
persisted = await this.persistModelSelection(alias, thinking);
|
||||
} catch (error) {
|
||||
const msg = formatErrorMessage(error);
|
||||
this.showError(`Switched to ${alias}, but failed to save default: ${msg}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const status = runtimeChanged
|
||||
? `Switched to ${alias} with thinking ${level}.`
|
||||
: persisted
|
||||
? `Saved ${alias} with thinking ${level} as default.`
|
||||
: `Already using ${alias} with thinking ${level}.`;
|
||||
this.showStatus(status, this.state.theme.colors.success);
|
||||
}
|
||||
|
||||
// Persists the selected model and thinking state as the startup defaults.
|
||||
private async persistModelSelection(alias: string, thinking: boolean): Promise<boolean> {
|
||||
const config = await this.harness.getConfig({ reload: true });
|
||||
if (config.defaultModel === alias && config.defaultThinking === thinking) {
|
||||
return false;
|
||||
}
|
||||
await this.harness.setConfig({
|
||||
defaultModel: alias,
|
||||
defaultThinking: thinking,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Shows the theme selector.
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ function makeHarness(session = makeSession(), overrides: Record<string, unknown>
|
|||
k2: { model: 'moonshot-v1', maxContextSize: 100 },
|
||||
},
|
||||
})),
|
||||
setConfig: vi.fn(async () => ({ providers: {} })),
|
||||
createSession: vi.fn(async () => session),
|
||||
resumeSession: vi.fn(async () => session),
|
||||
forkSession: vi.fn(async () => session),
|
||||
|
|
@ -1026,6 +1027,7 @@ describe('KimiTUI message flow', () => {
|
|||
|
||||
it('applies /model selection with inline thinking state', async () => {
|
||||
const session = makeSession();
|
||||
const setConfig = vi.fn(async () => ({ providers: {} }));
|
||||
const { driver } = await makeDriver(session, {
|
||||
getConfig: vi.fn(async () => ({
|
||||
models: {
|
||||
|
|
@ -1044,7 +1046,10 @@ describe('KimiTUI message flow', () => {
|
|||
capabilities: ['thinking'],
|
||||
},
|
||||
},
|
||||
defaultModel: 'k2',
|
||||
defaultThinking: false,
|
||||
})),
|
||||
setConfig,
|
||||
});
|
||||
|
||||
driver.handleUserInput('/model turbo');
|
||||
|
|
@ -1060,11 +1065,51 @@ describe('KimiTUI message flow', () => {
|
|||
await vi.waitFor(() => {
|
||||
expect(session.setModel).toHaveBeenCalledWith('turbo');
|
||||
expect(session.setThinking).toHaveBeenCalledWith('on');
|
||||
expect(setConfig).toHaveBeenCalledWith({
|
||||
defaultModel: 'turbo',
|
||||
defaultThinking: true,
|
||||
});
|
||||
});
|
||||
expect(driver.state.appState.model).toBe('turbo');
|
||||
expect(driver.state.appState.thinking).toBe(true);
|
||||
});
|
||||
|
||||
it('persists /model selection even when runtime state is unchanged', async () => {
|
||||
const session = makeSession();
|
||||
const setConfig = vi.fn(async () => ({ providers: {} }));
|
||||
const { driver } = await makeDriver(session, {
|
||||
getConfig: vi.fn(async () => ({
|
||||
models: {
|
||||
k2: {
|
||||
provider: 'managed:kimi-code',
|
||||
model: 'kimi-k2',
|
||||
maxContextSize: 100,
|
||||
displayName: 'Kimi K2',
|
||||
capabilities: ['thinking'],
|
||||
},
|
||||
},
|
||||
defaultModel: 'old-default',
|
||||
defaultThinking: true,
|
||||
})),
|
||||
setConfig,
|
||||
});
|
||||
|
||||
driver.handleUserInput('/model k2');
|
||||
|
||||
const picker = driver.state.editorContainer.children[0];
|
||||
expect(picker).toBeInstanceOf(ModelSelectorComponent);
|
||||
(picker as ModelSelectorComponent).handleInput('\r');
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(setConfig).toHaveBeenCalledWith({
|
||||
defaultModel: 'k2',
|
||||
defaultThinking: false,
|
||||
});
|
||||
});
|
||||
expect(session.setModel).not.toHaveBeenCalled();
|
||||
expect(session.setThinking).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('deletes Kitty inline images when /new clears the transcript', async () => {
|
||||
setCapabilities({ images: 'kitty', trueColor: true, hyperlinks: true });
|
||||
const { driver, harness } = await makeDriver(makeSession({ id: 'ses-1' }));
|
||||
|
|
|
|||
|
|
@ -113,7 +113,10 @@ export class ProviderManager {
|
|||
}
|
||||
|
||||
resolveThinkingLevel(requestedThinking?: string): ThinkingEffort {
|
||||
return resolveThinkingEffort(requestedThinking, this.state.config.thinking);
|
||||
return resolveThinkingEffort(
|
||||
resolveRuntimeThinkingRequest(requestedThinking, this.state.config.defaultThinking),
|
||||
this.state.config.thinking,
|
||||
);
|
||||
}
|
||||
|
||||
resolveSelectedModel(requestedModel: string | undefined): string | undefined {
|
||||
|
|
@ -137,3 +140,12 @@ function normalizeString(value: string | undefined): string | undefined {
|
|||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function resolveRuntimeThinkingRequest(
|
||||
requestedThinking: string | undefined,
|
||||
defaultThinking: boolean | undefined,
|
||||
): string | undefined {
|
||||
const normalized = normalizeString(requestedThinking);
|
||||
if (normalized !== undefined) return normalized;
|
||||
return defaultThinking === false ? 'off' : undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -585,16 +585,29 @@ describe('ProviderManager thinking level', () => {
|
|||
const manager = new ProviderManager({
|
||||
config: {
|
||||
providers: {},
|
||||
defaultThinking: false,
|
||||
thinking: { effort: 'medium', mode: 'auto' },
|
||||
},
|
||||
});
|
||||
expect(manager.resolveThinkingLevel('on')).toBe('medium');
|
||||
expect(manager.resolveThinkingLevel('off')).toBe('off');
|
||||
expect(manager.resolveThinkingLevel('low')).toBe('low');
|
||||
expect(manager.resolveThinkingLevel()).toBe('medium');
|
||||
expect(manager.resolveThinkingLevel()).toBe('off');
|
||||
expect(manager.resolveThinkingLevel('')).toBe('off');
|
||||
expect(manager.resolveThinkingLevel(' ')).toBe('off');
|
||||
|
||||
const managerOnByDefault = new ProviderManager({
|
||||
config: {
|
||||
providers: {},
|
||||
defaultThinking: true,
|
||||
thinking: { effort: 'medium', mode: 'auto' },
|
||||
},
|
||||
});
|
||||
expect(managerOnByDefault.resolveThinkingLevel()).toBe('medium');
|
||||
expect(managerOnByDefault.resolveThinkingLevel(' ')).toBe('medium');
|
||||
|
||||
const managerWithoutEffort = new ProviderManager({
|
||||
config: { providers: {}, thinking: { mode: 'auto' } },
|
||||
config: { providers: {}, defaultThinking: true, thinking: { mode: 'auto' } },
|
||||
});
|
||||
expect(managerWithoutEffort.resolveThinkingLevel('on')).toBe('high');
|
||||
expect(managerWithoutEffort.resolveThinkingLevel()).toBe('high');
|
||||
|
|
@ -604,6 +617,16 @@ describe('ProviderManager thinking level', () => {
|
|||
});
|
||||
expect(managerOffByDefault.resolveThinkingLevel()).toBe('off');
|
||||
|
||||
const managerWithModeOffAndDefaultThinking = new ProviderManager({
|
||||
config: {
|
||||
providers: {},
|
||||
defaultThinking: true,
|
||||
thinking: { effort: 'medium', mode: 'off' },
|
||||
},
|
||||
});
|
||||
expect(managerWithModeOffAndDefaultThinking.resolveThinkingLevel()).toBe('off');
|
||||
expect(managerWithModeOffAndDefaultThinking.resolveThinkingLevel(' ')).toBe('off');
|
||||
|
||||
const managerWithoutThinking = new ProviderManager({ config: { providers: {} } });
|
||||
expect(managerWithoutThinking.resolveThinkingLevel()).toBe('high');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue