feat: add instance config provider and map storage ids

This commit is contained in:
Shantur Rathore
2025-11-20 14:46:13 +00:00
parent 038cf3c762
commit 3f46d73a31
14 changed files with 247 additions and 131 deletions

View File

@@ -1,4 +1,5 @@
import type { import type {
AgentModelSelection,
AgentModelSelections, AgentModelSelections,
ConfigFile, ConfigFile,
ModelPreference, ModelPreference,
@@ -107,6 +108,7 @@ export type WorkspaceFileSearchResponse = FileSystemEntry[]
export interface InstanceData { export interface InstanceData {
messageHistory: string[] messageHistory: string[]
agentModelSelections: AgentModelSelection
} }
export interface BinaryRecord { export interface BinaryRecord {

View File

@@ -13,7 +13,6 @@ const PreferencesSchema = z.object({
lastUsedBinary: z.string().optional(), lastUsedBinary: z.string().optional(),
environmentVariables: z.record(z.string()).default({}), environmentVariables: z.record(z.string()).default({}),
modelRecents: z.array(ModelPreferenceSchema).default([]), modelRecents: z.array(ModelPreferenceSchema).default([]),
agentModelSelections: AgentModelSelectionsSchema.default({}),
diffViewMode: z.enum(["split", "unified"]).default("split"), diffViewMode: z.enum(["split", "unified"]).default("split"),
toolOutputExpansion: z.enum(["expanded", "collapsed"]).default("expanded"), toolOutputExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
diagnosticsExpansion: z.enum(["expanded", "collapsed"]).default("expanded"), diagnosticsExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),

View File

@@ -67,7 +67,11 @@ export function createHttpServer(deps: HttpServerDeps) {
registerFilesystemRoutes(app, { fileSystemBrowser: deps.fileSystemBrowser }) registerFilesystemRoutes(app, { fileSystemBrowser: deps.fileSystemBrowser })
registerMetaRoutes(app, { serverMeta: deps.serverMeta }) registerMetaRoutes(app, { serverMeta: deps.serverMeta })
registerEventRoutes(app, { eventBus: deps.eventBus, registerClient: registerSseClient }) registerEventRoutes(app, { eventBus: deps.eventBus, registerClient: registerSseClient })
registerStorageRoutes(app, { instanceStore: deps.instanceStore, eventBus: deps.eventBus }) registerStorageRoutes(app, {
instanceStore: deps.instanceStore,
eventBus: deps.eventBus,
workspaceManager: deps.workspaceManager,
})
registerInstanceProxyRoutes(app, { workspaceManager: deps.workspaceManager, logger: proxyLogger }) registerInstanceProxyRoutes(app, { workspaceManager: deps.workspaceManager, logger: proxyLogger })

View File

@@ -2,25 +2,36 @@ import { FastifyInstance } from "fastify"
import { z } from "zod" import { z } from "zod"
import { InstanceStore } from "../../storage/instance-store" import { InstanceStore } from "../../storage/instance-store"
import { EventBus } from "../../events/bus" import { EventBus } from "../../events/bus"
import { ModelPreferenceSchema } from "../../config/schema"
import type { InstanceData } from "../../api-types" import type { InstanceData } from "../../api-types"
import { WorkspaceManager } from "../../workspaces/manager"
interface RouteDeps { interface RouteDeps {
instanceStore: InstanceStore instanceStore: InstanceStore
eventBus: EventBus eventBus: EventBus
workspaceManager: WorkspaceManager
} }
const InstanceDataSchema = z.object({ const InstanceDataSchema = z.object({
messageHistory: z.array(z.string()).default([]), messageHistory: z.array(z.string()).default([]),
agentModelSelections: z.record(z.string(), ModelPreferenceSchema).default({}),
}) })
const EMPTY_INSTANCE_DATA: InstanceData = { const EMPTY_INSTANCE_DATA: InstanceData = {
messageHistory: [], messageHistory: [],
agentModelSelections: {},
} }
export function registerStorageRoutes(app: FastifyInstance, deps: RouteDeps) { export function registerStorageRoutes(app: FastifyInstance, deps: RouteDeps) {
const resolveStorageKey = (instanceId: string): string => {
const workspace = deps.workspaceManager.get(instanceId)
return workspace?.path ?? instanceId
}
app.get<{ Params: { id: string } }>("/api/storage/instances/:id", async (request, reply) => { app.get<{ Params: { id: string } }>("/api/storage/instances/:id", async (request, reply) => {
try { try {
const data = await deps.instanceStore.read(request.params.id) const storageId = resolveStorageKey(request.params.id)
const data = await deps.instanceStore.read(storageId)
return data return data
} catch (error) { } catch (error) {
reply.code(500) reply.code(500)
@@ -31,7 +42,8 @@ export function registerStorageRoutes(app: FastifyInstance, deps: RouteDeps) {
app.put<{ Params: { id: string } }>("/api/storage/instances/:id", async (request, reply) => { app.put<{ Params: { id: string } }>("/api/storage/instances/:id", async (request, reply) => {
try { try {
const body = InstanceDataSchema.parse(request.body ?? {}) const body = InstanceDataSchema.parse(request.body ?? {})
await deps.instanceStore.write(request.params.id, body) const storageId = resolveStorageKey(request.params.id)
await deps.instanceStore.write(storageId, body)
deps.eventBus.publish({ type: "instance.dataChanged", instanceId: request.params.id, data: body }) deps.eventBus.publish({ type: "instance.dataChanged", instanceId: request.params.id, data: body })
reply.code(204) reply.code(204)
} catch (error) { } catch (error) {
@@ -42,7 +54,8 @@ export function registerStorageRoutes(app: FastifyInstance, deps: RouteDeps) {
app.delete<{ Params: { id: string } }>("/api/storage/instances/:id", async (request, reply) => { app.delete<{ Params: { id: string } }>("/api/storage/instances/:id", async (request, reply) => {
try { try {
await deps.instanceStore.delete(request.params.id) const storageId = resolveStorageKey(request.params.id)
await deps.instanceStore.delete(storageId)
deps.eventBus.publish({ type: "instance.dataChanged", instanceId: request.params.id, data: EMPTY_INSTANCE_DATA }) deps.eventBus.publish({ type: "instance.dataChanged", instanceId: request.params.id, data: EMPTY_INSTANCE_DATA })
reply.code(204) reply.code(204)
} catch (error) { } catch (error) {

View File

@@ -6,6 +6,7 @@ import type { InstanceData } from "../api-types"
const DEFAULT_INSTANCE_DATA: InstanceData = { const DEFAULT_INSTANCE_DATA: InstanceData = {
messageHistory: [], messageHistory: [],
agentModelSelections: {},
} }
export class InstanceStore { export class InstanceStore {

View File

@@ -6,6 +6,7 @@ export type ConfigData = AppConfig
const DEFAULT_INSTANCE_DATA: InstanceData = { const DEFAULT_INSTANCE_DATA: InstanceData = {
messageHistory: [], messageHistory: [],
agentModelSelections: {},
} }
function isDeepEqual(a: unknown, b: unknown): boolean { function isDeepEqual(a: unknown, b: unknown): boolean {
@@ -150,9 +151,11 @@ export class ServerStorage {
private normalizeInstanceData(data?: InstanceData | null): InstanceData { private normalizeInstanceData(data?: InstanceData | null): InstanceData {
const source = data ?? DEFAULT_INSTANCE_DATA const source = data ?? DEFAULT_INSTANCE_DATA
const messageHistory = Array.isArray(source.messageHistory) ? [...source.messageHistory] : [] const messageHistory = Array.isArray(source.messageHistory) ? [...source.messageHistory] : []
const agentModelSelections = { ...(source.agentModelSelections ?? {}) }
return { return {
...source, ...source,
messageHistory, messageHistory,
agentModelSelections,
} }
} }

View File

@@ -2,6 +2,7 @@ import { render } from "solid-js/web"
import App from "./App" import App from "./App"
import { ThemeProvider } from "./lib/theme" import { ThemeProvider } from "./lib/theme"
import { ConfigProvider } from "./stores/preferences" import { ConfigProvider } from "./stores/preferences"
import { InstanceConfigProvider } from "./stores/instance-config"
import "./index.css" import "./index.css"
import "@git-diff-view/solid/styles/diff-view-pure.css" import "@git-diff-view/solid/styles/diff-view-pure.css"
@@ -14,9 +15,11 @@ if (!root) {
render( render(
() => ( () => (
<ConfigProvider> <ConfigProvider>
<ThemeProvider> <InstanceConfigProvider>
<App /> <ThemeProvider>
</ThemeProvider> <App />
</ThemeProvider>
</InstanceConfigProvider>
</ConfigProvider> </ConfigProvider>
), ),
root, root,

View File

@@ -0,0 +1,138 @@
import { createContext, createMemo, createSignal, onCleanup, type Accessor, type ParentComponent, useContext } from "solid-js"
import type { InstanceData } from "../../../cli/src/api-types"
import { storage } from "../lib/storage"
const DEFAULT_INSTANCE_DATA: InstanceData = { messageHistory: [], agentModelSelections: {} }
const [instanceDataMap, setInstanceDataMap] = createSignal<Map<string, InstanceData>>(new Map())
const loadPromises = new Map<string, Promise<void>>()
const instanceSubscriptions = new Map<string, () => void>()
function cloneInstanceData(data?: InstanceData | null): InstanceData {
const source = data ?? DEFAULT_INSTANCE_DATA
return {
...source,
messageHistory: Array.isArray(source.messageHistory) ? [...source.messageHistory] : [],
agentModelSelections: { ...(source.agentModelSelections ?? {}) },
}
}
function attachSubscription(instanceId: string) {
if (instanceSubscriptions.has(instanceId)) return
const unsubscribe = storage.onInstanceDataChanged(instanceId, (data) => {
setInstanceData(instanceId, data)
})
instanceSubscriptions.set(instanceId, unsubscribe)
}
function detachSubscription(instanceId: string) {
const unsubscribe = instanceSubscriptions.get(instanceId)
if (!unsubscribe) return
unsubscribe()
instanceSubscriptions.delete(instanceId)
}
function setInstanceData(instanceId: string, data: InstanceData) {
setInstanceDataMap((prev) => {
const next = new Map(prev)
next.set(instanceId, cloneInstanceData(data))
return next
})
}
async function ensureInstanceConfig(instanceId: string): Promise<void> {
if (!instanceId) return
if (instanceDataMap().has(instanceId)) return
if (loadPromises.has(instanceId)) {
await loadPromises.get(instanceId)
return
}
const promise = storage
.loadInstanceData(instanceId)
.then((data) => {
setInstanceData(instanceId, data)
attachSubscription(instanceId)
})
.catch((error) => {
console.warn("Failed to load instance data:", error)
setInstanceData(instanceId, DEFAULT_INSTANCE_DATA)
attachSubscription(instanceId)
})
.finally(() => {
loadPromises.delete(instanceId)
})
loadPromises.set(instanceId, promise)
await promise
}
async function updateInstanceConfig(instanceId: string, mutator: (draft: InstanceData) => void): Promise<void> {
if (!instanceId) return
await ensureInstanceConfig(instanceId)
const current = instanceDataMap().get(instanceId) ?? DEFAULT_INSTANCE_DATA
const draft = cloneInstanceData(current)
mutator(draft)
try {
await storage.saveInstanceData(instanceId, draft)
} catch (error) {
console.warn("Failed to persist instance data:", error)
}
setInstanceData(instanceId, draft)
}
function getInstanceConfig(instanceId: string): InstanceData {
return instanceDataMap().get(instanceId) ?? DEFAULT_INSTANCE_DATA
}
function useInstanceConfig(instanceId: string): Accessor<InstanceData> {
const context = useContext(InstanceConfigContext)
if (!context) {
throw new Error("useInstanceConfig must be used within InstanceConfigProvider")
}
return createMemo(() => instanceDataMap().get(instanceId) ?? DEFAULT_INSTANCE_DATA)
}
function clearInstanceConfig(instanceId: string): void {
setInstanceDataMap((prev) => {
if (!prev.has(instanceId)) return prev
const next = new Map(prev)
next.delete(instanceId)
return next
})
detachSubscription(instanceId)
}
interface InstanceConfigContextValue {
getInstanceConfig: typeof getInstanceConfig
ensureInstanceConfig: typeof ensureInstanceConfig
updateInstanceConfig: typeof updateInstanceConfig
clearInstanceConfig: typeof clearInstanceConfig
}
const InstanceConfigContext = createContext<InstanceConfigContextValue>()
const contextValue: InstanceConfigContextValue = {
getInstanceConfig,
ensureInstanceConfig,
updateInstanceConfig,
clearInstanceConfig,
}
const InstanceConfigProvider: ParentComponent = (props) => {
onCleanup(() => {
for (const unsubscribe of instanceSubscriptions.values()) {
unsubscribe()
}
instanceSubscriptions.clear()
})
return <InstanceConfigContext.Provider value={contextValue}>{props.children}</InstanceConfigContext.Provider>
}
export {
InstanceConfigProvider,
useInstanceConfig,
ensureInstanceConfig as ensureInstanceConfigLoaded,
getInstanceConfig,
updateInstanceConfig,
clearInstanceConfig,
}

View File

@@ -7,6 +7,7 @@ import { sseManager } from "../lib/sse-manager"
import { cliApi } from "../lib/api-client" import { cliApi } from "../lib/api-client"
import { cliEvents } from "../lib/cli-events" import { cliEvents } from "../lib/cli-events"
import type { WorkspaceDescriptor, WorkspaceEventPayload, WorkspaceLogEntry } from "../../../cli/src/api-types" import type { WorkspaceDescriptor, WorkspaceEventPayload, WorkspaceLogEntry } from "../../../cli/src/api-types"
import { ensureInstanceConfigLoaded } from "./instance-config"
import { import {
fetchSessions, fetchSessions,
fetchAgents, fetchAgents,
@@ -20,6 +21,7 @@ import { computeDisplayParts } from "./session-messages"
import { withSession, setSessionPendingPermission } from "./session-state" import { withSession, setSessionPendingPermission } from "./session-state"
import { setHasInstances } from "./ui" import { setHasInstances } from "./ui"
const [instances, setInstances] = createSignal<Map<string, Instance>>(new Map()) const [instances, setInstances] = createSignal<Map<string, Instance>>(new Map())
const [activeInstanceId, setActiveInstanceId] = createSignal<string | null>(null) const [activeInstanceId, setActiveInstanceId] = createSignal<string | null>(null)
const [instanceLogs, setInstanceLogs] = createSignal<Map<string, LogEntry[]>>(new Map()) const [instanceLogs, setInstanceLogs] = createSignal<Map<string, LogEntry[]>>(new Map())
@@ -116,6 +118,7 @@ async function hydrateInstanceData(instanceId: string) {
await fetchSessions(instanceId) await fetchSessions(instanceId)
await fetchAgents(instanceId) await fetchAgents(instanceId)
await fetchProviders(instanceId) await fetchProviders(instanceId)
await ensureInstanceConfigLoaded(instanceId)
const instance = instances().get(instanceId) const instance = instances().get(instanceId)
if (!instance?.client) return if (!instance?.client) return
await fetchCommands(instanceId, instance.client) await fetchCommands(instanceId, instance.client)

View File

@@ -1,88 +1,35 @@
import type { InstanceData } from "../../../cli/src/api-types" import type { InstanceData } from "../../../cli/src/api-types"
import { storage } from "../lib/storage" import {
ensureInstanceConfigLoaded,
getInstanceConfig,
updateInstanceConfig,
} from "./instance-config"
const MAX_HISTORY = 100 const MAX_HISTORY = 100
const instanceDataCache = new Map<string, InstanceData>()
const instanceSubscriptions = new Map<string, () => void>()
export async function addToHistory(instanceId: string, text: string): Promise<void> { export async function addToHistory(instanceId: string, text: string): Promise<void> {
const data = await ensureInstanceData(instanceId) if (!instanceId || !text) return
const nextHistory = [text, ...data.messageHistory] await ensureInstanceConfigLoaded(instanceId)
if (nextHistory.length > MAX_HISTORY) { await updateInstanceConfig(instanceId, (draft) => {
nextHistory.length = MAX_HISTORY const nextHistory = [text, ...(draft.messageHistory ?? [])]
} if (nextHistory.length > MAX_HISTORY) {
nextHistory.length = MAX_HISTORY
const nextData: InstanceData = { }
...data, draft.messageHistory = nextHistory
messageHistory: nextHistory, })
}
instanceDataCache.set(instanceId, cloneInstanceData(nextData))
try {
await storage.saveInstanceData(instanceId, nextData)
} catch (err) {
console.warn("Failed to persist message history:", err)
}
} }
export async function getHistory(instanceId: string): Promise<string[]> { export async function getHistory(instanceId: string): Promise<string[]> {
const data = await ensureInstanceData(instanceId) if (!instanceId) return []
return [...data.messageHistory] await ensureInstanceConfigLoaded(instanceId)
const data = getInstanceConfig(instanceId)
return [...(data.messageHistory ?? [])]
} }
export async function clearHistory(instanceId: string): Promise<void> { export async function clearHistory(instanceId: string): Promise<void> {
const data = await ensureInstanceData(instanceId) if (!instanceId) return
const nextData: InstanceData = { await ensureInstanceConfigLoaded(instanceId)
...data, await updateInstanceConfig(instanceId, (draft) => {
messageHistory: [], draft.messageHistory = []
}
instanceDataCache.set(instanceId, cloneInstanceData(nextData))
try {
await storage.saveInstanceData(instanceId, nextData)
} catch (error) {
console.warn("Failed to clear history:", error)
}
}
async function ensureInstanceData(instanceId: string): Promise<InstanceData> {
const cached = instanceDataCache.get(instanceId)
if (cached) {
return cached
}
try {
const data = await storage.loadInstanceData(instanceId)
const normalized = cloneInstanceData(data)
instanceDataCache.set(instanceId, normalized)
attachInstanceSubscription(instanceId)
return normalized
} catch (error) {
console.warn("Failed to load history:", error)
const fallback = cloneInstanceData({ messageHistory: [] })
instanceDataCache.set(instanceId, fallback)
attachInstanceSubscription(instanceId)
return fallback
}
}
function attachInstanceSubscription(instanceId: string) {
if (instanceSubscriptions.has(instanceId)) {
return
}
const unsubscribe = storage.onInstanceDataChanged(instanceId, (data) => {
instanceDataCache.set(instanceId, cloneInstanceData(data))
}) })
instanceSubscriptions.set(instanceId, unsubscribe)
}
function cloneInstanceData(data?: InstanceData | null): InstanceData {
const source: InstanceData = data ?? { messageHistory: [] }
return {
...source,
messageHistory: Array.isArray(source.messageHistory) ? [...source.messageHistory] : [],
}
} }

View File

@@ -1,6 +1,11 @@
import { createContext, createMemo, createSignal, onMount, useContext } from "solid-js" import { createContext, createMemo, createSignal, onMount, useContext } from "solid-js"
import type { Accessor, ParentComponent } from "solid-js" import type { Accessor, ParentComponent } from "solid-js"
import { storage, type ConfigData } from "../lib/storage" import { storage, type ConfigData } from "../lib/storage"
import {
ensureInstanceConfigLoaded,
getInstanceConfig,
updateInstanceConfig as updateInstanceData,
} from "./instance-config"
type DeepReadonly<T> = T extends (...args: any[]) => unknown type DeepReadonly<T> = T extends (...args: any[]) => unknown
? T ? T
@@ -27,7 +32,6 @@ export interface Preferences {
lastUsedBinary?: string lastUsedBinary?: string
environmentVariables: Record<string, string> environmentVariables: Record<string, string>
modelRecents: ModelPreference[] modelRecents: ModelPreference[]
agentModelSelections: AgentModelSelections
diffViewMode: DiffViewMode diffViewMode: DiffViewMode
toolOutputExpansion: ExpansionPreference toolOutputExpansion: ExpansionPreference
diagnosticsExpansion: ExpansionPreference diagnosticsExpansion: ExpansionPreference
@@ -53,7 +57,6 @@ const defaultPreferences: Preferences = {
showThinkingBlocks: false, showThinkingBlocks: false,
environmentVariables: {}, environmentVariables: {},
modelRecents: [], modelRecents: [],
agentModelSelections: {},
diffViewMode: "split", diffViewMode: "split",
toolOutputExpansion: "expanded", toolOutputExpansion: "expanded",
diagnosticsExpansion: "expanded", diagnosticsExpansion: "expanded",
@@ -71,32 +74,24 @@ function deepEqual(a: unknown, b: unknown): boolean {
return false return false
} }
function normalizePreferences(pref?: Partial<Preferences>): Preferences { function normalizePreferences(pref?: Partial<Preferences> & { agentModelSelections?: unknown }): Preferences {
const sanitized = pref ?? {}
const environmentVariables = { const environmentVariables = {
...defaultPreferences.environmentVariables, ...defaultPreferences.environmentVariables,
...(pref?.environmentVariables ?? {}), ...(sanitized.environmentVariables ?? {}),
} }
const sourceModelRecents = pref?.modelRecents ?? defaultPreferences.modelRecents const sourceModelRecents = sanitized.modelRecents ?? defaultPreferences.modelRecents
const modelRecents = sourceModelRecents.map((item) => ({ ...item })) const modelRecents = sourceModelRecents.map((item) => ({ ...item }))
const sourceAgentSelections = pref?.agentModelSelections ?? defaultPreferences.agentModelSelections
const agentModelSelections: AgentModelSelections = {}
for (const [instanceId, selections] of Object.entries(sourceAgentSelections)) {
agentModelSelections[instanceId] = Object.fromEntries(
Object.entries(selections).map(([agentId, selection]) => [agentId, { ...selection }]),
)
}
return { return {
showThinkingBlocks: pref?.showThinkingBlocks ?? defaultPreferences.showThinkingBlocks, showThinkingBlocks: sanitized.showThinkingBlocks ?? defaultPreferences.showThinkingBlocks,
lastUsedBinary: pref?.lastUsedBinary ?? defaultPreferences.lastUsedBinary, lastUsedBinary: sanitized.lastUsedBinary ?? defaultPreferences.lastUsedBinary,
environmentVariables, environmentVariables,
modelRecents, modelRecents,
agentModelSelections, diffViewMode: sanitized.diffViewMode ?? defaultPreferences.diffViewMode,
diffViewMode: pref?.diffViewMode ?? defaultPreferences.diffViewMode, toolOutputExpansion: sanitized.toolOutputExpansion ?? defaultPreferences.toolOutputExpansion,
toolOutputExpansion: pref?.toolOutputExpansion ?? defaultPreferences.toolOutputExpansion, diagnosticsExpansion: sanitized.diagnosticsExpansion ?? defaultPreferences.diagnosticsExpansion,
diagnosticsExpansion: pref?.diagnosticsExpansion ?? defaultPreferences.diagnosticsExpansion,
} }
} }
@@ -122,10 +117,22 @@ function buildFallbackConfig(): ConfigData {
return normalizeConfig() return normalizeConfig()
} }
function removeLegacyAgentSelections(config?: ConfigData | null): { cleaned: ConfigData; migrated: boolean } {
const migrated = Boolean((config?.preferences as { agentModelSelections?: unknown } | undefined)?.agentModelSelections)
const cleanedConfig = normalizeConfig(config)
return { cleaned: cleanedConfig, migrated }
}
async function syncConfig(source?: ConfigData): Promise<void> { async function syncConfig(source?: ConfigData): Promise<void> {
try { try {
const configData = source ?? (await storage.loadConfig()) const loaded = source ?? (await storage.loadConfig())
applyConfig(configData) const { cleaned, migrated } = removeLegacyAgentSelections(loaded)
applyConfig(cleaned)
if (migrated) {
void storage.updateConfig(cleaned).catch((error: unknown) => {
console.error("Failed to persist legacy config cleanup:", error)
})
}
} catch (error) { } catch (error) {
console.error("Failed to load config:", error) console.error("Failed to load config:", error)
applyConfig(buildFallbackConfig()) applyConfig(buildFallbackConfig())
@@ -328,27 +335,25 @@ function addRecentModelPreference(model: ModelPreference): void {
updatePreferences({ modelRecents: updated }) updatePreferences({ modelRecents: updated })
} }
function setAgentModelPreference(instanceId: string, agent: string, model: ModelPreference): void { async function setAgentModelPreference(instanceId: string, agent: string, model: ModelPreference): Promise<void> {
if (!instanceId || !agent || !model.providerId || !model.modelId) return if (!instanceId || !agent || !model.providerId || !model.modelId) return
const selections = preferences().agentModelSelections ?? {} await ensureInstanceConfigLoaded(instanceId)
const instanceSelections = selections[instanceId] ?? {} await updateInstanceData(instanceId, (draft) => {
const existing = instanceSelections[agent] const selections = { ...(draft.agentModelSelections ?? {}) }
if (existing && existing.providerId === model.providerId && existing.modelId === model.modelId) { const existing = selections[agent]
return if (existing && existing.providerId === model.providerId && existing.modelId === model.modelId) {
} return
updatePreferences({ }
agentModelSelections: { selections[agent] = model
...selections, draft.agentModelSelections = selections
[instanceId]: {
...instanceSelections,
[agent]: model,
},
},
}) })
} }
function getAgentModelPreference(instanceId: string, agent: string): ModelPreference | undefined { async function getAgentModelPreference(instanceId: string, agent: string): Promise<ModelPreference | undefined> {
return preferences().agentModelSelections?.[instanceId]?.[agent] if (!instanceId || !agent) return undefined
await ensureInstanceConfigLoaded(instanceId)
const selections = getInstanceConfig(instanceId).agentModelSelections ?? {}
return selections[agent]
} }
void ensureConfigLoaded().catch((error: unknown) => { void ensureConfigLoaded().catch((error: unknown) => {

View File

@@ -306,7 +306,7 @@ async function updateSessionAgent(instanceId: string, sessionId: string, agent:
}) })
if (agent && shouldApplyModel) { if (agent && shouldApplyModel) {
setAgentModelPreference(instanceId, agent, nextModel) await setAgentModelPreference(instanceId, agent, nextModel)
} }
if (shouldApplyModel) { if (shouldApplyModel) {
@@ -335,7 +335,7 @@ async function updateSessionModel(
}) })
if (session.agent) { if (session.agent) {
setAgentModelPreference(instanceId, session.agent, model) await setAgentModelPreference(instanceId, session.agent, model)
} }
addRecentModelPreference(model) addRecentModelPreference(model)

View File

@@ -159,7 +159,7 @@ async function createSession(instanceId: string, agent?: string): Promise<Sessio
const defaultModel = await getDefaultModel(instanceId, selectedAgent) const defaultModel = await getDefaultModel(instanceId, selectedAgent)
if (selectedAgent && isModelValid(instanceId, defaultModel)) { if (selectedAgent && isModelValid(instanceId, defaultModel)) {
setAgentModelPreference(instanceId, selectedAgent, defaultModel) await setAgentModelPreference(instanceId, selectedAgent, defaultModel)
} }
setLoading((prev) => { setLoading((prev) => {

View File

@@ -32,13 +32,6 @@ async function getDefaultModel(
const instanceProviders = providers().get(instanceId) || [] const instanceProviders = providers().get(instanceId) || []
const instanceAgents = agents().get(instanceId) || [] const instanceAgents = agents().get(instanceId) || []
if (agentName) {
const stored = getAgentModelPreference(instanceId, agentName)
if (isModelValid(instanceId, stored)) {
return stored
}
}
if (agentName) { if (agentName) {
const agent = instanceAgents.find((a) => a.name === agentName) const agent = instanceAgents.find((a) => a.name === agentName)
if (agent && agent.model && isModelValid(instanceId, agent.model)) { if (agent && agent.model && isModelValid(instanceId, agent.model)) {
@@ -47,6 +40,11 @@ async function getDefaultModel(
modelId: agent.model.modelId, modelId: agent.model.modelId,
} }
} }
const stored = await getAgentModelPreference(instanceId, agentName)
if (isModelValid(instanceId, stored)) {
return stored
}
} }
const recent = getRecentModelPreferenceForInstance(instanceId) const recent = getRecentModelPreferenceForInstance(instanceId)