fix(auth): deterministic fallback profile selection when default key missing

For models without explicit authProfile:
- use bare provider key when present
- use :default when present
- otherwise choose first available provider profile (sorted)

Also reflect inferred profile in TUI status when model has no explicit profile.
pull/21353/head
PabloGNU 2026-04-07 18:32:07 +02:00
parent 82746621de
commit 2e0666d19d
2 changed files with 36 additions and 7 deletions

View File

@ -1,5 +1,5 @@
import { createStore } from "solid-js/store"
import { batch, createEffect, createMemo } from "solid-js"
import { batch, createEffect, createMemo, createResource } from "solid-js"
import { useSync } from "@tui/context/sync"
import { useTheme } from "@tui/context/theme"
import { uniqueBy } from "remeda"
@ -9,6 +9,7 @@ import { iife } from "@/util/iife"
import { createSimpleContext } from "./helper"
import { useToast } from "../ui/toast"
import { Provider } from "@/provider/provider"
import { Auth } from "@/auth"
import { useArgs } from "./args"
import { useSDK } from "./sdk"
import { RGBA } from "@opentui/core"
@ -208,6 +209,20 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
) ?? undefined
)
})
const [auths] = createResource(async () => Auth.all())
const inferredProfile = (providerID: string) => {
const data = auths()
if (!data) return undefined
if (providerID in data) return undefined
if (`${providerID}:default` in data) return "default"
const prefix = `${providerID}:`
const profiles = Object.keys(data)
.filter((key) => key.startsWith(prefix))
.map((key) => key.slice(prefix.length))
.sort()
return profiles[0]
}
return {
current: currentModel,
@ -232,10 +247,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
const provider = sync.data.provider.find((x) => x.id === value.providerID)
const info = provider?.models[value.modelID]
const profile = value.authProfile ?? inferredProfile(value.providerID)
return {
provider: provider?.name ?? value.providerID,
model: info?.name ?? value.modelID,
profile: value.authProfile,
profile,
reasoning: info?.capabilities?.reasoning ?? false,
}
}),

View File

@ -90,16 +90,29 @@ export namespace LLM {
modelID: input.model.id,
providerID: input.model.providerID,
})
const authKey = input.model.authProfile
? `${input.model.providerID}:${input.model.authProfile}`
: input.model.providerID
const [cfg, provider, auth] = await Promise.all([
const [cfg, provider, auths] = await Promise.all([
Config.get(),
Provider.getProvider(input.model.providerID),
Auth.get(authKey),
Auth.all(),
])
const chooseProfile = () => {
if (input.model.authProfile) return input.model.authProfile
if (input.model.providerID in auths) return undefined
if (`${input.model.providerID}:default` in auths) return "default"
const prefix = `${input.model.providerID}:`
const profiles = Object.keys(auths)
.filter((key) => key.startsWith(prefix))
.map((key) => key.slice(prefix.length))
.sort()
if (profiles.length === 0) return undefined
return profiles[0]
}
const profile = chooseProfile()
const authKey = profile ? `${input.model.providerID}:${profile}` : input.model.providerID
const auth = auths[authKey]
const runtimeModel = {
...input.model,
...(profile ? { authProfile: profile } : {}),
options:
auth?.type === "api"
? mergeDeep(input.model.options, { apiKey: auth.key })