diff --git a/app/[lang]/admin/models-section.tsx b/app/[lang]/admin/models-section.tsx index 242d3ab8..e15fdef2 100644 --- a/app/[lang]/admin/models-section.tsx +++ b/app/[lang]/admin/models-section.tsx @@ -33,14 +33,9 @@ import { import { Switch } from "@/components/ui/switch" import { useDictionary } from "@/hooks/use-dictionary" import { formatMessage } from "@/lib/i18n/utils" +import { PROVIDERS, type ProviderName } from "@/lib/providers" import { STORAGE_KEYS } from "@/lib/storage" -import { - FIXED_CRED_PROVIDERS, - generateId, - PROVIDER_INFO, - type ProviderName, - SUGGESTED_MODELS, -} from "@/lib/types/model-config" +import { generateId, SUGGESTED_MODELS } from "@/lib/types/model-config" import { cn } from "@/lib/utils" import { type AdminProvider, @@ -72,7 +67,7 @@ function ProviderDetail({ Record >({}) - const info = PROVIDER_INFO[provider.provider] + const info = PROVIDERS[provider.provider] const suggestions = (SUGGESTED_MODELS[provider.provider] || []).filter( (m) => !provider.models.includes(m), ) @@ -500,7 +495,7 @@ export function ModelsSection({ > - {p.name || PROVIDER_INFO[p.provider].label} + {p.name || PROVIDERS[p.provider].label} {p.isDefault && ( - {(Object.keys(PROVIDER_INFO) as ProviderName[]).map( + {(Object.keys(PROVIDERS) as ProviderName[]).map( (p) => { // Global-credential providers already in // the env config can't be added here — // panel credentials would override theirs const envBlocked = - FIXED_CRED_PROVIDERS.includes(p) && + !!PROVIDERS[p].fixedCredentials && envProviders.some( (e) => e.provider === p, ) @@ -544,7 +539,7 @@ export function ModelsSection({ >
- {PROVIDER_INFO[p].label} + {PROVIDERS[p].label} {envBlocked && ( { diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index e63e6f63..73e36198 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -47,6 +47,7 @@ import { resolveMaxOutputTokens, withOutputTokenLimitFallback, } from "@/lib/output-token-limit" +import { PROVIDERS, type ProviderName } from "@/lib/providers" import { type FlattenedServerModel, findServerModelById, @@ -173,14 +174,17 @@ async function handleChatRequest(req: Request): Promise { // A server model's provider comes from its config: for one set up in // the admin panel the header holds the provider name's slug. Without // either, the server's own AI_PROVIDER. - const isEdgeOne = - (serverModelConfig.provider || provider || getServerProvider()) === - "edgeone" - - // EdgeOne is this deployment's own function, whatever URL the request - // names: another host would get the user's EdgeOne cookies, and the - // quota counts it. Absolute, as the SDK needs. - if (isEdgeOne) baseUrl = edgeOneEndpoint(req) + const requestedProvider = + serverModelConfig.provider || provider || getServerProvider() + // A provider without credentials (EdgeOne) is this deployment's own + // function, whatever URL the request names: another host would get the + // user's EdgeOne cookies, and the quota counts it. Absolute, as the SDK + // needs. + const onSiteFunction = + !!requestedProvider && + Object.hasOwn(PROVIDERS, requestedProvider) && + PROVIDERS[requestedProvider as ProviderName].credential === "none" + if (onSiteFunction) baseUrl = edgeOneEndpoint(req) // Same rule as validate-model: with ALLOW_PRIVATE_URLS=false a request may // not point the server at a private or internal address @@ -195,12 +199,13 @@ async function handleChatRequest(req: Request): Promise { const cookieHeader = req.headers.get("cookie") const clientOverrides = { - // Server model provider takes precedence over client header; EdgeOne - // named only in AI_PROVIDER is named here, for its own base URL + // Server model provider takes precedence over client header; the + // site's function named only in AI_PROVIDER is named here, for its + // own base URL provider: serverModelConfig.provider || provider || - (isEdgeOne ? "edgeone" : null), + (onSiteFunction ? requestedProvider : null), baseUrl, apiKey: req.headers.get("x-ai-api-key"), // A server model runs the model it was configured with, whatever the header says @@ -216,7 +221,7 @@ async function handleChatRequest(req: Request): Promise { vertexApiKey: req.headers.get("x-vertex-api-key"), // Pass cookies for EdgeOne Pages authentication, and the access code, // which the EdgeOne function checks too - ...(isEdgeOne && { + ...(onSiteFunction && { headers: { ...(cookieHeader && { cookie: cookieHeader }), "x-access-code": req.headers.get("x-access-code") || "", @@ -420,7 +425,7 @@ ${userInputText} // Also merge for OpenAI-compatible providers with custom base URLs (e.g. vLLM, LMStudio) // because open-source model chat templates (Qwen, Llama, etc.) typically reject multiple system messages const isCustomOpenAIEndpoint = - resolvedProvider === "openai" && + PROVIDERS[resolvedProvider].sdk === "openai" && !!( baseUrl || process.env.OPENAI_BASE_URL || diff --git a/components/model-config-dialog.tsx b/components/model-config-dialog.tsx index 98012c02..2998ed18 100644 --- a/components/model-config-dialog.tsx +++ b/components/model-config-dialog.tsx @@ -55,13 +55,14 @@ import { getApiEndpoint } from "@/lib/base-path" import { formatMessage } from "@/lib/i18n/utils" import type { ListedModel } from "@/lib/provider-models" import { hasCredentials } from "@/lib/provider-setup" +import { PROVIDERS } from "@/lib/providers" import { STORAGE_KEYS } from "@/lib/storage" import type { ModelConfig, ProviderConfig, ProviderName, } from "@/lib/types/model-config" -import { PROVIDER_INFO, SUGGESTED_MODELS } from "@/lib/types/model-config" +import { SUGGESTED_MODELS } from "@/lib/types/model-config" import { cn } from "@/lib/utils" import { useUiStore } from "@/stores/ui-store" @@ -300,7 +301,7 @@ export function ModelConfigDialog({ const errorHints = (p: ProviderConfig): Record => ({ ...(dict.errors.llm as Record), invalid_api_key: - p.provider === "bedrock" && !p.apiKey + PROVIDERS[p.provider].credential === "aws" && !p.apiKey ? t.awsKeysRejected : t.keyRejected, model_not_found: t.modelNotFound, @@ -530,9 +531,9 @@ export function ModelConfigDialog({ const run = (latestTestRun.get(selectedProviderId) ?? 0) + 1 latestTestRun.set(selectedProviderId, run) - // For EdgeOne, construct baseUrl from current origin + // The site's own function (EdgeOne): its URL is this origin's const baseUrl = - selectedProvider.provider === "edgeone" + PROVIDERS[selectedProvider.provider].credential === "none" ? `${window.location.origin}/api/edgeai` : selectedProvider.baseUrl const hints = errorHints(selectedProvider) @@ -741,7 +742,7 @@ export function ModelConfigDialog({ } const providerName = (p: ProviderConfig) => - p.name || PROVIDER_INFO[p.provider].label + p.name || PROVIDERS[p.provider].label const renderModelRow = (model: ModelConfig) => { if (!selectedProvider) return null @@ -922,11 +923,13 @@ export function ModelConfigDialog({ : undefined const canTest = credentialsDone && models.length > 0 const testing = validationStatus === "validating" + const def = PROVIDERS[provider.provider] + // A key-only provider without a default URL (Azure) needs its URL too const testBlocker = !credentialsDone - ? provider.provider === "bedrock" + ? def.credential === "aws" ? t.needAws - : provider.provider === "azure" - ? t.needAzure + : def.credential === "apiKey" && !def.defaultBaseUrl + ? t.needUrl : t.needKey : models.length === 0 ? t.needModel @@ -984,7 +987,7 @@ export function ModelConfigDialog({ done={credentialsDone && !keyRejected} title={t.stepConnect} > - {provider.provider === "edgeone" ? ( + {def.credential === "none" ? (

{t.edgeoneNoKey}

@@ -1027,7 +1030,7 @@ export function ModelConfigDialog({ done={models.length > 0} title={t.stepModels} action={ - PROVIDER_INFO[provider.provider].modelList && ( + def.modelList && (
) - // EdgeOne needs no credentials - if (provider === "edgeone") return null + // A provider without credentials (EdgeOne) has no fields + if (info.credential === "none") return null if (settingsLayout) { return (
- {provider === "bedrock" ? ( + {info.credential === "aws" ? ( <> {/* Buttons, not radios: arrow keys would empty secrets without a deliberate press */} @@ -393,7 +386,7 @@ export function ProviderCredentialsFields({ )} {regionField} - ) : provider === "vertexai" ? ( + ) : info.credential === "vertexKey" ? (