feat(opencode): persist dynamic model selection

This commit is contained in:
DhanushSantosh
2026-01-12 23:07:05 +05:30
parent 9ce3cfee7d
commit 8094941385
4 changed files with 21 additions and 12 deletions

View File

@@ -158,6 +158,8 @@ export function parseLocalStorageSettings(): Partial<GlobalSettings> | null {
cursorDefaultModel: state.cursorDefaultModel as GlobalSettings['cursorDefaultModel'],
enabledOpencodeModels: state.enabledOpencodeModels as GlobalSettings['enabledOpencodeModels'],
opencodeDefaultModel: state.opencodeDefaultModel as GlobalSettings['opencodeDefaultModel'],
enabledDynamicModelIds:
state.enabledDynamicModelIds as GlobalSettings['enabledDynamicModelIds'],
autoLoadClaudeMd: state.autoLoadClaudeMd as boolean,
keyboardShortcuts: state.keyboardShortcuts as GlobalSettings['keyboardShortcuts'],
mcpServers: state.mcpServers as GlobalSettings['mcpServers'],
@@ -517,6 +519,12 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
sanitizedEnabledOpencodeModels.push(sanitizedOpencodeDefaultModel);
}
const persistedDynamicModelIds =
settings.enabledDynamicModelIds ?? current.enabledDynamicModelIds;
const sanitizedDynamicModelIds = persistedDynamicModelIds.filter(
(modelId) => !modelId.startsWith('amazon-bedrock/')
);
// Convert ProjectRef[] to Project[] (minimal data, features will be loaded separately)
const projects = (settings.projects ?? []).map((ref) => ({
id: ref.id,
@@ -562,6 +570,7 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
cursorDefaultModel: settings.cursorDefaultModel ?? 'auto',
enabledOpencodeModels: sanitizedEnabledOpencodeModels,
opencodeDefaultModel: sanitizedOpencodeDefaultModel,
enabledDynamicModelIds: sanitizedDynamicModelIds,
autoLoadClaudeMd: settings.autoLoadClaudeMd ?? false,
skipSandboxWarning: settings.skipSandboxWarning ?? false,
keyboardShortcuts: {
@@ -615,6 +624,7 @@ function buildSettingsUpdateFromStore(): Record<string, unknown> {
enhancementModel: state.enhancementModel,
validationModel: state.validationModel,
phaseModels: state.phaseModels,
enabledDynamicModelIds: state.enabledDynamicModelIds,
autoLoadClaudeMd: state.autoLoadClaudeMd,
skipSandboxWarning: state.skipSandboxWarning,
keyboardShortcuts: state.keyboardShortcuts,

View File

@@ -46,6 +46,7 @@ const SETTINGS_FIELDS_TO_SYNC = [
'cursorDefaultModel',
'enabledOpencodeModels',
'opencodeDefaultModel',
'enabledDynamicModelIds',
'autoLoadClaudeMd',
'keyboardShortcuts',
'mcpServers',

View File

@@ -592,7 +592,7 @@ export interface AppState {
// Dynamic models are session-only (not persisted) because they're discovered at runtime
// from `opencode models` CLI and depend on current provider authentication state
dynamicOpencodeModels: ModelDefinition[]; // Dynamically discovered models from OpenCode CLI
enabledDynamicModelIds: string[]; // Which dynamic models are enabled (session-only)
enabledDynamicModelIds: string[]; // Which dynamic models are enabled
cachedOpencodeProviders: Array<{
id: string;
name: string;
@@ -1241,7 +1241,7 @@ const initialState: AppState = {
enabledOpencodeModels: getAllOpencodeModelIds(), // All OpenCode models enabled by default
opencodeDefaultModel: DEFAULT_OPENCODE_MODEL, // Default to OpenCode free tier
dynamicOpencodeModels: [], // Empty until fetched from OpenCode CLI
enabledDynamicModelIds: [], // All dynamic models enabled by default (populated when models are fetched)
enabledDynamicModelIds: [], // Empty until user enables dynamic models
cachedOpencodeProviders: [], // Empty until fetched from OpenCode CLI
autoLoadClaudeMd: false, // Default to disabled (user must opt-in)
skipSandboxWarning: false, // Default to disabled (show sandbox warning dialog)
@@ -2041,9 +2041,8 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
: state.enabledOpencodeModels.filter((m) => m !== model),
})),
setDynamicOpencodeModels: (models) => {
// Dynamic models are session-only (not persisted to server) because they depend on
// current CLI authentication state and are re-discovered each session
// When setting dynamic models, auto-enable all of them if enabledDynamicModelIds is empty
// Dynamic models depend on CLI authentication state and are re-discovered each session.
// Persist enabled model IDs, but do not auto-enable new models.
const filteredModels = models.filter(
(model) =>
model.provider !== OPENCODE_BEDROCK_PROVIDER_ID &&
@@ -2051,14 +2050,10 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
);
const currentEnabled = get().enabledDynamicModelIds;
const newModelIds = filteredModels.map((m) => m.id);
const filteredEnabled = currentEnabled.filter((modelId) => newModelIds.includes(modelId));
// If no models were previously enabled, enable all new ones
if (currentEnabled.length === 0) {
set({ dynamicOpencodeModels: filteredModels, enabledDynamicModelIds: newModelIds });
} else {
// Keep existing enabled state, just update the models list
set({ dynamicOpencodeModels: filteredModels });
}
const nextEnabled = currentEnabled.length === 0 ? [] : filteredEnabled;
set({ dynamicOpencodeModels: filteredModels, enabledDynamicModelIds: nextEnabled });
},
setEnabledDynamicModelIds: (ids) => set({ enabledDynamicModelIds: ids }),
toggleDynamicModel: (modelId, enabled) =>

View File

@@ -401,6 +401,8 @@ export interface GlobalSettings {
enabledOpencodeModels?: OpencodeModelId[];
/** Default OpenCode model selection when switching to OpenCode CLI */
opencodeDefaultModel?: OpencodeModelId;
/** Which dynamic OpenCode models are enabled (empty = all discovered) */
enabledDynamicModelIds?: string[];
// Input Configuration
/** User's keyboard shortcut bindings */
@@ -704,6 +706,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
cursorDefaultModel: 'auto',
enabledOpencodeModels: getAllOpencodeModelIds(),
opencodeDefaultModel: DEFAULT_OPENCODE_MODEL,
enabledDynamicModelIds: [],
keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS,
projects: [],
trashedProjects: [],