fix(ui): preserve default in thinking cycle (#3153)
This commit is contained in:
@@ -213,6 +213,13 @@ Each of them therefore keeps two things:
|
||||
- a flat mirror (`agents`, `commands`, `skills`, `mcpServers`, `providers`) that
|
||||
tracks the **active** project only.
|
||||
|
||||
Thinking variants keep the effective value in `currentVariant` so existing send
|
||||
paths capture a stable configuration. The transient `currentVariantSelection`
|
||||
distinguishes automatic initialization from a picker or shortcut choosing an
|
||||
explicit override or `Default`; returning to `Default` restores its inherited
|
||||
effective value. Only explicit overrides are stored in the per-session
|
||||
selection store.
|
||||
|
||||
Every loader and mutation takes an explicit directory; omitting it means the
|
||||
active project, which is what non-Settings callers pass. A load for another
|
||||
directory writes the map and leaves the mirror alone, so browsing another
|
||||
|
||||
@@ -268,6 +268,7 @@ describe('useConfigStore provider persistence', () => {
|
||||
currentProviderId: '',
|
||||
currentModelId: '',
|
||||
currentVariant: undefined,
|
||||
currentVariantSelection: { override: undefined, inherited: undefined },
|
||||
selectedProviderId: '',
|
||||
currentAgentName: undefined,
|
||||
agents: [],
|
||||
@@ -525,20 +526,58 @@ describe('useConfigStore provider persistence', () => {
|
||||
expect(state.directoryScoped[DIRECTORY]?.currentVariant).toBe('high');
|
||||
});
|
||||
|
||||
test('cycleCurrentVariant wraps through every model variant', () => {
|
||||
test('cycleCurrentVariant reaches Default, low, and medium from inherited high', () => {
|
||||
useConfigStore.setState({
|
||||
providers: [provider('openai', 'gpt-5.6-sol', { none: {}, low: {}, medium: {}, high: {}, xhigh: {}, max: {} })],
|
||||
currentProviderId: 'openai',
|
||||
currentModelId: 'gpt-5.6-sol',
|
||||
currentVariant: 'high',
|
||||
currentVariantSelection: { override: undefined, inherited: 'high' },
|
||||
directoryScoped: {},
|
||||
});
|
||||
|
||||
const expectedVariants = ['xhigh', 'max', 'none', 'low', 'medium', 'high'];
|
||||
const expectedVariants = ['xhigh', 'max', undefined, 'none', 'low', 'medium', 'high'];
|
||||
for (const expectedVariant of expectedVariants) {
|
||||
useConfigStore.getState().cycleCurrentVariant();
|
||||
expect(useConfigStore.getState().currentVariant).toBe(expectedVariant);
|
||||
expect(useConfigStore.getState().cycleCurrentVariant()).toBe(expectedVariant);
|
||||
expect(useConfigStore.getState().currentVariantSelection.override).toBe(expectedVariant ?? null);
|
||||
}
|
||||
|
||||
useConfigStore.getState().setCurrentVariantOverride('max', 'high');
|
||||
expect(useConfigStore.getState().cycleCurrentVariant()).toBe(undefined);
|
||||
expect(useConfigStore.getState().currentVariant).toBe('high');
|
||||
expect(useConfigStore.getState().currentVariantSelection).toEqual({ override: null, inherited: 'high' });
|
||||
});
|
||||
|
||||
test('cycleCurrentVariant toggles a single variant with Default', () => {
|
||||
useConfigStore.setState({
|
||||
providers: [provider('openai', 'single', { high: {} })],
|
||||
currentProviderId: 'openai',
|
||||
currentModelId: 'single',
|
||||
currentVariant: 'high',
|
||||
currentVariantSelection: { override: null, inherited: 'high' },
|
||||
directoryScoped: {},
|
||||
});
|
||||
|
||||
expect(useConfigStore.getState().cycleCurrentVariant()).toBe('high');
|
||||
expect(useConfigStore.getState().currentVariantSelection.override).toBe('high');
|
||||
expect(useConfigStore.getState().cycleCurrentVariant()).toBe(undefined);
|
||||
expect(useConfigStore.getState().currentVariantSelection.override).toBeNull();
|
||||
expect(useConfigStore.getState().currentVariant).toBe('high');
|
||||
});
|
||||
|
||||
test('an unavailable explicit variant cycles back to Default', () => {
|
||||
useConfigStore.setState({
|
||||
providers: [provider('openai', 'changed', { low: {}, high: {} })],
|
||||
currentProviderId: 'openai',
|
||||
currentModelId: 'changed',
|
||||
currentVariant: 'removed',
|
||||
currentVariantSelection: { override: 'removed', inherited: 'low' },
|
||||
directoryScoped: {},
|
||||
});
|
||||
|
||||
expect(useConfigStore.getState().cycleCurrentVariant()).toBe(undefined);
|
||||
expect(useConfigStore.getState().currentVariant).toBe('low');
|
||||
expect(useConfigStore.getState().currentVariantSelection.override).toBeNull();
|
||||
});
|
||||
|
||||
test('setAgent prefers saved and agent variants before settings default', () => {
|
||||
@@ -716,6 +755,29 @@ describe('useConfigStore provider persistence', () => {
|
||||
expect(state.currentVariant).toBe('high');
|
||||
});
|
||||
|
||||
test('a fresh session applies the settings thinking level instead of the previous override', () => {
|
||||
useConfigStore.setState({
|
||||
activeDirectoryKey: DIRECTORY,
|
||||
providers: [provider('openai', 'gpt-5.5', { low: {}, high: {} })],
|
||||
agents: [testAgent('build')],
|
||||
currentProviderId: 'openai',
|
||||
currentModelId: 'gpt-5.5',
|
||||
currentVariant: 'low',
|
||||
currentVariantSelection: { override: 'low', inherited: 'high' },
|
||||
settingsDefaultModel: 'openai/gpt-5.5',
|
||||
settingsDefaultVariant: 'high',
|
||||
selectionSource: 'manual',
|
||||
directoryScoped: {},
|
||||
});
|
||||
|
||||
useConfigStore.getState().applyDefaultModelAgentSelection();
|
||||
|
||||
const state = useConfigStore.getState();
|
||||
expect(state.currentVariant).toBe('high');
|
||||
expect(state.currentVariantSelection).toEqual({ override: 'high', inherited: 'high' });
|
||||
expect(state.directoryScoped[DIRECTORY]?.currentVariant).toBe('high');
|
||||
});
|
||||
|
||||
test('a thinking level the project model does not offer is ignored', async () => {
|
||||
useConfigStore.setState({
|
||||
activeDirectoryKey: DIRECTORY,
|
||||
@@ -1052,6 +1114,8 @@ describe('useConfigStore provider persistence', () => {
|
||||
useConfigStore.setState({
|
||||
activeDirectoryKey: DIRECTORY,
|
||||
selectionSource: 'manual',
|
||||
currentVariant: 'high',
|
||||
currentVariantSelection: { override: 'high', inherited: 'medium' },
|
||||
opencodeDefaultAgent: 'active-default',
|
||||
opencodeDefaultModel: 'active/model',
|
||||
directoryScoped: {
|
||||
@@ -1073,6 +1137,7 @@ describe('useConfigStore provider persistence', () => {
|
||||
agents: [testAgent('other-agent')],
|
||||
currentProviderId: 'other',
|
||||
currentModelId: 'other-model',
|
||||
currentVariant: 'low',
|
||||
currentAgentName: 'other-agent',
|
||||
selectedProviderId: 'other',
|
||||
agentModelSelections: {},
|
||||
@@ -1092,6 +1157,7 @@ describe('useConfigStore provider persistence', () => {
|
||||
expect(state.selectionSource).toBe('auto');
|
||||
expect(state.opencodeDefaultAgent).toBe('other-default');
|
||||
expect(state.opencodeDefaultModel).toBe('other/model');
|
||||
expect(state.currentVariantSelection).toEqual({ override: undefined, inherited: 'low' });
|
||||
});
|
||||
|
||||
test('sync config without defaults clears stored OpenCode defaults without changing manual selection', () => {
|
||||
|
||||
@@ -885,6 +885,11 @@ interface DirectoryScopedConfig {
|
||||
selectionSource?: "auto" | "manual";
|
||||
}
|
||||
|
||||
type CurrentVariantSelection = {
|
||||
override: string | null | undefined;
|
||||
inherited: string | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lift the active directory's cached provider/agent snapshot into the top-level
|
||||
* fields the pickers read (`providers`, `agents`, selections), so a cold start
|
||||
@@ -1006,6 +1011,7 @@ interface ConfigStore {
|
||||
currentProviderId: string;
|
||||
currentModelId: string;
|
||||
currentVariant: string | undefined;
|
||||
currentVariantSelection: CurrentVariantSelection;
|
||||
currentAgentName: string | undefined;
|
||||
selectedProviderId: string;
|
||||
agentModelSelections: { [agentName: string]: { providerId: string; modelId: string } };
|
||||
@@ -1098,7 +1104,8 @@ interface ConfigStore {
|
||||
setProvider: (providerId: string) => void;
|
||||
setModel: (modelId: string) => void;
|
||||
setCurrentVariant: (variant: string | undefined) => void;
|
||||
cycleCurrentVariant: () => void;
|
||||
setCurrentVariantOverride: (override: string | null | undefined, inherited: string | undefined) => void;
|
||||
cycleCurrentVariant: () => string | undefined;
|
||||
getCurrentModelVariants: () => string[];
|
||||
setAgent: (agentName: string | undefined) => void;
|
||||
applyDefaultModelAgentSelection: (options?: { projectDefaultModel?: string; projectDefaultVariant?: string }) => void;
|
||||
@@ -1171,6 +1178,7 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
currentProviderId: "",
|
||||
currentModelId: "",
|
||||
currentVariant: undefined,
|
||||
currentVariantSelection: { override: undefined, inherited: undefined },
|
||||
currentAgentName: undefined,
|
||||
selectedProviderId: "",
|
||||
agentModelSelections: {},
|
||||
@@ -1437,6 +1445,7 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
currentProviderId: snapshot.currentProviderId,
|
||||
currentModelId: snapshot.currentModelId,
|
||||
currentVariant: snapshot.currentVariant,
|
||||
currentVariantSelection: { override: undefined, inherited: snapshot.currentVariant },
|
||||
currentAgentName: snapshot.currentAgentName,
|
||||
selectedProviderId: snapshot.selectedProviderId,
|
||||
agentModelSelections: snapshot.agentModelSelections,
|
||||
@@ -1453,6 +1462,7 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
agents: [],
|
||||
currentProviderId: "",
|
||||
currentModelId: "",
|
||||
currentVariantSelection: { override: undefined, inherited: undefined },
|
||||
currentAgentName: undefined,
|
||||
selectedProviderId: "",
|
||||
agentModelSelections: {},
|
||||
@@ -1847,13 +1857,22 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
},
|
||||
|
||||
setCurrentVariant: (variant: string | undefined) => {
|
||||
get().setCurrentVariantOverride(undefined, variant);
|
||||
},
|
||||
|
||||
setCurrentVariantOverride: (override, inherited) => {
|
||||
set((state) => {
|
||||
if (state.currentVariant === variant) {
|
||||
const currentVariant = override ?? inherited;
|
||||
if (
|
||||
state.currentVariant === currentVariant
|
||||
&& state.currentVariantSelection.override === override
|
||||
&& state.currentVariantSelection.inherited === inherited
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const directoryKey = state.activeDirectoryKey;
|
||||
const baseSnapshot: DirectoryScopedConfig = state.directoryScoped[directoryKey] ?? {
|
||||
const baseSnapshot = state.directoryScoped[directoryKey] ?? {
|
||||
providers: state.providers,
|
||||
agents: state.agents,
|
||||
currentProviderId: state.currentProviderId,
|
||||
@@ -1865,18 +1884,17 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
defaultProviders: state.defaultProviders,
|
||||
};
|
||||
|
||||
const nextSnapshot: DirectoryScopedConfig = {
|
||||
...baseSnapshot,
|
||||
currentVariant: variant,
|
||||
selectionSource: "manual",
|
||||
};
|
||||
|
||||
return {
|
||||
currentVariant: variant,
|
||||
currentVariant,
|
||||
currentVariantSelection: { override, inherited },
|
||||
selectionSource: "manual",
|
||||
directoryScoped: {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: nextSnapshot,
|
||||
[directoryKey]: {
|
||||
...baseSnapshot,
|
||||
currentVariant,
|
||||
selectionSource: "manual",
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1894,22 +1912,26 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
cycleCurrentVariant: () => {
|
||||
const variantKeys = get().getCurrentModelVariants();
|
||||
if (variantKeys.length === 0) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const current = get().currentVariant;
|
||||
if (!current) {
|
||||
get().setCurrentVariant(variantKeys[0]);
|
||||
return;
|
||||
const state = get();
|
||||
const currentOverride = state.currentVariantSelection.override;
|
||||
const inheritedVariant = state.currentVariantSelection.inherited ?? state.currentVariant;
|
||||
const currentVariant = currentOverride === undefined
|
||||
? state.currentVariant
|
||||
: currentOverride;
|
||||
let nextOverride: string | null;
|
||||
|
||||
if (currentVariant === null || currentVariant === undefined) {
|
||||
nextOverride = variantKeys[0];
|
||||
} else {
|
||||
const index = variantKeys.indexOf(currentVariant);
|
||||
nextOverride = index >= 0 ? (variantKeys[index + 1] ?? null) : null;
|
||||
}
|
||||
|
||||
const index = variantKeys.indexOf(current);
|
||||
if (index === -1) {
|
||||
get().setCurrentVariant(variantKeys[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
get().setCurrentVariant(variantKeys[(index + 1) % variantKeys.length]);
|
||||
get().setCurrentVariantOverride(nextOverride, inheritedVariant);
|
||||
return nextOverride ?? undefined;
|
||||
},
|
||||
|
||||
setSelectedProvider: (providerId: string) => {
|
||||
@@ -2659,6 +2681,10 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
nextState.currentProviderId = resolvedProviderId;
|
||||
nextState.currentModelId = resolvedModelId;
|
||||
nextState.currentVariant = resolvedVariant;
|
||||
nextState.currentVariantSelection = {
|
||||
override: resolvedVariant,
|
||||
inherited: resolvedVariant,
|
||||
};
|
||||
}
|
||||
|
||||
return nextState;
|
||||
|
||||
Reference in New Issue
Block a user