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 { create } from "zustand"
|
||||||
import { useConnections } from "./connections"
|
import { useConnections } from "./connections"
|
||||||
import type { Agent, Command } from "../lib/sdk"
|
import type { Agent, Command } from "../lib/sdk"
|
||||||
|
import { chooseModelSelection } from "../lib/model-selection"
|
||||||
|
|
||||||
export interface ProviderModel {
|
export interface ProviderModel {
|
||||||
id: string
|
id: string
|
||||||
@@ -92,19 +93,16 @@ export const useCatalog = create<CatalogState>((set, get) => ({
|
|||||||
const current = get().agent
|
const current = get().agent
|
||||||
const agent = current && visible.some((a) => a.name === current) ? current : visible[0]?.name || "build"
|
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 existing = get().model
|
||||||
const fallback = (() => {
|
const defaultAgent = visible[0]
|
||||||
const defaultAgent = visible[0]
|
const model = chooseModelSelection({
|
||||||
if (defaultAgent?.model) return defaultAgent.model
|
providers,
|
||||||
for (const p of providers) {
|
defaults,
|
||||||
const defaultModelID = defaults[p.id]
|
existing,
|
||||||
const match = defaultModelID ? p.models.find((m) => m.id === defaultModelID) : p.models[0]
|
agentModel: defaultAgent?.model || null,
|
||||||
if (match) return { providerID: p.id, modelID: match.id }
|
})
|
||||||
}
|
|
||||||
return null
|
|
||||||
})()
|
|
||||||
const model = existing || fallback
|
|
||||||
|
|
||||||
set({ agents: visible, commands, providers, defaults, agent, model, loaded: true })
|
set({ agents: visible, commands, providers, defaults, agent, model, loaded: true })
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user