perf: make OpenCode config defaults non-blocking
Removes startup blocking on OpenCode config defaults Preserves manual and directory-specific model selections Adds regression coverage for config races
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { create } from "zustand";
|
||||
import type { StoreApi, UseBoundStore } from "zustand";
|
||||
import { devtools, persist, createJSONStorage } from "zustand/middleware";
|
||||
import type { Provider, Agent } from "@opencode-ai/sdk/v2";
|
||||
import type { Provider, Agent, Config } from "@opencode-ai/sdk/v2";
|
||||
import { opencodeClient } from "@/lib/opencode/client";
|
||||
import { scopeMatches, subscribeToConfigChanges } from "@/lib/configSync";
|
||||
import type { ModelMetadata } from "@/types";
|
||||
@@ -18,6 +18,7 @@ import { streamDebugEnabled } from "@/stores/utils/streamDebug";
|
||||
import { parseModelIdentifier } from "@/lib/modelIdentifier";
|
||||
import { runtimeFetch } from "@/lib/runtime-fetch";
|
||||
import { markStartupTrace, measureStartupTrace } from "@/lib/startupTrace";
|
||||
import { getSyncConfig, subscribeToSyncConfigChanges } from "@/sync/sync-refs";
|
||||
|
||||
const MODELS_DEV_API_URL = "https://models.dev/api.json";
|
||||
const MODELS_DEV_PROXY_URL = "/api/openchamber/models-metadata";
|
||||
@@ -824,6 +825,9 @@ interface DirectoryScopedConfig {
|
||||
selectedProviderId: string;
|
||||
agentModelSelections: { [agentName: string]: { providerId: string; modelId: string } };
|
||||
defaultProviders: { [key: string]: string };
|
||||
opencodeDefaultAgent?: string;
|
||||
opencodeDefaultModel?: string;
|
||||
selectionSource?: "auto" | "manual";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -851,9 +855,92 @@ const hydrateActiveDirectorySnapshot = <T extends Partial<ConfigStore>>(merged:
|
||||
next.defaultProviders = snapshot.defaultProviders;
|
||||
}
|
||||
}
|
||||
if (snapshot.opencodeDefaultAgent !== undefined) {
|
||||
next.opencodeDefaultAgent = snapshot.opencodeDefaultAgent;
|
||||
}
|
||||
if (snapshot.opencodeDefaultModel !== undefined) {
|
||||
next.opencodeDefaultModel = snapshot.opencodeDefaultModel;
|
||||
}
|
||||
if (snapshot.selectionSource) {
|
||||
next.selectionSource = snapshot.selectionSource;
|
||||
}
|
||||
return next as T;
|
||||
};
|
||||
|
||||
const createEmptyDirectoryScopedConfig = (
|
||||
providers: ProviderWithModelList[] = [],
|
||||
agents: Agent[] = [],
|
||||
): DirectoryScopedConfig => ({
|
||||
providers,
|
||||
agents,
|
||||
currentProviderId: "",
|
||||
currentModelId: "",
|
||||
currentVariant: undefined,
|
||||
currentAgentName: undefined,
|
||||
selectedProviderId: "",
|
||||
agentModelSelections: {},
|
||||
defaultProviders: {},
|
||||
opencodeDefaultAgent: undefined,
|
||||
opencodeDefaultModel: undefined,
|
||||
selectionSource: "auto",
|
||||
});
|
||||
|
||||
const hasValidVariant = (
|
||||
providers: ProviderWithModelList[],
|
||||
providerId: string,
|
||||
modelId: string,
|
||||
variant: string | undefined,
|
||||
): boolean => {
|
||||
if (!variant) return true;
|
||||
const model = providers
|
||||
.find((provider) => provider.id === providerId)
|
||||
?.models.find((entry) => entry.id === modelId) as { variants?: Record<string, unknown> } | undefined;
|
||||
return !!model?.variants && Object.prototype.hasOwnProperty.call(model.variants, variant);
|
||||
};
|
||||
|
||||
const resolveSelectionWithManualGuard = ({
|
||||
agents,
|
||||
providers,
|
||||
currentAgentName,
|
||||
currentProviderId,
|
||||
currentModelId,
|
||||
currentVariant,
|
||||
selectionSource,
|
||||
resolvedAgentName,
|
||||
resolvedProviderId,
|
||||
resolvedModelId,
|
||||
resolvedVariant,
|
||||
}: {
|
||||
agents: Agent[];
|
||||
providers: ProviderWithModelList[];
|
||||
currentAgentName: string | undefined;
|
||||
currentProviderId: string;
|
||||
currentModelId: string;
|
||||
currentVariant: string | undefined;
|
||||
selectionSource: "auto" | "manual";
|
||||
resolvedAgentName: string | undefined;
|
||||
resolvedProviderId: string | undefined;
|
||||
resolvedModelId: string | undefined;
|
||||
resolvedVariant: string | undefined;
|
||||
}) => {
|
||||
const manualAgentName = currentAgentName && agents.some((agent) => agent.name === currentAgentName)
|
||||
? currentAgentName
|
||||
: undefined;
|
||||
const manualModelValid = !!currentProviderId
|
||||
&& !!currentModelId
|
||||
&& hasProviderModel(providers, currentProviderId, currentModelId)
|
||||
&& hasValidVariant(providers, currentProviderId, currentModelId, currentVariant);
|
||||
const preserveManual = selectionSource === "manual" && (!!manualAgentName || manualModelValid);
|
||||
|
||||
return {
|
||||
agentName: preserveManual ? (manualAgentName ?? resolvedAgentName) : resolvedAgentName,
|
||||
providerId: preserveManual && manualModelValid ? currentProviderId : resolvedProviderId,
|
||||
modelId: preserveManual && manualModelValid ? currentModelId : resolvedModelId,
|
||||
variant: preserveManual && manualModelValid ? currentVariant : resolvedVariant,
|
||||
selectionSource: preserveManual ? "manual" as const : "auto" as const,
|
||||
};
|
||||
};
|
||||
|
||||
interface ConfigStore {
|
||||
|
||||
activeDirectoryKey: string;
|
||||
@@ -868,6 +955,7 @@ interface ConfigStore {
|
||||
selectedProviderId: string;
|
||||
agentModelSelections: { [agentName: string]: { providerId: string; modelId: string } };
|
||||
defaultProviders: { [key: string]: string };
|
||||
selectionSource: "auto" | "manual";
|
||||
isConnected: boolean;
|
||||
hasEverConnected: boolean;
|
||||
connectionPhase: "connecting" | "connected" | "reconnecting";
|
||||
@@ -879,7 +967,7 @@ interface ConfigStore {
|
||||
settingsDefaultVariant: string | undefined;
|
||||
settingsDefaultAgent: string | undefined;
|
||||
// OpenCode server's own `default_agent` config field (name of a primary agent), used as a
|
||||
// fallback when our own settingsDefaultAgent is unset. Sourced from opencodeClient.getConfig().
|
||||
// fallback when our own settingsDefaultAgent is unset. Sourced from sync config.
|
||||
opencodeDefaultAgent: string | undefined;
|
||||
// OpenCode server's own global `model` config field ("provider/model"), used as a fallback
|
||||
// when neither our settingsDefaultModel nor the resolved agent pins a model.
|
||||
@@ -963,6 +1051,7 @@ interface ConfigStore {
|
||||
getCurrentModelVariants: () => string[];
|
||||
setAgent: (agentName: string | undefined) => void;
|
||||
applyDefaultModelAgentSelection: () => void;
|
||||
applyOpenCodeConfigDefaults: (directory?: string | null, source?: string, config?: Config) => void;
|
||||
setSelectedProvider: (providerId: string) => void;
|
||||
setSettingsDefaultModel: (model: string | undefined) => void;
|
||||
setSettingsDefaultVariant: (variant: string | undefined) => void;
|
||||
@@ -1015,6 +1104,7 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
selectedProviderId: "",
|
||||
agentModelSelections: {},
|
||||
defaultProviders: {},
|
||||
selectionSource: "auto",
|
||||
isConnected: false,
|
||||
hasEverConnected: false,
|
||||
connectionPhase: "connecting",
|
||||
@@ -1296,6 +1386,9 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
selectedProviderId: snapshot.selectedProviderId,
|
||||
agentModelSelections: snapshot.agentModelSelections,
|
||||
defaultProviders: snapshot.defaultProviders,
|
||||
opencodeDefaultAgent: snapshot.opencodeDefaultAgent,
|
||||
opencodeDefaultModel: snapshot.opencodeDefaultModel,
|
||||
selectionSource: snapshot.selectionSource ?? "auto",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1309,6 +1402,9 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
selectedProviderId: "",
|
||||
agentModelSelections: {},
|
||||
defaultProviders: {},
|
||||
opencodeDefaultAgent: undefined,
|
||||
opencodeDefaultModel: undefined,
|
||||
selectionSource: "auto",
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1638,12 +1734,14 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
currentProviderId: providerId,
|
||||
currentModelId: newModelId,
|
||||
selectedProviderId: providerId,
|
||||
selectionSource: "manual",
|
||||
};
|
||||
|
||||
return {
|
||||
currentProviderId: providerId,
|
||||
currentModelId: newModelId,
|
||||
selectedProviderId: providerId,
|
||||
selectionSource: "manual",
|
||||
directoryScoped: {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: nextSnapshot,
|
||||
@@ -1670,10 +1768,12 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
const nextSnapshot: DirectoryScopedConfig = {
|
||||
...baseSnapshot,
|
||||
currentModelId: modelId,
|
||||
selectionSource: "manual",
|
||||
};
|
||||
|
||||
return {
|
||||
currentModelId: modelId,
|
||||
selectionSource: "manual",
|
||||
directoryScoped: {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: nextSnapshot,
|
||||
@@ -1704,10 +1804,12 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
const nextSnapshot: DirectoryScopedConfig = {
|
||||
...baseSnapshot,
|
||||
currentVariant: variant,
|
||||
selectionSource: "manual",
|
||||
};
|
||||
|
||||
return {
|
||||
currentVariant: variant,
|
||||
selectionSource: "manual",
|
||||
directoryScoped: {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: nextSnapshot,
|
||||
@@ -1763,10 +1865,12 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
const nextSnapshot: DirectoryScopedConfig = {
|
||||
...baseSnapshot,
|
||||
selectedProviderId: providerId,
|
||||
selectionSource: "manual",
|
||||
};
|
||||
|
||||
return {
|
||||
selectedProviderId: providerId,
|
||||
selectionSource: "manual",
|
||||
directoryScoped: {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: nextSnapshot,
|
||||
@@ -1797,10 +1901,12 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
const nextSnapshot: DirectoryScopedConfig = {
|
||||
...baseSnapshot,
|
||||
agentModelSelections: nextSelections,
|
||||
selectionSource: "manual",
|
||||
};
|
||||
|
||||
return {
|
||||
agentModelSelections: nextSelections,
|
||||
selectionSource: "manual",
|
||||
directoryScoped: {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: nextSnapshot,
|
||||
@@ -1844,24 +1950,25 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
try {
|
||||
// Fetch agents, OpenChamber settings, and the OpenCode config in parallel.
|
||||
// The OpenCode config is best-effort: a failure should not block agent
|
||||
// loading, it just means we won't honor its default_agent this round.
|
||||
const [agents, openChamberDefaults, opencodeConfig] = await Promise.all([
|
||||
// Fetch agents and OpenChamber settings in parallel. OpenCode config
|
||||
// comes from sync state if it is already available; it must not block
|
||||
// the agent refresh path.
|
||||
const configDirectoryPath = fromDirectoryKey(directoryKey);
|
||||
const initialSyncedOpencodeConfig = getSyncConfig(requestedDirectory ?? undefined)
|
||||
?? getSyncConfig(configDirectoryPath ?? undefined);
|
||||
if (initialSyncedOpencodeConfig) {
|
||||
markStartupTrace('loadAgents:syncConfigHit', { directoryKey, source });
|
||||
}
|
||||
const [agents, openChamberDefaults] = await Promise.all([
|
||||
measureStartupTrace(
|
||||
'loadAgents:api',
|
||||
() => opencodeClient.listAgents(fromDirectoryKey(directoryKey)),
|
||||
() => opencodeClient.listAgents(configDirectoryPath),
|
||||
{ directoryKey, source, requestedDirectory, effectiveDirectory, attempt: attempt + 1 },
|
||||
),
|
||||
fetchOpenChamberDefaults(),
|
||||
opencodeClient
|
||||
.withDirectory(fromDirectoryKey(directoryKey), () => opencodeClient.getConfig())
|
||||
.catch(() => null),
|
||||
]);
|
||||
|
||||
const safeAgents = Array.isArray(agents) ? agents : [];
|
||||
const opencodeDefaultAgent = normalizeOptionalString(opencodeConfig?.default_agent);
|
||||
const opencodeDefaultModel = normalizeOptionalString(opencodeConfig?.model);
|
||||
|
||||
const providerLoad = _inFlightProviders.get(directoryKey);
|
||||
if (providerLoad) {
|
||||
@@ -1869,6 +1976,16 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
await providerLoad;
|
||||
}
|
||||
|
||||
const latestSyncedOpencodeConfig = getSyncConfig(requestedDirectory ?? undefined)
|
||||
?? getSyncConfig(configDirectoryPath ?? undefined);
|
||||
const hasLatestSyncedOpencodeConfig = latestSyncedOpencodeConfig !== undefined;
|
||||
const latestSyncedOpencodeDefaultAgent = hasLatestSyncedOpencodeConfig
|
||||
? normalizeOptionalString(latestSyncedOpencodeConfig.default_agent)
|
||||
: undefined;
|
||||
const latestSyncedOpencodeDefaultModel = hasLatestSyncedOpencodeConfig
|
||||
? normalizeOptionalString(latestSyncedOpencodeConfig.model)
|
||||
: undefined;
|
||||
|
||||
const providers = get().activeDirectoryKey === directoryKey
|
||||
? get().providers
|
||||
: (get().directoryScoped[directoryKey]?.providers ?? []);
|
||||
@@ -1902,19 +2019,25 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
agentModelSelections: {},
|
||||
defaultProviders: {},
|
||||
};
|
||||
const opencodeDefaultAgent = hasLatestSyncedOpencodeConfig
|
||||
? latestSyncedOpencodeDefaultAgent
|
||||
: baseSnapshot.opencodeDefaultAgent ?? (state.activeDirectoryKey === directoryKey ? state.opencodeDefaultAgent : undefined);
|
||||
const opencodeDefaultModel = hasLatestSyncedOpencodeConfig
|
||||
? latestSyncedOpencodeDefaultModel
|
||||
: baseSnapshot.opencodeDefaultModel ?? (state.activeDirectoryKey === directoryKey ? state.opencodeDefaultModel : undefined);
|
||||
|
||||
const nextSnapshot: DirectoryScopedConfig = {
|
||||
...baseSnapshot,
|
||||
providers,
|
||||
agents: safeAgents,
|
||||
opencodeDefaultAgent,
|
||||
opencodeDefaultModel,
|
||||
};
|
||||
|
||||
const nextState: Partial<ConfigStore> = {
|
||||
settingsDefaultModel: openChamberDefaults.defaultModel,
|
||||
settingsDefaultVariant: openChamberDefaults.defaultVariant,
|
||||
settingsDefaultAgent: openChamberDefaults.defaultAgent,
|
||||
opencodeDefaultAgent,
|
||||
opencodeDefaultModel,
|
||||
settingsAutoCreateWorktree: openChamberDefaults.autoCreateWorktree ?? false,
|
||||
settingsGitmojiEnabled: openChamberDefaults.gitmojiEnabled ?? false,
|
||||
settingsDefaultFileViewerPreview: openChamberDefaults.defaultFileViewerPreview ?? false,
|
||||
@@ -1934,11 +2057,20 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
|
||||
if (state.activeDirectoryKey === directoryKey) {
|
||||
nextState.agents = safeAgents;
|
||||
nextState.opencodeDefaultAgent = opencodeDefaultAgent;
|
||||
nextState.opencodeDefaultModel = opencodeDefaultModel;
|
||||
}
|
||||
|
||||
return nextState;
|
||||
});
|
||||
|
||||
const latestConfigState = get();
|
||||
const latestSnapshot = latestConfigState.directoryScoped[directoryKey];
|
||||
const opencodeDefaultAgent = latestSnapshot?.opencodeDefaultAgent
|
||||
?? (latestConfigState.activeDirectoryKey === directoryKey ? latestConfigState.opencodeDefaultAgent : undefined);
|
||||
const opencodeDefaultModel = latestSnapshot?.opencodeDefaultModel
|
||||
?? (latestConfigState.activeDirectoryKey === directoryKey ? latestConfigState.opencodeDefaultModel : undefined);
|
||||
|
||||
const shouldPersistResolvedZenModel =
|
||||
!!resolvedZenModel &&
|
||||
resolvedZenModel !== defaultZenModel;
|
||||
@@ -2058,15 +2190,37 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
agentModelSelections: {},
|
||||
defaultProviders: {},
|
||||
};
|
||||
const isActive = state.activeDirectoryKey === directoryKey;
|
||||
const currentAgentName = isActive ? state.currentAgentName : baseSnapshot.currentAgentName;
|
||||
const currentProviderId = isActive ? state.currentProviderId : baseSnapshot.currentProviderId;
|
||||
const currentModelId = isActive ? state.currentModelId : baseSnapshot.currentModelId;
|
||||
const currentVariant = isActive ? state.currentVariant : baseSnapshot.currentVariant;
|
||||
const selectionSource = isActive ? state.selectionSource : (baseSnapshot.selectionSource ?? "auto");
|
||||
const nextSelection = resolveSelectionWithManualGuard({
|
||||
agents: safeAgents,
|
||||
providers,
|
||||
currentAgentName,
|
||||
currentProviderId,
|
||||
currentModelId,
|
||||
currentVariant,
|
||||
selectionSource,
|
||||
resolvedAgentName,
|
||||
resolvedProviderId,
|
||||
resolvedModelId,
|
||||
resolvedVariant,
|
||||
});
|
||||
|
||||
const nextSnapshot: DirectoryScopedConfig = {
|
||||
...baseSnapshot,
|
||||
providers,
|
||||
agents: safeAgents,
|
||||
currentAgentName: resolvedAgentName,
|
||||
currentProviderId: resolvedProviderId ?? baseSnapshot.currentProviderId,
|
||||
currentModelId: resolvedModelId ?? baseSnapshot.currentModelId,
|
||||
currentVariant: resolvedVariant,
|
||||
currentAgentName: nextSelection.agentName,
|
||||
currentProviderId: nextSelection.providerId ?? baseSnapshot.currentProviderId,
|
||||
currentModelId: nextSelection.modelId ?? baseSnapshot.currentModelId,
|
||||
currentVariant: nextSelection.variant,
|
||||
opencodeDefaultAgent,
|
||||
opencodeDefaultModel,
|
||||
selectionSource: nextSelection.selectionSource,
|
||||
};
|
||||
|
||||
const nextState: Partial<ConfigStore> = {
|
||||
@@ -2076,13 +2230,16 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
},
|
||||
};
|
||||
|
||||
if (state.activeDirectoryKey === directoryKey) {
|
||||
nextState.currentAgentName = resolvedAgentName;
|
||||
if (resolvedProviderId && resolvedModelId) {
|
||||
nextState.currentProviderId = resolvedProviderId;
|
||||
nextState.currentModelId = resolvedModelId;
|
||||
nextState.currentVariant = resolvedVariant;
|
||||
if (isActive) {
|
||||
nextState.currentAgentName = nextSelection.agentName;
|
||||
nextState.opencodeDefaultAgent = opencodeDefaultAgent;
|
||||
nextState.opencodeDefaultModel = opencodeDefaultModel;
|
||||
if (nextSelection.providerId && nextSelection.modelId) {
|
||||
nextState.currentProviderId = nextSelection.providerId;
|
||||
nextState.currentModelId = nextSelection.modelId;
|
||||
nextState.currentVariant = nextSelection.variant;
|
||||
}
|
||||
nextState.selectionSource = nextSelection.selectionSource;
|
||||
}
|
||||
|
||||
return nextState;
|
||||
@@ -2210,10 +2367,12 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
const nextSnapshot: DirectoryScopedConfig = {
|
||||
...baseSnapshot,
|
||||
currentAgentName: agentName,
|
||||
selectionSource: "manual",
|
||||
};
|
||||
|
||||
return {
|
||||
currentAgentName: agentName,
|
||||
selectionSource: "manual",
|
||||
directoryScoped: {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: nextSnapshot,
|
||||
@@ -2261,6 +2420,7 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
currentModelId: modelId,
|
||||
currentVariant: variant,
|
||||
selectedProviderId: providerId,
|
||||
selectionSource: "manual",
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -2268,6 +2428,7 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
currentModelId: modelId,
|
||||
currentVariant: variant,
|
||||
selectedProviderId: providerId,
|
||||
selectionSource: "manual",
|
||||
directoryScoped: {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: nextSnapshot,
|
||||
@@ -2399,10 +2560,12 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
selectedProviderId: resolvedProviderId,
|
||||
}
|
||||
: {}),
|
||||
selectionSource: "auto",
|
||||
};
|
||||
|
||||
const nextState: Partial<ConfigStore> = {
|
||||
currentAgentName: resolvedAgentName,
|
||||
selectionSource: "auto",
|
||||
directoryScoped: {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: nextSnapshot,
|
||||
@@ -2420,6 +2583,153 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
});
|
||||
},
|
||||
|
||||
applyOpenCodeConfigDefaults: (directory, source = "syncConfig", config) => {
|
||||
const eventDirectory = directory ?? fromDirectoryKey(get().activeDirectoryKey);
|
||||
const directoryKey = toConfigDirectoryKey(eventDirectory);
|
||||
const configDirectory = fromDirectoryKey(directoryKey);
|
||||
const syncedConfig = config
|
||||
?? getSyncConfig(eventDirectory ?? undefined)
|
||||
?? getSyncConfig(configDirectory ?? undefined);
|
||||
if (!syncedConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opencodeDefaultAgent = normalizeOptionalString(syncedConfig.default_agent);
|
||||
const opencodeDefaultModel = normalizeOptionalString(syncedConfig.model);
|
||||
|
||||
set((state) => {
|
||||
const snapshot = state.directoryScoped[directoryKey];
|
||||
const isActive = state.activeDirectoryKey === directoryKey;
|
||||
const providers = isActive ? state.providers : (snapshot?.providers ?? []);
|
||||
const agents = isActive ? state.agents : (snapshot?.agents ?? []);
|
||||
const baseSnapshot: DirectoryScopedConfig = snapshot ?? createEmptyDirectoryScopedConfig(providers, agents);
|
||||
const defaultsChanged = baseSnapshot.opencodeDefaultAgent !== opencodeDefaultAgent
|
||||
|| baseSnapshot.opencodeDefaultModel !== opencodeDefaultModel
|
||||
|| (isActive && (
|
||||
state.opencodeDefaultAgent !== opencodeDefaultAgent
|
||||
|| state.opencodeDefaultModel !== opencodeDefaultModel
|
||||
));
|
||||
const defaultsSnapshot: DirectoryScopedConfig = {
|
||||
...baseSnapshot,
|
||||
providers,
|
||||
agents,
|
||||
opencodeDefaultAgent,
|
||||
opencodeDefaultModel,
|
||||
};
|
||||
const nextState: Partial<ConfigStore> = {
|
||||
directoryScoped: {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: defaultsSnapshot,
|
||||
},
|
||||
};
|
||||
|
||||
if (isActive) {
|
||||
nextState.opencodeDefaultAgent = opencodeDefaultAgent;
|
||||
nextState.opencodeDefaultModel = opencodeDefaultModel;
|
||||
}
|
||||
|
||||
const selectionSource = isActive ? state.selectionSource : (snapshot?.selectionSource ?? "auto");
|
||||
|
||||
if (providers.length === 0 || agents.length === 0) {
|
||||
if (!defaultsChanged) {
|
||||
return state;
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
|
||||
const resolved = resolveDefaultAgentModelSelection({
|
||||
agents,
|
||||
providers,
|
||||
settingsDefaultAgent: state.settingsDefaultAgent,
|
||||
settingsDefaultModel: state.settingsDefaultModel,
|
||||
settingsDefaultVariant: state.settingsDefaultVariant,
|
||||
opencodeDefaultAgent,
|
||||
opencodeDefaultModel,
|
||||
});
|
||||
|
||||
if (!resolved.agentName) {
|
||||
if (!defaultsChanged) {
|
||||
return state;
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
|
||||
const currentAgentName = isActive ? state.currentAgentName : baseSnapshot.currentAgentName;
|
||||
const currentProviderId = isActive ? state.currentProviderId : baseSnapshot.currentProviderId;
|
||||
const currentModelId = isActive ? state.currentModelId : baseSnapshot.currentModelId;
|
||||
const currentVariant = isActive ? state.currentVariant : baseSnapshot.currentVariant;
|
||||
const nextSelection = resolveSelectionWithManualGuard({
|
||||
agents,
|
||||
providers,
|
||||
currentAgentName,
|
||||
currentProviderId,
|
||||
currentModelId,
|
||||
currentVariant,
|
||||
selectionSource,
|
||||
resolvedAgentName: resolved.agentName,
|
||||
resolvedProviderId: resolved.providerId,
|
||||
resolvedModelId: resolved.modelId,
|
||||
resolvedVariant: resolved.variant,
|
||||
});
|
||||
|
||||
const nextSnapshot: DirectoryScopedConfig = {
|
||||
...defaultsSnapshot,
|
||||
providers,
|
||||
agents,
|
||||
currentAgentName: nextSelection.agentName,
|
||||
...(nextSelection.providerId && nextSelection.modelId
|
||||
? {
|
||||
currentProviderId: nextSelection.providerId,
|
||||
currentModelId: nextSelection.modelId,
|
||||
currentVariant: nextSelection.variant,
|
||||
selectedProviderId: nextSelection.providerId,
|
||||
}
|
||||
: {}),
|
||||
selectionSource: nextSelection.selectionSource,
|
||||
};
|
||||
|
||||
const selectionChanged = baseSnapshot.currentAgentName !== nextSnapshot.currentAgentName
|
||||
|| baseSnapshot.currentProviderId !== nextSnapshot.currentProviderId
|
||||
|| baseSnapshot.currentModelId !== nextSnapshot.currentModelId
|
||||
|| baseSnapshot.currentVariant !== nextSnapshot.currentVariant
|
||||
|| baseSnapshot.selectedProviderId !== nextSnapshot.selectedProviderId
|
||||
|| (baseSnapshot.selectionSource ?? "auto") !== nextSnapshot.selectionSource
|
||||
|| (isActive && (
|
||||
state.currentAgentName !== nextSelection.agentName
|
||||
|| state.selectionSource !== nextSelection.selectionSource
|
||||
|| (nextSelection.providerId !== undefined && nextSelection.modelId !== undefined && (
|
||||
state.currentProviderId !== nextSelection.providerId
|
||||
|| state.currentModelId !== nextSelection.modelId
|
||||
|| state.currentVariant !== nextSelection.variant
|
||||
|| state.selectedProviderId !== nextSelection.providerId
|
||||
))
|
||||
));
|
||||
|
||||
if (!defaultsChanged && !selectionChanged) {
|
||||
return state;
|
||||
}
|
||||
|
||||
nextState.directoryScoped = {
|
||||
...state.directoryScoped,
|
||||
[directoryKey]: nextSnapshot,
|
||||
};
|
||||
|
||||
if (isActive) {
|
||||
nextState.currentAgentName = nextSelection.agentName;
|
||||
nextState.selectionSource = nextSelection.selectionSource;
|
||||
if (nextSelection.providerId && nextSelection.modelId) {
|
||||
nextState.currentProviderId = nextSelection.providerId;
|
||||
nextState.currentModelId = nextSelection.modelId;
|
||||
nextState.currentVariant = nextSelection.variant;
|
||||
nextState.selectedProviderId = nextSelection.providerId;
|
||||
}
|
||||
}
|
||||
|
||||
markStartupTrace('loadAgents:opencodeConfigDefaultsApplied', { directoryKey, eventDirectory, source });
|
||||
return nextState;
|
||||
});
|
||||
},
|
||||
|
||||
setSettingsDefaultModel: (model: string | undefined) => {
|
||||
set({ settingsDefaultModel: model });
|
||||
},
|
||||
@@ -3020,6 +3330,8 @@ if (!unsubscribeConfigStoreChanges) {
|
||||
unsubscribeConfigStoreChanges = subscribeToConfigChanges(async (event) => {
|
||||
const tasks: Promise<void>[] = [];
|
||||
|
||||
opencodeClient.clearConfigCache();
|
||||
|
||||
if (scopeMatches(event, "agents")) {
|
||||
const { loadAgents } = useConfigStore.getState();
|
||||
tasks.push(loadAgents({ source: 'configChange:agents' }).then(() => {}));
|
||||
@@ -3037,6 +3349,14 @@ if (!unsubscribeConfigStoreChanges) {
|
||||
|
||||
let unsubscribeConfigStoreDirectoryChanges: (() => void) | null = null;
|
||||
|
||||
let unsubscribeConfigStoreSyncConfigChanges: (() => void) | null = null;
|
||||
|
||||
if (!unsubscribeConfigStoreSyncConfigChanges) {
|
||||
unsubscribeConfigStoreSyncConfigChanges = subscribeToSyncConfigChanges((directory, config) => {
|
||||
useConfigStore.getState().applyOpenCodeConfigDefaults(directory, 'syncConfig', config);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined" && !unsubscribeConfigStoreDirectoryChanges) {
|
||||
unsubscribeConfigStoreDirectoryChanges = useDirectoryStore.subscribe((state, prevState) => {
|
||||
const nextKey = toDirectoryKey(state.currentDirectory);
|
||||
|
||||
Reference in New Issue
Block a user