fix: model selection precedence to prefer provider default over agent model (#36)
Fixes #35. The app was selecting claude-sonnet-4-6 (agent default) instead of gpt-5.4 (provider default), causing send_message failures in CI where only Azure provider is available.
This commit is contained in:
50
src/lib/model-selection.test.ts
Normal file
50
src/lib/model-selection.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { test } from "node:test"
|
||||
import assert from "node:assert/strict"
|
||||
import { chooseModelSelection } from "./model-selection.ts"
|
||||
|
||||
const providers = [
|
||||
{
|
||||
id: "azure",
|
||||
models: [{ id: "gpt-5.4" }, { id: "gpt-5.2" }],
|
||||
},
|
||||
]
|
||||
|
||||
test("keeps existing selection when still available", () => {
|
||||
const selected = chooseModelSelection({
|
||||
providers,
|
||||
defaults: { azure: "gpt-5.4" },
|
||||
existing: { providerID: "azure", modelID: "gpt-5.2" },
|
||||
agentModel: { providerID: "azure", modelID: "claude-sonnet-4-6" },
|
||||
})
|
||||
assert.deepEqual(selected, { providerID: "azure", modelID: "gpt-5.2" })
|
||||
})
|
||||
|
||||
test("prefers provider default over unavailable agent model", () => {
|
||||
const selected = chooseModelSelection({
|
||||
providers,
|
||||
defaults: { azure: "gpt-5.4" },
|
||||
existing: null,
|
||||
agentModel: { providerID: "azure", modelID: "claude-sonnet-4-6" },
|
||||
})
|
||||
assert.deepEqual(selected, { providerID: "azure", modelID: "gpt-5.4" })
|
||||
})
|
||||
|
||||
test("falls back to first connected provider model when default missing", () => {
|
||||
const selected = chooseModelSelection({
|
||||
providers,
|
||||
defaults: {},
|
||||
existing: null,
|
||||
agentModel: null,
|
||||
})
|
||||
assert.deepEqual(selected, { providerID: "azure", modelID: "gpt-5.4" })
|
||||
})
|
||||
|
||||
test("returns null when no connected providers", () => {
|
||||
const selected = chooseModelSelection({
|
||||
providers: [],
|
||||
defaults: {},
|
||||
existing: null,
|
||||
agentModel: null,
|
||||
})
|
||||
assert.equal(selected, null)
|
||||
})
|
||||
50
src/lib/model-selection.ts
Normal file
50
src/lib/model-selection.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
export interface ProviderModelRef {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface ProviderRef {
|
||||
id: string
|
||||
models: ProviderModelRef[]
|
||||
}
|
||||
|
||||
export interface ModelSelection {
|
||||
providerID: string
|
||||
modelID: string
|
||||
}
|
||||
|
||||
export function isModelAvailable(
|
||||
providers: ProviderRef[],
|
||||
selection: ModelSelection | null | undefined,
|
||||
): selection is ModelSelection {
|
||||
if (!selection) return false
|
||||
const provider = providers.find((p) => p.id === selection.providerID)
|
||||
if (!provider) return false
|
||||
return provider.models.some((m) => m.id === selection.modelID)
|
||||
}
|
||||
|
||||
export function chooseModelSelection(params: {
|
||||
providers: ProviderRef[]
|
||||
defaults: Record<string, string>
|
||||
existing: ModelSelection | null
|
||||
agentModel: ModelSelection | null
|
||||
}): ModelSelection | null {
|
||||
const { providers, defaults, existing, agentModel } = params
|
||||
|
||||
if (isModelAvailable(providers, existing)) return existing
|
||||
|
||||
for (const provider of providers) {
|
||||
const defaultModelID = defaults[provider.id]
|
||||
if (!defaultModelID) continue
|
||||
if (provider.models.some((m) => m.id === defaultModelID)) {
|
||||
return { providerID: provider.id, modelID: defaultModelID }
|
||||
}
|
||||
}
|
||||
|
||||
if (providers.length > 0 && providers[0].models.length > 0) {
|
||||
return { providerID: providers[0].id, modelID: providers[0].models[0].id }
|
||||
}
|
||||
|
||||
if (isModelAvailable(providers, agentModel)) return agentModel
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { create } from "zustand"
|
||||
import { useConnections } from "./connections"
|
||||
import type { Agent, Command } from "../lib/sdk"
|
||||
import { chooseModelSelection } from "../lib/model-selection"
|
||||
|
||||
export interface ProviderModel {
|
||||
id: string
|
||||
@@ -92,19 +93,16 @@ export const useCatalog = create<CatalogState>((set, get) => ({
|
||||
const current = get().agent
|
||||
const agent = current && visible.some((a) => a.name === current) ? current : visible[0]?.name || "build"
|
||||
|
||||
// Default model: use default agent's model, or first connected provider's default model
|
||||
// Default model: keep valid existing selection; otherwise prefer connected
|
||||
// provider defaults, then first connected model; agent model is last fallback.
|
||||
const existing = get().model
|
||||
const fallback = (() => {
|
||||
const defaultAgent = visible[0]
|
||||
if (defaultAgent?.model) return defaultAgent.model
|
||||
for (const p of providers) {
|
||||
const defaultModelID = defaults[p.id]
|
||||
const match = defaultModelID ? p.models.find((m) => m.id === defaultModelID) : p.models[0]
|
||||
if (match) return { providerID: p.id, modelID: match.id }
|
||||
}
|
||||
return null
|
||||
})()
|
||||
const model = existing || fallback
|
||||
const model = chooseModelSelection({
|
||||
providers,
|
||||
defaults,
|
||||
existing,
|
||||
agentModel: defaultAgent?.model || null,
|
||||
})
|
||||
|
||||
set({ agents: visible, commands, providers, defaults, agent, model, loaded: true })
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user