feat(provider,web,asr): enhance model management with explicit provider metadata (#2701)

* feat(provider,web): enhance model management with provider options

* fix(asr): enhance compatibility for ElevenLabs transcription model

* fix(provider,web): align provider availability predicates and add flow gating

* fix(web,asr): preserve legacy elevenlabs transcription configs

* fix(provider,web,asr): normalize elevenlabs configs and gate default chat models

* fix: tighten provider catalog and elevenlabs compatibility
This commit is contained in:
LC
2026-05-06 16:06:49 +08:00
committed by GitHub
parent 4d3070e849
commit 81a050555d
26 changed files with 2341 additions and 193 deletions
@@ -1,8 +1,12 @@
import { IconLoader2 } from "@tabler/icons-react"
import { useEffect, useState } from "react"
import { useEffect, useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
import { addModel, setDefaultModel } from "@/api/models"
import {
type ModelProviderOption,
addModel,
setDefaultModel,
} from "@/api/models"
import { ConfigChangeNotice } from "@/components/config-change-notice"
import { maskedSecretPlaceholder } from "@/components/secret-placeholder"
import {
@@ -13,6 +17,13 @@ import {
} from "@/components/shared-form"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
Sheet,
SheetContent,
@@ -25,6 +36,15 @@ import { Textarea } from "@/components/ui/textarea"
import { showSaveSuccessOrRestartToast } from "@/lib/restart-required"
import { refreshGatewayState } from "@/store/gateway"
import {
findProviderOption,
getProviderDefaultAPIBase,
getProviderDefaultAuthMethod,
getProviderLabel,
getSortedProviderOptions,
isProviderAuthMethodLocked,
} from "./provider-label"
interface AddForm {
modelName: string
provider: string
@@ -46,7 +66,7 @@ interface AddForm {
const EMPTY_ADD_FORM: AddForm = {
modelName: "",
provider: "",
provider: "openai",
model: "",
apiBase: "",
apiKey: "",
@@ -68,6 +88,7 @@ interface AddModelSheetProps {
onClose: () => void
onSaved: () => void
existingModelNames: string[]
providerOptions: ModelProviderOption[]
}
export function AddModelSheet({
@@ -75,6 +96,7 @@ export function AddModelSheet({
onClose,
onSaved,
existingModelNames,
providerOptions,
}: AddModelSheetProps) {
const { t } = useTranslation()
const [form, setForm] = useState<AddForm>(EMPTY_ADD_FORM)
@@ -88,6 +110,37 @@ export function AddModelSheet({
form.apiKey,
t("models.field.apiKeyPlaceholder"),
)
const sortedProviderOptions = useMemo(
() => getSortedProviderOptions(providerOptions),
[providerOptions],
)
const creatableProviderOptions = useMemo(
() => sortedProviderOptions.filter((option) => option.create_allowed),
[sortedProviderOptions],
)
const selectedProviderOption = findProviderOption(
form.provider,
providerOptions,
)
const authMethodLocked = isProviderAuthMethodLocked(
form.provider,
providerOptions,
)
const defaultAuthMethod = getProviderDefaultAuthMethod(
form.provider,
providerOptions,
)
const effectiveAuthMethod = (
authMethodLocked ? defaultAuthMethod : form.authMethod
)
.trim()
.toLowerCase()
const isOAuth = effectiveAuthMethod === "oauth"
const defaultModelAllowed =
selectedProviderOption?.default_model_allowed !== false
const apiBasePlaceholder =
getProviderDefaultAPIBase(form.provider, providerOptions) ||
"https://api.example.com/v1"
const isDirty =
JSON.stringify(form) !== JSON.stringify(EMPTY_ADD_FORM) || setAsDefault
@@ -108,6 +161,9 @@ export function AddModelSheet({
} else if (existingModelNames.some((name) => name.trim() === modelName)) {
errors.modelName = t("models.add.errorDuplicateModelName")
}
if (!selectedProviderOption) {
errors.provider = t("models.field.providerInvalid")
}
if (!form.model.trim()) errors.model = t("models.add.errorRequired")
setFieldErrors(errors)
return Object.keys(errors).length === 0
@@ -122,22 +178,47 @@ export function AddModelSheet({
}
}
const setProvider = (value: string) => {
setForm((f) => {
const previousOption = findProviderOption(f.provider, providerOptions)
const nextOption = findProviderOption(value, providerOptions)
let authMethod = f.authMethod
if (nextOption?.auth_method_locked) {
authMethod = nextOption.default_auth_method ?? ""
} else if (
previousOption?.auth_method_locked &&
f.authMethod === (previousOption.default_auth_method ?? "")
) {
authMethod = ""
}
return { ...f, provider: value, authMethod }
})
const nextOption = findProviderOption(value, providerOptions)
if (nextOption?.default_model_allowed === false) {
setSetAsDefault(false)
}
if (fieldErrors.provider) {
setFieldErrors((prev) => ({ ...prev, provider: undefined }))
}
}
const handleSave = async () => {
if (!validate()) return
setSaving(true)
setServerError("")
try {
const modelName = form.modelName.trim()
const provider = form.provider.trim()
const modelId = form.model.trim()
await addModel({
model_name: modelName,
provider: provider || undefined,
provider: form.provider.trim(),
model: modelId,
api_base: form.apiBase.trim() || undefined,
api_key: form.apiKey.trim() || undefined,
proxy: form.proxy.trim() || undefined,
auth_method: form.authMethod.trim() || undefined,
auth_method: authMethodLocked
? defaultAuthMethod || undefined
: form.authMethod.trim() || undefined,
connect_mode: form.connectMode.trim() || undefined,
workspace: form.workspace.trim() || undefined,
rpm: form.rpm ? Number(form.rpm) : undefined,
@@ -208,12 +289,29 @@ export function AddModelSheet({
<Field
label={t("models.field.provider")}
hint={t("models.field.providerHint")}
error={fieldErrors.provider}
required
>
<Input
value={form.provider}
onChange={setField("provider")}
placeholder={t("models.field.providerPlaceholder")}
/>
<Select
value={selectedProviderOption?.id}
onValueChange={setProvider}
>
<SelectTrigger
className="w-full"
aria-invalid={!!fieldErrors.provider}
>
<SelectValue
placeholder={t("models.field.providerPlaceholder")}
/>
</SelectTrigger>
<SelectContent>
{creatableProviderOptions.map((option) => (
<SelectItem key={option.id} value={option.id}>
{getProviderLabel(option.id)}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<Field
@@ -232,27 +330,38 @@ export function AddModelSheet({
)}
</Field>
<Field label={t("models.field.apiKey")}>
<KeyInput
value={form.apiKey}
onChange={(v) => setForm((f) => ({ ...f, apiKey: v }))}
placeholder={apiKeyPlaceholder}
/>
</Field>
{!isOAuth && (
<Field label={t("models.field.apiKey")}>
<KeyInput
value={form.apiKey}
onChange={(v) => setForm((f) => ({ ...f, apiKey: v }))}
placeholder={apiKeyPlaceholder}
/>
</Field>
)}
<Field label={t("models.field.apiBase")}>
<Field
label={t("models.field.apiBase")}
hint={isOAuth ? t("models.edit.oauthNote") : undefined}
>
<Input
value={form.apiBase}
onChange={setField("apiBase")}
placeholder="https://api.example.com/v1"
placeholder={apiBasePlaceholder}
disabled={isOAuth}
/>
</Field>
<SwitchCardField
label={t("models.defaultOnSave.label")}
hint={t("models.defaultOnSave.description")}
hint={
defaultModelAllowed
? t("models.defaultOnSave.description")
: t("models.defaultOnSave.unsupportedProvider")
}
checked={setAsDefault}
onCheckedChange={setSetAsDefault}
disabled={!defaultModelAllowed}
/>
<AdvancedSection>
@@ -269,12 +378,17 @@ export function AddModelSheet({
<Field
label={t("models.field.authMethod")}
hint={t("models.field.authMethodHint")}
hint={
authMethodLocked
? t("models.field.authMethodManagedHint")
: t("models.field.authMethodHint")
}
>
<Input
value={form.authMethod}
value={authMethodLocked ? defaultAuthMethod : form.authMethod}
onChange={setField("authMethod")}
placeholder="oauth"
disabled={authMethodLocked}
/>
</Field>
@@ -1,8 +1,13 @@
import { IconLoader2 } from "@tabler/icons-react"
import { useEffect, useState } from "react"
import { useEffect, useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
import { type ModelInfo, setDefaultModel, updateModel } from "@/api/models"
import {
type ModelInfo,
type ModelProviderOption,
setDefaultModel,
updateModel,
} from "@/api/models"
import { ConfigChangeNotice } from "@/components/config-change-notice"
import { maskedSecretPlaceholder } from "@/components/secret-placeholder"
import {
@@ -13,6 +18,13 @@ import {
} from "@/components/shared-form"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
Sheet,
SheetContent,
@@ -25,6 +37,15 @@ import { Textarea } from "@/components/ui/textarea"
import { showSaveSuccessOrRestartToast } from "@/lib/restart-required"
import { refreshGatewayState } from "@/store/gateway"
import {
findProviderOption,
getProviderDefaultAPIBase,
getProviderDefaultAuthMethod,
getProviderLabel,
getSortedProviderOptions,
isProviderAuthMethodLocked,
} from "./provider-label"
interface EditForm {
provider: string
modelId: string
@@ -45,6 +66,7 @@ interface EditForm {
interface EditModelSheetProps {
model: ModelInfo | null
providerOptions: ModelProviderOption[]
open: boolean
onClose: () => void
onSaved: () => void
@@ -76,6 +98,7 @@ function buildInitialEditForm(model: ModelInfo): EditForm {
export function EditModelSheet({
model,
providerOptions,
open,
onClose,
onSaved,
@@ -102,26 +125,99 @@ export function EditModelSheet({
const [setAsDefault, setSetAsDefault] = useState(false)
const [error, setError] = useState("")
const initialForm = model ? buildInitialEditForm(model) : null
const sortedProviderOptions = useMemo(
() => getSortedProviderOptions(providerOptions),
[providerOptions],
)
const currentProviderID = model
? (findProviderOption(model.provider, providerOptions)?.id ??
model.provider?.trim().toLowerCase() ??
"")
: ""
const selectedProviderOption = findProviderOption(
form.provider,
providerOptions,
)
const authMethodLocked = isProviderAuthMethodLocked(
form.provider,
providerOptions,
)
const defaultAuthMethod = getProviderDefaultAuthMethod(
form.provider,
providerOptions,
)
const effectiveAuthMethod = (
authMethodLocked ? defaultAuthMethod : form.authMethod
)
.trim()
.toLowerCase()
const providerError = selectedProviderOption
? ""
: t("models.field.providerInvalid")
const defaultModelAllowed =
selectedProviderOption?.default_model_allowed !== false
const willClearDefaultOnSave =
model?.is_default === true && defaultModelAllowed === false
const apiBasePlaceholder =
getProviderDefaultAPIBase(form.provider, providerOptions) ||
"https://api.example.com/v1"
const isDirty =
model != null &&
(JSON.stringify(form) !== JSON.stringify(initialForm) ||
setAsDefault !== model.is_default)
useEffect(() => {
if (model) {
setForm(buildInitialEditForm(model))
setSetAsDefault(model.is_default)
setError("")
if (model) {
const initialForm = buildInitialEditForm(model)
const option = findProviderOption(initialForm.provider, providerOptions)
if (option?.auth_method_locked && !initialForm.authMethod) {
initialForm.authMethod = option.default_auth_method ?? ""
}
}, [model])
setForm(initialForm)
setSetAsDefault(model.is_default && model.default_model_allowed !== false)
setError("")
}
}, [model, providerOptions])
const setField =
(key: keyof EditForm) =>
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (error) {
setError("")
}
setForm((f) => ({ ...f, [key]: e.target.value }))
}
const setProvider = (value: string) => {
if (error) {
setError("")
}
setForm((f) => {
const previousOption = findProviderOption(f.provider, providerOptions)
const nextOption = findProviderOption(value, providerOptions)
let authMethod = f.authMethod
if (nextOption?.auth_method_locked) {
authMethod = nextOption.default_auth_method ?? ""
} else if (
previousOption?.auth_method_locked &&
f.authMethod === (previousOption.default_auth_method ?? "")
) {
authMethod = ""
}
return { ...f, provider: value, authMethod }
})
const nextOption = findProviderOption(value, providerOptions)
if (nextOption?.default_model_allowed === false) {
setSetAsDefault(false)
}
}
const handleSave = async () => {
if (!model) return
if (!selectedProviderOption) {
setError(providerError)
return
}
if (!form.modelId.trim()) {
setError(t("models.add.errorRequired"))
return
@@ -136,7 +232,9 @@ export function EditModelSheet({
api_base: form.apiBase || undefined,
api_key: form.apiKey || undefined,
proxy: form.proxy || undefined,
auth_method: form.authMethod || undefined,
auth_method: authMethodLocked
? defaultAuthMethod || undefined
: form.authMethod || undefined,
connect_mode: form.connectMode || undefined,
workspace: form.workspace || undefined,
rpm: form.rpm ? Number(form.rpm) : undefined,
@@ -172,7 +270,7 @@ export function EditModelSheet({
}
}
const isOAuth = model?.auth_method === "oauth"
const isOAuth = effectiveAuthMethod === "oauth"
const hasSavedAPIKey = Boolean(model?.api_key)
const apiKeyPlaceholder = hasSavedAPIKey
? maskedSecretPlaceholder(
@@ -201,12 +299,36 @@ export function EditModelSheet({
<Field
label={t("models.field.provider")}
hint={t("models.field.providerHint")}
error={providerError}
required
>
<Input
value={form.provider}
onChange={setField("provider")}
placeholder={t("models.field.providerPlaceholder")}
/>
<Select
value={selectedProviderOption?.id}
onValueChange={setProvider}
>
<SelectTrigger
className="w-full"
aria-invalid={!!providerError}
>
<SelectValue
placeholder={t("models.field.providerPlaceholder")}
/>
</SelectTrigger>
<SelectContent>
{sortedProviderOptions.map((option) => (
<SelectItem
key={option.id}
value={option.id}
disabled={
!option.create_allowed &&
option.id !== currentProviderID
}
>
{getProviderLabel(option.id)}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<Field
@@ -241,16 +363,23 @@ export function EditModelSheet({
<Input
value={form.apiBase}
onChange={setField("apiBase")}
placeholder="https://api.example.com/v1"
placeholder={apiBasePlaceholder}
disabled={isOAuth}
/>
</Field>
<SwitchCardField
label={t("models.defaultOnSave.label")}
hint={t("models.defaultOnSave.description")}
hint={
willClearDefaultOnSave
? t("models.defaultOnSave.clearOnSave")
: defaultModelAllowed
? t("models.defaultOnSave.description")
: t("models.defaultOnSave.unsupportedProvider")
}
checked={setAsDefault}
onCheckedChange={setSetAsDefault}
disabled={!defaultModelAllowed}
/>
<AdvancedSection>
@@ -267,12 +396,17 @@ export function EditModelSheet({
<Field
label={t("models.field.authMethod")}
hint={t("models.field.authMethodHint")}
hint={
authMethodLocked
? t("models.field.authMethodManagedHint")
: t("models.field.authMethodHint")
}
>
<Input
value={form.authMethod}
value={authMethodLocked ? defaultAuthMethod : form.authMethod}
onChange={setField("authMethod")}
placeholder="oauth"
disabled={authMethodLocked}
/>
</Field>
@@ -36,7 +36,10 @@ export function ModelCard({
const status = model.status
const statusLabel = t(`models.status.${status}`)
const canSetDefault =
model.available && !model.is_default && !model.is_virtual
model.available &&
!model.is_default &&
!model.is_virtual &&
model.default_model_allowed !== false
const setDefaultLabel = t("models.action.setDefault")
const setDefaultDisabledReason = (() => {
@@ -45,6 +48,9 @@ export function ModelCard({
return t("models.action.setDefaultDisabled.unavailable")
if (model.is_default) return t("models.action.setDefaultDisabled.isDefault")
if (model.is_virtual) return t("models.action.setDefaultDisabled.isVirtual")
if (model.default_model_allowed === false) {
return t("models.action.setDefaultDisabled.unsupportedProvider")
}
return setDefaultLabel
})()
@@ -3,7 +3,12 @@ import { useCallback, useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
import { toast } from "sonner"
import { type ModelInfo, getModels, setDefaultModel } from "@/api/models"
import {
type ModelInfo,
type ModelProviderOption,
getModels,
setDefaultModel,
} from "@/api/models"
import { PageHeader } from "@/components/page-header"
import { Button } from "@/components/ui/button"
import { showSaveSuccessOrRestartToast } from "@/lib/restart-required"
@@ -12,41 +17,13 @@ import { refreshGatewayState } from "@/store/gateway"
import { AddModelSheet } from "./add-model-sheet"
import { DeleteModelDialog } from "./delete-model-dialog"
import { EditModelSheet } from "./edit-model-sheet"
import { getProviderKey, getProviderLabel } from "./provider-label"
import {
PROVIDER_PRIORITY,
getProviderKey,
getProviderLabel,
} from "./provider-label"
import { ProviderSection } from "./provider-section"
const PROVIDER_PRIORITY: Record<string, number> = {
volcengine: 0,
openai: 1,
gemini: 2,
anthropic: 3,
zhipu: 4,
deepseek: 5,
openrouter: 6,
"qwen-portal": 7,
"qwen-intl": 8,
moonshot: 9,
groq: 10,
"github-copilot": 11,
antigravity: 12,
nvidia: 13,
cerebras: 14,
shengsuanyun: 15,
venice: 16,
vivgrid: 17,
minimax: 18,
longcat: 19,
modelscope: 20,
mistral: 21,
avian: 22,
azure: 23,
ollama: 24,
vllm: 25,
lmstudio: 26,
zai: 27,
mimo: 28,
}
interface ProviderGroup {
key: string
label: string
@@ -58,6 +35,9 @@ interface ProviderGroup {
export function ModelsPage() {
const { t } = useTranslation()
const [models, setModels] = useState<ModelInfo[]>([])
const [providerOptions, setProviderOptions] = useState<ModelProviderOption[]>(
[],
)
const [loading, setLoading] = useState(true)
const [fetchError, setFetchError] = useState("")
@@ -67,6 +47,7 @@ export function ModelsPage() {
const [settingDefaultIndex, setSettingDefaultIndex] = useState<number | null>(
null,
)
const addDisabled = loading || providerOptions.length === 0
const fetchModels = useCallback(async () => {
try {
@@ -79,6 +60,7 @@ export function ModelsPage() {
return a.model_name.localeCompare(b.model_name)
})
setModels(sorted)
setProviderOptions(data.provider_options ?? [])
setFetchError("")
} catch (e) {
setFetchError(e instanceof Error ? e.message : t("models.loadError"))
@@ -160,7 +142,12 @@ export function ModelsPage() {
<div className="flex h-full flex-col">
<PageHeader title={t("navigation.models")}>
<div className="flex items-center gap-3">
<Button size="sm" variant="outline" onClick={() => setAddOpen(true)}>
<Button
size="sm"
variant="outline"
disabled={addDisabled}
onClick={() => setAddOpen(true)}
>
<IconPlus className="size-4" />
{t("models.add.button")}
</Button>
@@ -213,6 +200,7 @@ export function ModelsPage() {
<EditModelSheet
model={editingModel}
providerOptions={providerOptions}
open={editingModel !== null}
onClose={() => setEditingModel(null)}
onSaved={fetchModels}
@@ -220,6 +208,7 @@ export function ModelsPage() {
<AddModelSheet
open={addOpen}
providerOptions={providerOptions}
onClose={() => setAddOpen(false)}
onSaved={fetchModels}
existingModelNames={models.map((model) => model.model_name)}
@@ -2,6 +2,7 @@ import { useMemo, useState } from "react"
const PROVIDER_ICON_SLUGS: Record<string, string> = {
openai: "openai",
elevenlabs: "elevenlabs",
anthropic: "anthropic",
azure: "microsoftazure",
gemini: "googlegemini",
@@ -21,6 +22,7 @@ const PROVIDER_ICON_SLUGS: Record<string, string> = {
const PROVIDER_DOMAINS: Record<string, string> = {
openai: "openai.com",
elevenlabs: "elevenlabs.io",
anthropic: "anthropic.com",
azure: "azure.com",
gemini: "gemini.google.com",
@@ -1,11 +1,19 @@
import type { ModelProviderOption } from "@/api/models"
const PROVIDER_LABELS: Record<string, string> = {
openai: "OpenAI",
bedrock: "AWS Bedrock",
elevenlabs: "ElevenLabs ASR",
anthropic: "Anthropic",
"anthropic-messages": "Anthropic Messages",
azure: "Azure OpenAI",
gemini: "Google Gemini",
deepseek: "DeepSeek",
"coding-plan": "Alibaba Coding Plan",
"coding-plan-anthropic": "Alibaba Coding Plan (Anthropic)",
"qwen-portal": "Qwen (阿里云)",
"qwen-intl": "Qwen International",
"qwen-us": "Qwen US",
moonshot: "Moonshot (月之暗面)",
groq: "Groq",
openrouter: "OpenRouter",
@@ -15,8 +23,11 @@ const PROVIDER_LABELS: Record<string, string> = {
shengsuanyun: "ShengsuanYun (神算云)",
antigravity: "Google Code Assist",
"github-copilot": "GitHub Copilot",
"claude-cli": "Claude CLI (local)",
"codex-cli": "Codex CLI (local)",
ollama: "Ollama (local)",
lmstudio: "LM Studio (local)",
litellm: "LiteLLM",
mistral: "Mistral AI",
avian: "Avian",
vllm: "VLLM (local)",
@@ -28,6 +39,7 @@ const PROVIDER_LABELS: Record<string, string> = {
minimax: "MiniMax",
longcat: "LongCat",
modelscope: "ModelScope (魔搭社区)",
novita: "Novita AI",
}
const PROVIDER_ALIASES: Record<string, string> = {
@@ -40,6 +52,48 @@ const PROVIDER_ALIASES: Record<string, string> = {
"google-antigravity": "antigravity",
}
export const PROVIDER_PRIORITY: Record<string, number> = {
volcengine: 0,
openai: 1,
gemini: 2,
anthropic: 3,
bedrock: 4,
elevenlabs: 5,
"anthropic-messages": 6,
zhipu: 7,
deepseek: 8,
openrouter: 9,
"qwen-portal": 10,
"qwen-intl": 11,
"qwen-us": 12,
moonshot: 13,
groq: 14,
"coding-plan": 15,
"coding-plan-anthropic": 16,
"github-copilot": 17,
antigravity: 18,
nvidia: 19,
cerebras: 20,
shengsuanyun: 21,
venice: 22,
vivgrid: 23,
minimax: 24,
longcat: 25,
modelscope: 26,
mistral: 27,
avian: 28,
novita: 29,
azure: 30,
litellm: 31,
ollama: 32,
vllm: 33,
lmstudio: 34,
"claude-cli": 35,
"codex-cli": 36,
zai: 37,
mimo: 38,
}
export function getProviderKey(provider?: string): string {
const normalized = provider?.trim().toLowerCase()
if (!normalized) return "openai"
@@ -50,3 +104,45 @@ export function getProviderLabel(provider?: string): string {
const prefix = getProviderKey(provider)
return PROVIDER_LABELS[prefix] ?? prefix
}
export function findProviderOption(
provider: string | undefined,
options: ModelProviderOption[],
): ModelProviderOption | undefined {
const providerKey = getProviderKey(provider)
return options.find((option) => option.id === providerKey)
}
export function getProviderDefaultAPIBase(
provider: string | undefined,
options: ModelProviderOption[],
): string {
return findProviderOption(provider, options)?.default_api_base ?? ""
}
export function getSortedProviderOptions(
options: ModelProviderOption[],
): ModelProviderOption[] {
return [...options].sort((a, b) => {
const aPriority = PROVIDER_PRIORITY[a.id] ?? Number.MAX_SAFE_INTEGER
const bPriority = PROVIDER_PRIORITY[b.id] ?? Number.MAX_SAFE_INTEGER
if (aPriority !== bPriority) {
return aPriority - bPriority
}
return getProviderLabel(a.id).localeCompare(getProviderLabel(b.id))
})
}
export function getProviderDefaultAuthMethod(
provider: string | undefined,
options: ModelProviderOption[],
): string {
return findProviderOption(provider, options)?.default_auth_method ?? ""
}
export function isProviderAuthMethodLocked(
provider: string | undefined,
options: ModelProviderOption[],
): boolean {
return findProviderOption(provider, options)?.auth_method_locked === true
}