fix(providers): keep add-provider flow selected during sync
This commit is contained in:
@@ -408,6 +408,31 @@ describe('useConfigStore provider persistence', () => {
|
|||||||
expect(useConfigStore.getState().selectedProviderId).toBe('__add_provider__');
|
expect(useConfigStore.getState().selectedProviderId).toBe('__add_provider__');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('add-provider sentinel is not persisted as a stable provider selection', async () => {
|
||||||
|
useConfigStore.setState({
|
||||||
|
activeDirectoryKey: DIRECTORY,
|
||||||
|
currentProviderId: 'live',
|
||||||
|
currentModelId: 'live-model',
|
||||||
|
selectedProviderId: '__add_provider__',
|
||||||
|
directoryScoped: {
|
||||||
|
[DIRECTORY]: {
|
||||||
|
providers: [provider('live')],
|
||||||
|
agents: [],
|
||||||
|
currentProviderId: 'live',
|
||||||
|
currentModelId: 'live-model',
|
||||||
|
currentAgentName: undefined,
|
||||||
|
selectedProviderId: '__add_provider__',
|
||||||
|
agentModelSelections: {},
|
||||||
|
defaultProviders: { default: 'live' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const persisted = JSON.parse(storage.get(STORAGE_KEY) ?? '{}');
|
||||||
|
expect(persisted.state.selectedProviderId).toBe('');
|
||||||
|
expect(persisted.state.directoryScoped[DIRECTORY].selectedProviderId).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
test('setAgent applies settings default variant for an agent configured model', () => {
|
test('setAgent applies settings default variant for an agent configured model', () => {
|
||||||
useSessionUIStore.setState({ currentSessionId: 'ses_agent_default_variant' });
|
useSessionUIStore.setState({ currentSessionId: 'ses_agent_default_variant' });
|
||||||
useConfigStore.setState({
|
useConfigStore.setState({
|
||||||
@@ -603,6 +628,47 @@ describe('useConfigStore provider persistence', () => {
|
|||||||
expect(state.currentAgentName).toBe('review');
|
expect(state.currentAgentName).toBe('review');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sync config defaults do not close the add-provider settings flow', () => {
|
||||||
|
useConfigStore.setState({
|
||||||
|
activeDirectoryKey: DIRECTORY,
|
||||||
|
providers: [provider('openai', 'gpt-5.5'), provider('anthropic', 'claude')],
|
||||||
|
agents: [
|
||||||
|
testAgent('build', { model: { providerID: 'anthropic', modelID: 'claude' } }),
|
||||||
|
testAgent('review', { model: { providerID: 'openai', modelID: 'gpt-5.5' } }),
|
||||||
|
],
|
||||||
|
currentProviderId: 'anthropic',
|
||||||
|
currentModelId: 'claude',
|
||||||
|
currentAgentName: 'build',
|
||||||
|
selectedProviderId: '__add_provider__',
|
||||||
|
selectionSource: 'auto',
|
||||||
|
directoryScoped: {
|
||||||
|
[DIRECTORY]: {
|
||||||
|
providers: [provider('openai', 'gpt-5.5'), provider('anthropic', 'claude')],
|
||||||
|
agents: [
|
||||||
|
testAgent('build', { model: { providerID: 'anthropic', modelID: 'claude' } }),
|
||||||
|
testAgent('review', { model: { providerID: 'openai', modelID: 'gpt-5.5' } }),
|
||||||
|
],
|
||||||
|
currentProviderId: 'anthropic',
|
||||||
|
currentModelId: 'claude',
|
||||||
|
currentAgentName: 'build',
|
||||||
|
selectedProviderId: '__add_provider__',
|
||||||
|
agentModelSelections: {},
|
||||||
|
defaultProviders: {},
|
||||||
|
selectionSource: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
emitSyncConfigChanged(DIRECTORY, { default_agent: 'review', model: 'openai/gpt-5.5' });
|
||||||
|
|
||||||
|
const state = useConfigStore.getState();
|
||||||
|
expect(state.currentAgentName).toBe('review');
|
||||||
|
expect(state.currentProviderId).toBe('openai');
|
||||||
|
expect(state.currentModelId).toBe('gpt-5.5');
|
||||||
|
expect(state.selectedProviderId).toBe('__add_provider__');
|
||||||
|
expect(state.directoryScoped[DIRECTORY]?.selectedProviderId).toBe('__add_provider__');
|
||||||
|
});
|
||||||
|
|
||||||
test('duplicate sync config event is a no-op when defaults and selection are unchanged', () => {
|
test('duplicate sync config event is a no-op when defaults and selection are unchanged', () => {
|
||||||
useConfigStore.setState({
|
useConfigStore.setState({
|
||||||
activeDirectoryKey: DIRECTORY,
|
activeDirectoryKey: DIRECTORY,
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ const STT_SILENCE_HOLD_MS_MAX = 10000;
|
|||||||
const FALLBACK_PROVIDER_ID = "opencode";
|
const FALLBACK_PROVIDER_ID = "opencode";
|
||||||
const FALLBACK_MODEL_ID = "big-pickle";
|
const FALLBACK_MODEL_ID = "big-pickle";
|
||||||
// Sentinel selectedProviderId used by the providers UI while the "Add provider"
|
// Sentinel selectedProviderId used by the providers UI while the "Add provider"
|
||||||
// form is open. It is intentionally not a real provider id.
|
// form is open. It is intentionally not a real provider id and must not be
|
||||||
|
// persisted as a stable provider selection.
|
||||||
const ADD_PROVIDER_SENTINEL = "__add_provider__";
|
const ADD_PROVIDER_SENTINEL = "__add_provider__";
|
||||||
const GIT_UTILITY_PROVIDER_ID = "zen";
|
const GIT_UTILITY_PROVIDER_ID = "zen";
|
||||||
const GIT_UTILITY_PREFERRED_MODEL_ID = "big-pickle";
|
const GIT_UTILITY_PREFERRED_MODEL_ID = "big-pickle";
|
||||||
@@ -189,6 +190,14 @@ type ProviderWithModelList = Omit<Provider, "models"> & { models: ProviderModel[
|
|||||||
type GitModelSelection = { providerId: string; modelId: string };
|
type GitModelSelection = { providerId: string; modelId: string };
|
||||||
type ProviderModelSelection = { providerId: string; modelId: string; variant?: string } | null;
|
type ProviderModelSelection = { providerId: string; modelId: string; variant?: string } | null;
|
||||||
|
|
||||||
|
const sanitizePersistedSelectedProviderId = (providerId: string | undefined): string => (
|
||||||
|
providerId === ADD_PROVIDER_SENTINEL ? "" : (providerId ?? "")
|
||||||
|
);
|
||||||
|
|
||||||
|
const preserveAddProviderSelection = (currentSelectedProviderId: string | undefined, nextProviderId: string): string => (
|
||||||
|
currentSelectedProviderId === ADD_PROVIDER_SENTINEL ? ADD_PROVIDER_SENTINEL : nextProviderId
|
||||||
|
);
|
||||||
|
|
||||||
const normalizeOptionalString = (value: unknown): string | undefined => {
|
const normalizeOptionalString = (value: unknown): string | undefined => {
|
||||||
if (typeof value !== "string") {
|
if (typeof value !== "string") {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -2424,7 +2433,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
currentProviderId: providerId,
|
currentProviderId: providerId,
|
||||||
currentModelId: modelId,
|
currentModelId: modelId,
|
||||||
currentVariant: variant,
|
currentVariant: variant,
|
||||||
selectedProviderId: providerId,
|
selectedProviderId: preserveAddProviderSelection(state.selectedProviderId, providerId),
|
||||||
selectionSource: "manual",
|
selectionSource: "manual",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2432,7 +2441,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
currentProviderId: providerId,
|
currentProviderId: providerId,
|
||||||
currentModelId: modelId,
|
currentModelId: modelId,
|
||||||
currentVariant: variant,
|
currentVariant: variant,
|
||||||
selectedProviderId: providerId,
|
selectedProviderId: preserveAddProviderSelection(state.selectedProviderId, providerId),
|
||||||
selectionSource: "manual",
|
selectionSource: "manual",
|
||||||
directoryScoped: {
|
directoryScoped: {
|
||||||
...state.directoryScoped,
|
...state.directoryScoped,
|
||||||
@@ -2577,7 +2586,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
currentProviderId: resolvedProviderId,
|
currentProviderId: resolvedProviderId,
|
||||||
currentModelId: resolvedModelId,
|
currentModelId: resolvedModelId,
|
||||||
currentVariant: resolvedVariant,
|
currentVariant: resolvedVariant,
|
||||||
selectedProviderId: resolvedProviderId,
|
selectedProviderId: preserveAddProviderSelection(state.selectedProviderId, resolvedProviderId),
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
selectionSource: "auto",
|
selectionSource: "auto",
|
||||||
@@ -2596,7 +2605,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
nextState.currentProviderId = resolvedProviderId;
|
nextState.currentProviderId = resolvedProviderId;
|
||||||
nextState.currentModelId = resolvedModelId;
|
nextState.currentModelId = resolvedModelId;
|
||||||
nextState.currentVariant = resolvedVariant;
|
nextState.currentVariant = resolvedVariant;
|
||||||
nextState.selectedProviderId = resolvedProviderId;
|
nextState.selectedProviderId = preserveAddProviderSelection(state.selectedProviderId, resolvedProviderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextState;
|
return nextState;
|
||||||
@@ -2678,6 +2687,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
const currentProviderId = isActive ? state.currentProviderId : baseSnapshot.currentProviderId;
|
const currentProviderId = isActive ? state.currentProviderId : baseSnapshot.currentProviderId;
|
||||||
const currentModelId = isActive ? state.currentModelId : baseSnapshot.currentModelId;
|
const currentModelId = isActive ? state.currentModelId : baseSnapshot.currentModelId;
|
||||||
const currentVariant = isActive ? state.currentVariant : baseSnapshot.currentVariant;
|
const currentVariant = isActive ? state.currentVariant : baseSnapshot.currentVariant;
|
||||||
|
const currentSelectedProviderId = isActive ? state.selectedProviderId : baseSnapshot.selectedProviderId;
|
||||||
const nextSelection = resolveSelectionWithManualGuard({
|
const nextSelection = resolveSelectionWithManualGuard({
|
||||||
agents,
|
agents,
|
||||||
providers,
|
providers,
|
||||||
@@ -2702,7 +2712,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
currentProviderId: nextSelection.providerId,
|
currentProviderId: nextSelection.providerId,
|
||||||
currentModelId: nextSelection.modelId,
|
currentModelId: nextSelection.modelId,
|
||||||
currentVariant: nextSelection.variant,
|
currentVariant: nextSelection.variant,
|
||||||
selectedProviderId: nextSelection.providerId,
|
selectedProviderId: preserveAddProviderSelection(currentSelectedProviderId, nextSelection.providerId),
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
selectionSource: nextSelection.selectionSource,
|
selectionSource: nextSelection.selectionSource,
|
||||||
@@ -2721,7 +2731,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
state.currentProviderId !== nextSelection.providerId
|
state.currentProviderId !== nextSelection.providerId
|
||||||
|| state.currentModelId !== nextSelection.modelId
|
|| state.currentModelId !== nextSelection.modelId
|
||||||
|| state.currentVariant !== nextSelection.variant
|
|| state.currentVariant !== nextSelection.variant
|
||||||
|| state.selectedProviderId !== nextSelection.providerId
|
|| state.selectedProviderId !== preserveAddProviderSelection(currentSelectedProviderId, nextSelection.providerId)
|
||||||
))
|
))
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -2741,7 +2751,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
nextState.currentProviderId = nextSelection.providerId;
|
nextState.currentProviderId = nextSelection.providerId;
|
||||||
nextState.currentModelId = nextSelection.modelId;
|
nextState.currentModelId = nextSelection.modelId;
|
||||||
nextState.currentVariant = nextSelection.variant;
|
nextState.currentVariant = nextSelection.variant;
|
||||||
nextState.selectedProviderId = nextSelection.providerId;
|
nextState.selectedProviderId = preserveAddProviderSelection(currentSelectedProviderId, nextSelection.providerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3288,14 +3298,22 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
// success) and by the provider/agent config-change subscriptions.
|
// success) and by the provider/agent config-change subscriptions.
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
activeDirectoryKey: state.activeDirectoryKey,
|
activeDirectoryKey: state.activeDirectoryKey,
|
||||||
directoryScoped: state.directoryScoped,
|
directoryScoped: Object.fromEntries(
|
||||||
|
Object.entries(state.directoryScoped).map(([directoryKey, snapshot]) => [
|
||||||
|
directoryKey,
|
||||||
|
{
|
||||||
|
...snapshot,
|
||||||
|
selectedProviderId: sanitizePersistedSelectedProviderId(snapshot.selectedProviderId),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
),
|
||||||
providers: state.providers,
|
providers: state.providers,
|
||||||
agents: state.agents,
|
agents: state.agents,
|
||||||
currentProviderId: state.currentProviderId,
|
currentProviderId: state.currentProviderId,
|
||||||
currentModelId: state.currentModelId,
|
currentModelId: state.currentModelId,
|
||||||
currentVariant: state.currentVariant,
|
currentVariant: state.currentVariant,
|
||||||
currentAgentName: state.currentAgentName,
|
currentAgentName: state.currentAgentName,
|
||||||
selectedProviderId: state.selectedProviderId,
|
selectedProviderId: sanitizePersistedSelectedProviderId(state.selectedProviderId),
|
||||||
agentModelSelections: state.agentModelSelections,
|
agentModelSelections: state.agentModelSelections,
|
||||||
defaultProviders: state.defaultProviders,
|
defaultProviders: state.defaultProviders,
|
||||||
settingsDefaultModel: state.settingsDefaultModel,
|
settingsDefaultModel: state.settingsDefaultModel,
|
||||||
|
|||||||
Reference in New Issue
Block a user