fix(ui): preserve default in thinking cycle (#3153)
This commit is contained in:
@@ -324,7 +324,9 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
|||||||
const providers = useConfigStore((state) => state.providers);
|
const providers = useConfigStore((state) => state.providers);
|
||||||
const currentProviderId = useConfigStore((state) => state.currentProviderId);
|
const currentProviderId = useConfigStore((state) => state.currentProviderId);
|
||||||
const currentModelId = useConfigStore((state) => state.currentModelId);
|
const currentModelId = useConfigStore((state) => state.currentModelId);
|
||||||
const currentVariant = useConfigStore((state) => state.currentVariant);
|
const effectiveCurrentVariant = useConfigStore((state) => state.currentVariant);
|
||||||
|
const currentVariantSelection = useConfigStore((state) => state.currentVariantSelection);
|
||||||
|
const currentVariant = currentVariantSelection.override ?? undefined;
|
||||||
const currentAgentName = useConfigStore((state) => state.currentAgentName);
|
const currentAgentName = useConfigStore((state) => state.currentAgentName);
|
||||||
const settingsDefaultVariant = useConfigStore((state) => state.settingsDefaultVariant);
|
const settingsDefaultVariant = useConfigStore((state) => state.settingsDefaultVariant);
|
||||||
const settingsDefaultAgent = useConfigStore((state) => state.settingsDefaultAgent);
|
const settingsDefaultAgent = useConfigStore((state) => state.settingsDefaultAgent);
|
||||||
@@ -332,6 +334,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
|||||||
const setSelectedProvider = useConfigStore((state) => state.setSelectedProvider);
|
const setSelectedProvider = useConfigStore((state) => state.setSelectedProvider);
|
||||||
const setModel = useConfigStore((state) => state.setModel);
|
const setModel = useConfigStore((state) => state.setModel);
|
||||||
const setCurrentVariant = useConfigStore((state) => state.setCurrentVariant);
|
const setCurrentVariant = useConfigStore((state) => state.setCurrentVariant);
|
||||||
|
const setCurrentVariantOverride = useConfigStore((state) => state.setCurrentVariantOverride);
|
||||||
const getCurrentModelVariants = useConfigStore((state) => state.getCurrentModelVariants);
|
const getCurrentModelVariants = useConfigStore((state) => state.getCurrentModelVariants);
|
||||||
const setAgent = useConfigStore((state) => state.setAgent);
|
const setAgent = useConfigStore((state) => state.setAgent);
|
||||||
const getCurrentProvider = useConfigStore((state) => state.getCurrentProvider);
|
const getCurrentProvider = useConfigStore((state) => state.getCurrentProvider);
|
||||||
@@ -693,6 +696,30 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
|||||||
return variants ? Object.keys(variants) : [];
|
return variants ? Object.keys(variants) : [];
|
||||||
}, [providers]);
|
}, [providers]);
|
||||||
|
|
||||||
|
const resolveInheritedVariantForModel = React.useCallback((providerId: string, modelId: string, agentName?: string | null) => {
|
||||||
|
const variantOptions = getModelVariantOptions(providerId, modelId);
|
||||||
|
if (variantOptions.length === 0) return undefined;
|
||||||
|
|
||||||
|
let currentInherited: string | undefined;
|
||||||
|
if (currentProviderId === providerId && currentModelId === modelId) {
|
||||||
|
currentInherited = currentVariantSelection.inherited
|
||||||
|
?? (currentVariantSelection.override === null || currentVariantSelection.override === undefined
|
||||||
|
? effectiveCurrentVariant
|
||||||
|
: undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveAgentName = agentName ?? uiAgentName ?? currentAgentName;
|
||||||
|
const agent = effectiveAgentName ? agents.find((candidate) => candidate.name === effectiveAgentName) : undefined;
|
||||||
|
const agentVariant = (
|
||||||
|
agent?.model?.providerID === providerId
|
||||||
|
&& agent.model.modelID === modelId
|
||||||
|
) ? agent.variant : undefined;
|
||||||
|
const candidates = currentSessionId
|
||||||
|
? [agentVariant, settingsDefaultVariant, currentInherited]
|
||||||
|
: [currentInherited, agentVariant, settingsDefaultVariant];
|
||||||
|
return candidates.find((candidate) => candidate !== undefined && variantOptions.includes(candidate));
|
||||||
|
}, [agents, currentAgentName, currentModelId, currentProviderId, currentSessionId, currentVariantSelection, effectiveCurrentVariant, getModelVariantOptions, settingsDefaultVariant, uiAgentName]);
|
||||||
|
|
||||||
const resolveModelVariantSelection = React.useCallback((providerId: string, modelId: string) => {
|
const resolveModelVariantSelection = React.useCallback((providerId: string, modelId: string) => {
|
||||||
const variantOptions = getModelVariantOptions(providerId, modelId);
|
const variantOptions = getModelVariantOptions(providerId, modelId);
|
||||||
if (variantOptions.length === 0) {
|
if (variantOptions.length === 0) {
|
||||||
@@ -711,10 +738,6 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
|||||||
return currentVariant;
|
return currentVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentSessionId && settingsDefaultVariant && variantOptions.includes(settingsDefaultVariant)) {
|
|
||||||
return settingsDefaultVariant;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [
|
}, [
|
||||||
currentAgentName,
|
currentAgentName,
|
||||||
@@ -724,7 +747,6 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
|||||||
currentVariant,
|
currentVariant,
|
||||||
getAgentModelVariantForSession,
|
getAgentModelVariantForSession,
|
||||||
getModelVariantOptions,
|
getModelVariantOptions,
|
||||||
settingsDefaultVariant,
|
|
||||||
uiAgentName,
|
uiAgentName,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -748,7 +770,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
manualVariantSelectionRef.current = true;
|
manualVariantSelectionRef.current = true;
|
||||||
setCurrentVariant(variant);
|
setCurrentVariantOverride(
|
||||||
|
variant ?? null,
|
||||||
|
resolveInheritedVariantForModel(providerId, modelId, agentNameOverride),
|
||||||
|
);
|
||||||
addRecentEffort(providerId, modelId, variant);
|
addRecentEffort(providerId, modelId, variant);
|
||||||
|
|
||||||
const effectiveAgentName = agentNameOverride ?? resolveLiveAgentName();
|
const effectiveAgentName = agentNameOverride ?? resolveLiveAgentName();
|
||||||
@@ -759,9 +784,11 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
|||||||
addRecentEffort,
|
addRecentEffort,
|
||||||
currentSessionId,
|
currentSessionId,
|
||||||
getModelVariantOptions,
|
getModelVariantOptions,
|
||||||
|
resolveInheritedVariantForModel,
|
||||||
resolveLiveAgentName,
|
resolveLiveAgentName,
|
||||||
saveAgentModelVariantForSession,
|
saveAgentModelVariantForSession,
|
||||||
setCurrentVariant,
|
setCurrentVariant,
|
||||||
|
setCurrentVariantOverride,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const applyModelSelectionWithVariant = React.useCallback((providerId: string, modelId: string, variant: string | undefined, agentNameOverride?: string | null) => {
|
const applyModelSelectionWithVariant = React.useCallback((providerId: string, modelId: string, variant: string | undefined, agentNameOverride?: string | null) => {
|
||||||
@@ -1121,18 +1148,21 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentVariant && !availableVariants.includes(currentVariant)) {
|
if (currentVariant && !availableVariants.includes(currentVariant)) {
|
||||||
setCurrentVariant(undefined);
|
setCurrentVariantOverride(
|
||||||
|
null,
|
||||||
|
resolveInheritedVariantForModel(currentProviderId, currentModelId),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draft state (no session yet): seed from settings default, but don't override
|
// Draft state (no session yet): seed from settings default, but don't override
|
||||||
// user selection while drafting.
|
// user selection while drafting.
|
||||||
if (!currentSessionId) {
|
if (!currentSessionId) {
|
||||||
if (!currentVariant && !manualVariantSelectionRef.current) {
|
if (currentVariantSelection.override === undefined && !manualVariantSelectionRef.current) {
|
||||||
const desired = settingsDefaultVariant && availableVariants.includes(settingsDefaultVariant)
|
const desired = settingsDefaultVariant && availableVariants.includes(settingsDefaultVariant)
|
||||||
? settingsDefaultVariant
|
? settingsDefaultVariant
|
||||||
: undefined;
|
: undefined;
|
||||||
setCurrentVariant(desired);
|
setCurrentVariantOverride(desired ?? null, desired);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1144,13 +1174,14 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
|||||||
currentModelId,
|
currentModelId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const resolvedSaved = savedVariant && availableVariants.includes(savedVariant)
|
const inheritedVariant = resolveInheritedVariantForModel(currentProviderId, currentModelId);
|
||||||
? savedVariant
|
if (savedVariant && availableVariants.includes(savedVariant)) {
|
||||||
: settingsDefaultVariant && availableVariants.includes(settingsDefaultVariant)
|
setCurrentVariantOverride(savedVariant, inheritedVariant);
|
||||||
? settingsDefaultVariant
|
} else if (currentVariantSelection.override === null) {
|
||||||
: undefined;
|
setCurrentVariantOverride(null, inheritedVariant);
|
||||||
|
} else {
|
||||||
setCurrentVariant(resolvedSaved);
|
setCurrentVariant(inheritedVariant);
|
||||||
|
}
|
||||||
manualVariantSelectionRef.current = false;
|
manualVariantSelectionRef.current = false;
|
||||||
}, [
|
}, [
|
||||||
availableVariants,
|
availableVariants,
|
||||||
@@ -1160,8 +1191,12 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
|||||||
currentProviderId,
|
currentProviderId,
|
||||||
currentModelId,
|
currentModelId,
|
||||||
currentVariant,
|
currentVariant,
|
||||||
|
currentVariantSelection.override,
|
||||||
|
effectiveCurrentVariant,
|
||||||
getAgentModelVariantForSession,
|
getAgentModelVariantForSession,
|
||||||
|
resolveInheritedVariantForModel,
|
||||||
setCurrentVariant,
|
setCurrentVariant,
|
||||||
|
setCurrentVariantOverride,
|
||||||
settingsDefaultVariant,
|
settingsDefaultVariant,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export const DefaultsSettings: React.FC = () => {
|
|||||||
const setModel = useConfigStore((state) => state.setModel);
|
const setModel = useConfigStore((state) => state.setModel);
|
||||||
const setAgent = useConfigStore((state) => state.setAgent);
|
const setAgent = useConfigStore((state) => state.setAgent);
|
||||||
const setCurrentVariant = useConfigStore((state) => state.setCurrentVariant);
|
const setCurrentVariant = useConfigStore((state) => state.setCurrentVariant);
|
||||||
|
const setCurrentVariantOverride = useConfigStore((state) => state.setCurrentVariantOverride);
|
||||||
const setSettingsDefaultModel = useConfigStore((state) => state.setSettingsDefaultModel);
|
const setSettingsDefaultModel = useConfigStore((state) => state.setSettingsDefaultModel);
|
||||||
const setSettingsDefaultVariant = useConfigStore((state) => state.setSettingsDefaultVariant);
|
const setSettingsDefaultVariant = useConfigStore((state) => state.setSettingsDefaultVariant);
|
||||||
const setSettingsDefaultAgent = useConfigStore((state) => state.setSettingsDefaultAgent);
|
const setSettingsDefaultAgent = useConfigStore((state) => state.setSettingsDefaultAgent);
|
||||||
@@ -210,7 +211,7 @@ export const DefaultsSettings: React.FC = () => {
|
|||||||
setDefaultVariant(newValue);
|
setDefaultVariant(newValue);
|
||||||
setSettingsDefaultVariant(newValue);
|
setSettingsDefaultVariant(newValue);
|
||||||
if (!chatHasOwnModel) {
|
if (!chatHasOwnModel) {
|
||||||
setCurrentVariant(newValue);
|
setCurrentVariantOverride(newValue ?? null, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -219,7 +220,7 @@ export const DefaultsSettings: React.FC = () => {
|
|||||||
console.warn('Failed to save default variant:', error);
|
console.warn('Failed to save default variant:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[chatHasOwnModel, setCurrentVariant, setSettingsDefaultVariant]
|
[chatHasOwnModel, setCurrentVariantOverride, setSettingsDefaultVariant]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAgentChange = React.useCallback(
|
const handleAgentChange = React.useCallback(
|
||||||
|
|||||||
@@ -273,16 +273,16 @@ export const useKeyboardShortcuts = () => {
|
|||||||
if (state.isSettingsDialogOpen || hasOverlay) return false;
|
if (state.isSettingsDialogOpen || hasOverlay) return false;
|
||||||
const config = useConfigStore.getState();
|
const config = useConfigStore.getState();
|
||||||
if (config.getCurrentModelVariants().length === 0) return false;
|
if (config.getCurrentModelVariants().length === 0) return false;
|
||||||
config.cycleCurrentVariant();
|
const nextVariantOverride = config.cycleCurrentVariant();
|
||||||
const sessionId = useSessionUIStore.getState().currentSessionId;
|
const sessionId = useSessionUIStore.getState().currentSessionId;
|
||||||
const { currentVariant, currentAgentName, currentProviderId, currentModelId } = useConfigStore.getState();
|
const { currentAgentName, currentProviderId, currentModelId } = useConfigStore.getState();
|
||||||
if (sessionId && currentAgentName && currentProviderId && currentModelId) {
|
if (sessionId && currentAgentName && currentProviderId && currentModelId) {
|
||||||
useSelectionStore.getState().saveAgentModelVariantForSession(
|
useSelectionStore.getState().saveAgentModelVariantForSession(
|
||||||
sessionId,
|
sessionId,
|
||||||
currentAgentName,
|
currentAgentName,
|
||||||
currentProviderId,
|
currentProviderId,
|
||||||
currentModelId,
|
currentModelId,
|
||||||
currentVariant,
|
nextVariantOverride,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -71,10 +71,9 @@ export const useMiniChatKeyboardShortcuts = () => {
|
|||||||
const configState = useConfigStore.getState();
|
const configState = useConfigStore.getState();
|
||||||
if (configState.getCurrentModelVariants().length === 0) return false;
|
if (configState.getCurrentModelVariants().length === 0) return false;
|
||||||
|
|
||||||
configState.cycleCurrentVariant();
|
const nextVariantOverride = configState.cycleCurrentVariant();
|
||||||
const sessionId = useSessionUIStore.getState().currentSessionId;
|
const sessionId = useSessionUIStore.getState().currentSessionId;
|
||||||
const {
|
const {
|
||||||
currentVariant,
|
|
||||||
currentAgentName,
|
currentAgentName,
|
||||||
currentProviderId,
|
currentProviderId,
|
||||||
currentModelId,
|
currentModelId,
|
||||||
@@ -85,7 +84,7 @@ export const useMiniChatKeyboardShortcuts = () => {
|
|||||||
currentAgentName,
|
currentAgentName,
|
||||||
currentProviderId,
|
currentProviderId,
|
||||||
currentModelId,
|
currentModelId,
|
||||||
currentVariant,
|
nextVariantOverride,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -213,6 +213,13 @@ Each of them therefore keeps two things:
|
|||||||
- a flat mirror (`agents`, `commands`, `skills`, `mcpServers`, `providers`) that
|
- a flat mirror (`agents`, `commands`, `skills`, `mcpServers`, `providers`) that
|
||||||
tracks the **active** project only.
|
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
|
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
|
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
|
directory writes the map and leaves the mirror alone, so browsing another
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ describe('useConfigStore provider persistence', () => {
|
|||||||
currentProviderId: '',
|
currentProviderId: '',
|
||||||
currentModelId: '',
|
currentModelId: '',
|
||||||
currentVariant: undefined,
|
currentVariant: undefined,
|
||||||
|
currentVariantSelection: { override: undefined, inherited: undefined },
|
||||||
selectedProviderId: '',
|
selectedProviderId: '',
|
||||||
currentAgentName: undefined,
|
currentAgentName: undefined,
|
||||||
agents: [],
|
agents: [],
|
||||||
@@ -525,20 +526,58 @@ describe('useConfigStore provider persistence', () => {
|
|||||||
expect(state.directoryScoped[DIRECTORY]?.currentVariant).toBe('high');
|
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({
|
useConfigStore.setState({
|
||||||
providers: [provider('openai', 'gpt-5.6-sol', { none: {}, low: {}, medium: {}, high: {}, xhigh: {}, max: {} })],
|
providers: [provider('openai', 'gpt-5.6-sol', { none: {}, low: {}, medium: {}, high: {}, xhigh: {}, max: {} })],
|
||||||
currentProviderId: 'openai',
|
currentProviderId: 'openai',
|
||||||
currentModelId: 'gpt-5.6-sol',
|
currentModelId: 'gpt-5.6-sol',
|
||||||
currentVariant: 'high',
|
currentVariant: 'high',
|
||||||
|
currentVariantSelection: { override: undefined, inherited: 'high' },
|
||||||
directoryScoped: {},
|
directoryScoped: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedVariants = ['xhigh', 'max', 'none', 'low', 'medium', 'high'];
|
const expectedVariants = ['xhigh', 'max', undefined, 'none', 'low', 'medium', 'high'];
|
||||||
for (const expectedVariant of expectedVariants) {
|
for (const expectedVariant of expectedVariants) {
|
||||||
useConfigStore.getState().cycleCurrentVariant();
|
expect(useConfigStore.getState().cycleCurrentVariant()).toBe(expectedVariant);
|
||||||
expect(useConfigStore.getState().currentVariant).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', () => {
|
test('setAgent prefers saved and agent variants before settings default', () => {
|
||||||
@@ -716,6 +755,29 @@ describe('useConfigStore provider persistence', () => {
|
|||||||
expect(state.currentVariant).toBe('high');
|
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 () => {
|
test('a thinking level the project model does not offer is ignored', async () => {
|
||||||
useConfigStore.setState({
|
useConfigStore.setState({
|
||||||
activeDirectoryKey: DIRECTORY,
|
activeDirectoryKey: DIRECTORY,
|
||||||
@@ -1052,6 +1114,8 @@ describe('useConfigStore provider persistence', () => {
|
|||||||
useConfigStore.setState({
|
useConfigStore.setState({
|
||||||
activeDirectoryKey: DIRECTORY,
|
activeDirectoryKey: DIRECTORY,
|
||||||
selectionSource: 'manual',
|
selectionSource: 'manual',
|
||||||
|
currentVariant: 'high',
|
||||||
|
currentVariantSelection: { override: 'high', inherited: 'medium' },
|
||||||
opencodeDefaultAgent: 'active-default',
|
opencodeDefaultAgent: 'active-default',
|
||||||
opencodeDefaultModel: 'active/model',
|
opencodeDefaultModel: 'active/model',
|
||||||
directoryScoped: {
|
directoryScoped: {
|
||||||
@@ -1073,6 +1137,7 @@ describe('useConfigStore provider persistence', () => {
|
|||||||
agents: [testAgent('other-agent')],
|
agents: [testAgent('other-agent')],
|
||||||
currentProviderId: 'other',
|
currentProviderId: 'other',
|
||||||
currentModelId: 'other-model',
|
currentModelId: 'other-model',
|
||||||
|
currentVariant: 'low',
|
||||||
currentAgentName: 'other-agent',
|
currentAgentName: 'other-agent',
|
||||||
selectedProviderId: 'other',
|
selectedProviderId: 'other',
|
||||||
agentModelSelections: {},
|
agentModelSelections: {},
|
||||||
@@ -1092,6 +1157,7 @@ describe('useConfigStore provider persistence', () => {
|
|||||||
expect(state.selectionSource).toBe('auto');
|
expect(state.selectionSource).toBe('auto');
|
||||||
expect(state.opencodeDefaultAgent).toBe('other-default');
|
expect(state.opencodeDefaultAgent).toBe('other-default');
|
||||||
expect(state.opencodeDefaultModel).toBe('other/model');
|
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', () => {
|
test('sync config without defaults clears stored OpenCode defaults without changing manual selection', () => {
|
||||||
|
|||||||
@@ -885,6 +885,11 @@ interface DirectoryScopedConfig {
|
|||||||
selectionSource?: "auto" | "manual";
|
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
|
* Lift the active directory's cached provider/agent snapshot into the top-level
|
||||||
* fields the pickers read (`providers`, `agents`, selections), so a cold start
|
* fields the pickers read (`providers`, `agents`, selections), so a cold start
|
||||||
@@ -1006,6 +1011,7 @@ interface ConfigStore {
|
|||||||
currentProviderId: string;
|
currentProviderId: string;
|
||||||
currentModelId: string;
|
currentModelId: string;
|
||||||
currentVariant: string | undefined;
|
currentVariant: string | undefined;
|
||||||
|
currentVariantSelection: CurrentVariantSelection;
|
||||||
currentAgentName: string | undefined;
|
currentAgentName: string | undefined;
|
||||||
selectedProviderId: string;
|
selectedProviderId: string;
|
||||||
agentModelSelections: { [agentName: string]: { providerId: string; modelId: string } };
|
agentModelSelections: { [agentName: string]: { providerId: string; modelId: string } };
|
||||||
@@ -1098,7 +1104,8 @@ interface ConfigStore {
|
|||||||
setProvider: (providerId: string) => void;
|
setProvider: (providerId: string) => void;
|
||||||
setModel: (modelId: string) => void;
|
setModel: (modelId: string) => void;
|
||||||
setCurrentVariant: (variant: string | undefined) => void;
|
setCurrentVariant: (variant: string | undefined) => void;
|
||||||
cycleCurrentVariant: () => void;
|
setCurrentVariantOverride: (override: string | null | undefined, inherited: string | undefined) => void;
|
||||||
|
cycleCurrentVariant: () => string | undefined;
|
||||||
getCurrentModelVariants: () => string[];
|
getCurrentModelVariants: () => string[];
|
||||||
setAgent: (agentName: string | undefined) => void;
|
setAgent: (agentName: string | undefined) => void;
|
||||||
applyDefaultModelAgentSelection: (options?: { projectDefaultModel?: string; projectDefaultVariant?: string }) => void;
|
applyDefaultModelAgentSelection: (options?: { projectDefaultModel?: string; projectDefaultVariant?: string }) => void;
|
||||||
@@ -1171,6 +1178,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
currentProviderId: "",
|
currentProviderId: "",
|
||||||
currentModelId: "",
|
currentModelId: "",
|
||||||
currentVariant: undefined,
|
currentVariant: undefined,
|
||||||
|
currentVariantSelection: { override: undefined, inherited: undefined },
|
||||||
currentAgentName: undefined,
|
currentAgentName: undefined,
|
||||||
selectedProviderId: "",
|
selectedProviderId: "",
|
||||||
agentModelSelections: {},
|
agentModelSelections: {},
|
||||||
@@ -1437,6 +1445,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
currentProviderId: snapshot.currentProviderId,
|
currentProviderId: snapshot.currentProviderId,
|
||||||
currentModelId: snapshot.currentModelId,
|
currentModelId: snapshot.currentModelId,
|
||||||
currentVariant: snapshot.currentVariant,
|
currentVariant: snapshot.currentVariant,
|
||||||
|
currentVariantSelection: { override: undefined, inherited: snapshot.currentVariant },
|
||||||
currentAgentName: snapshot.currentAgentName,
|
currentAgentName: snapshot.currentAgentName,
|
||||||
selectedProviderId: snapshot.selectedProviderId,
|
selectedProviderId: snapshot.selectedProviderId,
|
||||||
agentModelSelections: snapshot.agentModelSelections,
|
agentModelSelections: snapshot.agentModelSelections,
|
||||||
@@ -1453,6 +1462,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
agents: [],
|
agents: [],
|
||||||
currentProviderId: "",
|
currentProviderId: "",
|
||||||
currentModelId: "",
|
currentModelId: "",
|
||||||
|
currentVariantSelection: { override: undefined, inherited: undefined },
|
||||||
currentAgentName: undefined,
|
currentAgentName: undefined,
|
||||||
selectedProviderId: "",
|
selectedProviderId: "",
|
||||||
agentModelSelections: {},
|
agentModelSelections: {},
|
||||||
@@ -1847,13 +1857,22 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
setCurrentVariant: (variant: string | undefined) => {
|
setCurrentVariant: (variant: string | undefined) => {
|
||||||
|
get().setCurrentVariantOverride(undefined, variant);
|
||||||
|
},
|
||||||
|
|
||||||
|
setCurrentVariantOverride: (override, inherited) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
if (state.currentVariant === variant) {
|
const currentVariant = override ?? inherited;
|
||||||
|
if (
|
||||||
|
state.currentVariant === currentVariant
|
||||||
|
&& state.currentVariantSelection.override === override
|
||||||
|
&& state.currentVariantSelection.inherited === inherited
|
||||||
|
) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
const directoryKey = state.activeDirectoryKey;
|
const directoryKey = state.activeDirectoryKey;
|
||||||
const baseSnapshot: DirectoryScopedConfig = state.directoryScoped[directoryKey] ?? {
|
const baseSnapshot = state.directoryScoped[directoryKey] ?? {
|
||||||
providers: state.providers,
|
providers: state.providers,
|
||||||
agents: state.agents,
|
agents: state.agents,
|
||||||
currentProviderId: state.currentProviderId,
|
currentProviderId: state.currentProviderId,
|
||||||
@@ -1865,18 +1884,17 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
defaultProviders: state.defaultProviders,
|
defaultProviders: state.defaultProviders,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextSnapshot: DirectoryScopedConfig = {
|
|
||||||
...baseSnapshot,
|
|
||||||
currentVariant: variant,
|
|
||||||
selectionSource: "manual",
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentVariant: variant,
|
currentVariant,
|
||||||
|
currentVariantSelection: { override, inherited },
|
||||||
selectionSource: "manual",
|
selectionSource: "manual",
|
||||||
directoryScoped: {
|
directoryScoped: {
|
||||||
...state.directoryScoped,
|
...state.directoryScoped,
|
||||||
[directoryKey]: nextSnapshot,
|
[directoryKey]: {
|
||||||
|
...baseSnapshot,
|
||||||
|
currentVariant,
|
||||||
|
selectionSource: "manual",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -1894,22 +1912,26 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
cycleCurrentVariant: () => {
|
cycleCurrentVariant: () => {
|
||||||
const variantKeys = get().getCurrentModelVariants();
|
const variantKeys = get().getCurrentModelVariants();
|
||||||
if (variantKeys.length === 0) {
|
if (variantKeys.length === 0) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = get().currentVariant;
|
const state = get();
|
||||||
if (!current) {
|
const currentOverride = state.currentVariantSelection.override;
|
||||||
get().setCurrentVariant(variantKeys[0]);
|
const inheritedVariant = state.currentVariantSelection.inherited ?? state.currentVariant;
|
||||||
return;
|
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);
|
get().setCurrentVariantOverride(nextOverride, inheritedVariant);
|
||||||
if (index === -1) {
|
return nextOverride ?? undefined;
|
||||||
get().setCurrentVariant(variantKeys[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
get().setCurrentVariant(variantKeys[(index + 1) % variantKeys.length]);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setSelectedProvider: (providerId: string) => {
|
setSelectedProvider: (providerId: string) => {
|
||||||
@@ -2659,6 +2681,10 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
nextState.currentProviderId = resolvedProviderId;
|
nextState.currentProviderId = resolvedProviderId;
|
||||||
nextState.currentModelId = resolvedModelId;
|
nextState.currentModelId = resolvedModelId;
|
||||||
nextState.currentVariant = resolvedVariant;
|
nextState.currentVariant = resolvedVariant;
|
||||||
|
nextState.currentVariantSelection = {
|
||||||
|
override: resolvedVariant,
|
||||||
|
inherited: resolvedVariant,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextState;
|
return nextState;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { togglePermissionAutoAccept } from "../../components/chat/permissionAuto
|
|||||||
const storage = new Map<string, string>()
|
const storage = new Map<string, string>()
|
||||||
const createSessionCalls: Array<{ title?: string; directory: string | null; parentID: string | null; metadata?: unknown }> = []
|
const createSessionCalls: Array<{ title?: string; directory: string | null; parentID: string | null; metadata?: unknown }> = []
|
||||||
const permissionAutoAcceptCalls: Array<[string, boolean]> = []
|
const permissionAutoAcceptCalls: Array<[string, boolean]> = []
|
||||||
|
const savedVariantCalls: Array<string | undefined> = []
|
||||||
|
let configVariantOverride: string | null | undefined
|
||||||
// Sync's session→directory index. `createSession` writes it, and directory
|
// Sync's session→directory index. `createSession` writes it, and directory
|
||||||
// resolution reads it as the authoritative source, so the mock has to keep one.
|
// resolution reads it as the authoritative source, so the mock has to keep one.
|
||||||
const sessionDirectoryRegistry = new Map<string, string>()
|
const sessionDirectoryRegistry = new Map<string, string>()
|
||||||
@@ -96,6 +98,9 @@ mock.module("@/stores/useConfigStore", () => ({
|
|||||||
useConfigStore: {
|
useConfigStore: {
|
||||||
getState: () => ({
|
getState: () => ({
|
||||||
currentAgentName: "agent-default",
|
currentAgentName: "agent-default",
|
||||||
|
currentProviderId: "provider",
|
||||||
|
currentModelId: "model",
|
||||||
|
currentVariantSelection: { override: configVariantOverride, inherited: "high" },
|
||||||
agents: [],
|
agents: [],
|
||||||
activateDirectory: mock(async () => undefined),
|
activateDirectory: mock(async () => undefined),
|
||||||
applyDefaultModelAgentSelection: mock(() => undefined),
|
applyDefaultModelAgentSelection: mock(() => undefined),
|
||||||
@@ -170,7 +175,9 @@ mock.module("../selection-store", () => ({
|
|||||||
saveSessionModelSelection: () => undefined,
|
saveSessionModelSelection: () => undefined,
|
||||||
saveSessionAgentSelection: () => undefined,
|
saveSessionAgentSelection: () => undefined,
|
||||||
saveAgentModelForSession: () => undefined,
|
saveAgentModelForSession: () => undefined,
|
||||||
saveAgentModelVariantForSession: () => undefined,
|
saveAgentModelVariantForSession: (_sessionId: string, _agent: string, _provider: string, _model: string, variant: string | undefined) => {
|
||||||
|
savedVariantCalls.push(variant)
|
||||||
|
},
|
||||||
getSessionAgentSelection: () => null,
|
getSessionAgentSelection: () => null,
|
||||||
getSessionModelSelection: () => null,
|
getSessionModelSelection: () => null,
|
||||||
getAgentModelForSession: () => null,
|
getAgentModelForSession: () => null,
|
||||||
@@ -348,6 +355,8 @@ describe("issue 2039 draft auto-accept", () => {
|
|||||||
createSessionCalls.length = 0
|
createSessionCalls.length = 0
|
||||||
sessionDirectoryRegistry.clear()
|
sessionDirectoryRegistry.clear()
|
||||||
permissionAutoAcceptCalls.length = 0
|
permissionAutoAcceptCalls.length = 0
|
||||||
|
savedVariantCalls.length = 0
|
||||||
|
configVariantOverride = undefined
|
||||||
createdSessionDirectory = undefined
|
createdSessionDirectory = undefined
|
||||||
|
|
||||||
useSessionUIStore.setState({
|
useSessionUIStore.setState({
|
||||||
@@ -384,6 +393,29 @@ describe("issue 2039 draft auto-accept", () => {
|
|||||||
expect(useSessionUIStore.getState().currentSessionId).toBe("ses_issue_2039")
|
expect(useSessionUIStore.getState().currentSessionId).toBe("ses_issue_2039")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("stores only an explicit draft variant as the session override", async () => {
|
||||||
|
useSessionUIStore.getState().openNewSessionDraft()
|
||||||
|
await materializeOpenDraftSession({
|
||||||
|
providerID: "provider",
|
||||||
|
modelID: "model",
|
||||||
|
agent: "agent-default",
|
||||||
|
variant: "high",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(savedVariantCalls).toEqual([undefined])
|
||||||
|
|
||||||
|
configVariantOverride = "high"
|
||||||
|
useSessionUIStore.getState().openNewSessionDraft()
|
||||||
|
await materializeOpenDraftSession({
|
||||||
|
providerID: "provider",
|
||||||
|
modelID: "model",
|
||||||
|
agent: "agent-default",
|
||||||
|
variant: "high",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(savedVariantCalls).toEqual([undefined, "high"])
|
||||||
|
})
|
||||||
|
|
||||||
test("does not apply draft auto-accept after the draft is closed", async () => {
|
test("does not apply draft auto-accept after the draft is closed", async () => {
|
||||||
useSessionUIStore.getState().openNewSessionDraft()
|
useSessionUIStore.getState().openNewSessionDraft()
|
||||||
useSessionUIStore.getState().setDraftPermissionAutoAcceptEnabled(true)
|
useSessionUIStore.getState().setDraftPermissionAutoAcceptEnabled(true)
|
||||||
|
|||||||
@@ -840,13 +840,18 @@ export async function materializeOpenDraftSession(selection: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const effectiveDraftAgent = trimmedAgent ?? configState.currentAgentName
|
const effectiveDraftAgent = trimmedAgent ?? configState.currentAgentName
|
||||||
|
const variantOverride = configState.currentProviderId === selection.providerID
|
||||||
|
&& configState.currentModelId === selection.modelID
|
||||||
|
&& configState.currentAgentName === effectiveDraftAgent
|
||||||
|
? configState.currentVariantSelection.override ?? undefined
|
||||||
|
: selection.variant
|
||||||
|
|
||||||
useSelectionStore.getState().saveSessionModelSelection(created.id, selection.providerID, selection.modelID)
|
useSelectionStore.getState().saveSessionModelSelection(created.id, selection.providerID, selection.modelID)
|
||||||
|
|
||||||
if (effectiveDraftAgent) {
|
if (effectiveDraftAgent) {
|
||||||
useSelectionStore.getState().saveSessionAgentSelection(created.id, effectiveDraftAgent)
|
useSelectionStore.getState().saveSessionAgentSelection(created.id, effectiveDraftAgent)
|
||||||
useSelectionStore.getState().saveAgentModelForSession(created.id, effectiveDraftAgent, selection.providerID, selection.modelID)
|
useSelectionStore.getState().saveAgentModelForSession(created.id, effectiveDraftAgent, selection.providerID, selection.modelID)
|
||||||
useSelectionStore.getState().saveAgentModelVariantForSession(created.id, effectiveDraftAgent, selection.providerID, selection.modelID, selection.variant)
|
useSelectionStore.getState().saveAgentModelVariantForSession(created.id, effectiveDraftAgent, selection.providerID, selection.modelID, variantOverride)
|
||||||
}
|
}
|
||||||
|
|
||||||
store.initializeNewOpenChamberSession(created.id, configState.agents ?? [])
|
store.initializeNewOpenChamberSession(created.id, configState.agents ?? [])
|
||||||
|
|||||||
Reference in New Issue
Block a user