mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-07 02:07:47 +08:00
Measured on Bedrock: Claude Opus 4.7, 4.8 and every Claude 5 model reject
a thinking budget ("thinking.type.enabled") and a temperature, and all of
them accept adaptive thinking. The retry used to drop thinking, so with
a thinking budget set these models did not think at all. It now switches
to adaptive thinking with display "summarized"; without that setting the
models think but send no thinking text to show.
The suggested Bedrock ids had no region prefix, and the newer models only
answer through an inference profile id: "anthropic.claude-sonnet-5-5"
fails with "on-demand throughput isn't supported". Each suggested id was
called once; the Claude ones now start with "global.", Llama, Pixtral and
Nova 2 Lite with "us.". Nova Premier (end of life) and the ids without a
working profile are gone. The docs example had the same problem and a
wrong date.
647 lines
19 KiB
TypeScript
647 lines
19 KiB
TypeScript
// Types for multi-provider model configuration
|
|
|
|
export type ProviderName =
|
|
| "openai"
|
|
| "anthropic"
|
|
| "google"
|
|
| "vertexai"
|
|
| "azure"
|
|
| "bedrock"
|
|
| "ollama"
|
|
| "openrouter"
|
|
| "aihubmix"
|
|
| "deepseek"
|
|
| "siliconflow"
|
|
| "sglang"
|
|
| "gateway"
|
|
| "edgeone"
|
|
| "doubao"
|
|
| "modelscope"
|
|
| "glm"
|
|
| "qwen"
|
|
| "qiniu"
|
|
| "kimi"
|
|
| "minimax"
|
|
| "novita"
|
|
| "mimo"
|
|
| "atlascloud"
|
|
|
|
// Individual model configuration
|
|
export interface ModelConfig {
|
|
id: string // UUID for this model
|
|
modelId: string // e.g., "gpt-4o", "claude-sonnet-4-5"
|
|
validated?: boolean // Has this model been validated
|
|
validationError?: string // Error message if validation failed
|
|
validationWarning?: string // Passed, but e.g. did not call a tool
|
|
responseTime?: number // Milliseconds the last test took
|
|
}
|
|
|
|
// Provider configuration
|
|
export interface ProviderConfig {
|
|
id: string // UUID for this provider config
|
|
provider: ProviderName
|
|
name?: string // Custom display name (e.g., "OpenAI Production")
|
|
apiKey: string
|
|
baseUrl?: string
|
|
// AWS Bedrock specific fields
|
|
awsAccessKeyId?: string
|
|
awsSecretAccessKey?: string
|
|
awsRegion?: string
|
|
awsSessionToken?: string // Optional, for temporary credentials
|
|
// Vertex AI specific fields
|
|
vertexApiKey?: string // Express Mode API key
|
|
|
|
models: ModelConfig[]
|
|
validated?: boolean // Has API key been validated
|
|
}
|
|
|
|
// The complete multi-model configuration
|
|
export interface MultiModelConfig {
|
|
version: 1
|
|
providers: ProviderConfig[]
|
|
selectedModelId?: string // Currently selected model's UUID
|
|
showUnvalidatedModels?: boolean // Show models that haven't been validated
|
|
}
|
|
|
|
// Flattened model for dropdown display
|
|
export interface FlattenedModel {
|
|
id: string // Model config UUID or synthetic server ID (e.g., "server:provider:modelId")
|
|
modelId: string // Actual model ID
|
|
provider: ProviderName
|
|
providerLabel: string // Provider display name
|
|
apiKey: string
|
|
baseUrl?: string
|
|
// AWS Bedrock specific fields
|
|
awsAccessKeyId?: string
|
|
awsSecretAccessKey?: string
|
|
awsRegion?: string
|
|
awsSessionToken?: string
|
|
// Vertex AI specific fields
|
|
vertexApiKey?: string // Express Mode API key
|
|
|
|
validated?: boolean // Has this model been validated
|
|
// Source of this model config: user-defined (client) or server-defined
|
|
source?: "user" | "server"
|
|
// Whether this model is the server default (matches AI_MODEL env var)
|
|
isDefault?: boolean
|
|
// Custom env var name(s) for server models
|
|
// Can be a single string or array of strings for load balancing
|
|
apiKeyEnv?: string | string[]
|
|
baseUrlEnv?: string
|
|
}
|
|
|
|
// Providers whose server credentials live in fixed env vars
|
|
// (AWS_ACCESS_KEY_ID, GOOGLE_VERTEX_API_KEY, OLLAMA_API_KEY) with no
|
|
// apiKeyEnv redirection support — their credentials are global
|
|
export const FIXED_CRED_PROVIDERS: ProviderName[] = [
|
|
"bedrock",
|
|
"vertexai",
|
|
"ollama",
|
|
]
|
|
|
|
// Map provider names to models.dev logo names
|
|
export const PROVIDER_LOGO_MAP: Record<string, string> = {
|
|
openai: "openai",
|
|
anthropic: "anthropic",
|
|
google: "google",
|
|
azure: "azure",
|
|
bedrock: "amazon-bedrock",
|
|
openrouter: "openrouter",
|
|
aihubmix: "aihubmix",
|
|
deepseek: "deepseek",
|
|
siliconflow: "siliconflow",
|
|
sglang: "openai", // SGLang is OpenAI-compatible
|
|
gateway: "vercel",
|
|
edgeone: "tencent-cloud",
|
|
vertexai: "google",
|
|
doubao: "bytedance",
|
|
modelscope: "modelscope",
|
|
minimax: "minimax",
|
|
novita: "novita",
|
|
mimo: "xiaomi",
|
|
atlascloud: "openai",
|
|
}
|
|
|
|
/** How a provider lists its models (see lib/provider-models.ts) */
|
|
export type ModelListStyle =
|
|
| "openai"
|
|
| "anthropic"
|
|
| "google"
|
|
| "ollama"
|
|
| "openrouter"
|
|
| "aihubmix"
|
|
| "gateway"
|
|
|
|
// Provider metadata. apiKeyUrl is the page where users create a key.
|
|
// modelList is missing where a key alone cannot list the models (Bedrock,
|
|
// Vertex, Azure) or the list is not reliable (Doubao, MiniMax).
|
|
export const PROVIDER_INFO: Record<
|
|
ProviderName,
|
|
{
|
|
label: string
|
|
defaultBaseUrl?: string
|
|
apiKeyUrl?: string
|
|
modelList?: ModelListStyle
|
|
}
|
|
> = {
|
|
openai: {
|
|
label: "OpenAI",
|
|
defaultBaseUrl: "https://api.openai.com/v1",
|
|
apiKeyUrl: "https://platform.openai.com/api-keys",
|
|
modelList: "openai",
|
|
},
|
|
anthropic: {
|
|
label: "Anthropic",
|
|
defaultBaseUrl: "https://api.anthropic.com/v1",
|
|
apiKeyUrl: "https://platform.claude.com/settings/keys",
|
|
modelList: "anthropic",
|
|
},
|
|
google: {
|
|
label: "Google",
|
|
defaultBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
apiKeyUrl: "https://aistudio.google.com/apikey",
|
|
modelList: "google",
|
|
},
|
|
vertexai: { label: "Google Vertex AI" },
|
|
azure: {
|
|
label: "Azure OpenAI",
|
|
defaultBaseUrl: "https://your-resource.openai.azure.com/openai",
|
|
},
|
|
bedrock: { label: "Amazon Bedrock" },
|
|
ollama: {
|
|
label: "Ollama",
|
|
defaultBaseUrl: "https://ollama.com/api",
|
|
apiKeyUrl: "https://ollama.com/settings/keys",
|
|
modelList: "ollama",
|
|
},
|
|
openrouter: {
|
|
label: "OpenRouter",
|
|
defaultBaseUrl: "https://openrouter.ai/api/v1",
|
|
apiKeyUrl: "https://openrouter.ai/keys",
|
|
modelList: "openrouter",
|
|
},
|
|
aihubmix: {
|
|
label: "AIHubMix",
|
|
defaultBaseUrl: "https://aihubmix.com/v1",
|
|
apiKeyUrl: "https://aihubmix.com/token",
|
|
modelList: "aihubmix",
|
|
},
|
|
deepseek: {
|
|
label: "DeepSeek",
|
|
defaultBaseUrl: "https://api.deepseek.com/v1",
|
|
apiKeyUrl: "https://platform.deepseek.com/api_keys",
|
|
modelList: "openai",
|
|
},
|
|
siliconflow: {
|
|
label: "SiliconFlow",
|
|
defaultBaseUrl: "https://api.siliconflow.cn/v1",
|
|
apiKeyUrl: "https://cloud.siliconflow.cn/account/ak",
|
|
modelList: "openai",
|
|
},
|
|
sglang: {
|
|
label: "SGLang",
|
|
defaultBaseUrl: "http://127.0.0.1:8000/v1",
|
|
modelList: "openai",
|
|
},
|
|
gateway: {
|
|
label: "AI Gateway",
|
|
defaultBaseUrl: "https://ai-gateway.vercel.sh/v1/ai",
|
|
apiKeyUrl: "https://vercel.com/ai-gateway",
|
|
modelList: "gateway",
|
|
},
|
|
edgeone: { label: "EdgeOne Pages" },
|
|
doubao: {
|
|
label: "Doubao (ByteDance)",
|
|
defaultBaseUrl: "https://ark.cn-beijing.volces.com/api/v3",
|
|
apiKeyUrl:
|
|
"https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey",
|
|
},
|
|
modelscope: {
|
|
label: "ModelScope",
|
|
defaultBaseUrl: "https://api-inference.modelscope.cn/v1",
|
|
apiKeyUrl: "https://modelscope.cn/my/myaccesstoken",
|
|
modelList: "openai",
|
|
},
|
|
glm: {
|
|
label: "GLM (Zhipu)",
|
|
defaultBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
apiKeyUrl: "https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys",
|
|
modelList: "openai",
|
|
},
|
|
qwen: {
|
|
label: "Qwen (Alibaba)",
|
|
defaultBaseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
apiKeyUrl: "https://bailian.console.aliyun.com/?tab=model#/api-key",
|
|
modelList: "openai",
|
|
},
|
|
qiniu: {
|
|
label: "Qiniu",
|
|
defaultBaseUrl: "https://api.qnaigc.com/v1",
|
|
apiKeyUrl: "https://www.qiniu.com/ai/models",
|
|
modelList: "openai",
|
|
},
|
|
kimi: {
|
|
label: "Kimi (Moonshot)",
|
|
defaultBaseUrl: "https://api.moonshot.cn/v1",
|
|
apiKeyUrl: "https://platform.moonshot.cn/console/api-keys",
|
|
modelList: "openai",
|
|
},
|
|
minimax: {
|
|
label: "MiniMax",
|
|
defaultBaseUrl: "https://api.minimaxi.com/anthropic",
|
|
apiKeyUrl:
|
|
"https://platform.minimaxi.com/user-center/basic-information/interface-key",
|
|
},
|
|
novita: {
|
|
label: "Novita AI",
|
|
defaultBaseUrl: "https://api.novita.ai/openai",
|
|
apiKeyUrl: "https://novita.ai/dashboard/key",
|
|
modelList: "openai",
|
|
},
|
|
mimo: {
|
|
label: "MiMo (Xiaomi)",
|
|
defaultBaseUrl: "https://api.xiaomimimo.com/v1",
|
|
apiKeyUrl: "https://platform.xiaomimimo.com/#/console/api-keys",
|
|
modelList: "openai",
|
|
},
|
|
atlascloud: {
|
|
label: "Atlas Cloud",
|
|
defaultBaseUrl: "https://api.atlascloud.ai/v1",
|
|
apiKeyUrl: "https://www.atlascloud.ai/console/api-keys",
|
|
modelList: "openai",
|
|
},
|
|
}
|
|
|
|
// Suggested models per provider for quick add
|
|
export const SUGGESTED_MODELS: Partial<Record<ProviderName, string[]>> = {
|
|
openai: [
|
|
"gpt-6.1-sol",
|
|
"gpt-6-sol",
|
|
"gpt-6-luna",
|
|
"gpt-6-astra",
|
|
"gpt-5.5-pro",
|
|
"gpt-5.5",
|
|
"gpt-5.4-pro",
|
|
"gpt-5.4",
|
|
"gpt-5.4-mini",
|
|
"gpt-5.4-nano",
|
|
"gpt-5-codex-mini",
|
|
"gpt-4.1",
|
|
"gpt-4.1-mini",
|
|
"gpt-4o",
|
|
"gpt-4o-mini",
|
|
],
|
|
anthropic: [
|
|
// Claude 5 series (latest)
|
|
"claude-opus-5-5",
|
|
"claude-sonnet-5-5",
|
|
"claude-fable-5-1",
|
|
"claude-opus-5",
|
|
"claude-sonnet-5",
|
|
// Claude 4.8 / 4.7 / 4.6 series (dateless pinned IDs)
|
|
"claude-opus-4-8",
|
|
"claude-sonnet-4-6",
|
|
"claude-haiku-4-5",
|
|
"claude-opus-4-7",
|
|
"claude-opus-4-6",
|
|
// Claude 4.5 series
|
|
"claude-sonnet-4-5-20250929",
|
|
"claude-opus-4-5-20251101",
|
|
// Claude 3.7 series
|
|
"claude-3-7-sonnet-20250219",
|
|
// Claude 3.5 series
|
|
"claude-3-5-sonnet-20241022",
|
|
"claude-3-5-haiku-20241022",
|
|
],
|
|
google: [
|
|
// Gemini 3 series
|
|
"gemini-3.1-pro",
|
|
"gemini-3.5-flash",
|
|
"gemini-3-flash",
|
|
"gemini-3.1-flash-lite",
|
|
// Gemini 2.5 series
|
|
"gemini-2.5-pro",
|
|
"gemini-2.5-flash",
|
|
"gemini-2.5-flash-lite",
|
|
],
|
|
vertexai: [
|
|
// Gemini 3 series
|
|
"gemini-3.1-pro-preview",
|
|
"gemini-3.5-flash",
|
|
"gemini-3-flash-preview",
|
|
"gemini-3.1-flash-lite",
|
|
// Gemini 2.5 series
|
|
"gemini-2.5-pro",
|
|
"gemini-2.5-flash",
|
|
"gemini-2.5-flash-lite",
|
|
],
|
|
azure: [
|
|
"gpt-5.5",
|
|
"gpt-5.4",
|
|
"gpt-5.1",
|
|
"gpt-5",
|
|
"gpt-5-mini",
|
|
"gpt-4.1",
|
|
"gpt-4o",
|
|
"gpt-4o-mini",
|
|
"o3",
|
|
"o4-mini",
|
|
],
|
|
// Newer models only answer through an inference profile id (the region
|
|
// prefix). Each id here was called once on 2026-10-04.
|
|
bedrock: [
|
|
// Anthropic Claude ("global." works from any region)
|
|
"global.anthropic.claude-opus-5-5",
|
|
"global.anthropic.claude-sonnet-5-5",
|
|
"global.anthropic.claude-fable-5-1",
|
|
"global.anthropic.claude-opus-5",
|
|
"global.anthropic.claude-sonnet-5",
|
|
"global.anthropic.claude-opus-4-8",
|
|
"global.anthropic.claude-opus-4-7",
|
|
"global.anthropic.claude-sonnet-4-6",
|
|
"global.anthropic.claude-opus-4-6-v1",
|
|
"global.anthropic.claude-opus-4-5-20251101-v1:0",
|
|
"global.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
|
"global.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
"global.anthropic.claude-sonnet-4-20250514-v1:0",
|
|
// Amazon Nova
|
|
"us.amazon.nova-2-lite-v1:0",
|
|
"amazon.nova-pro-v1:0",
|
|
"amazon.nova-lite-v1:0",
|
|
"amazon.nova-micro-v1:0",
|
|
// Meta Llama
|
|
"us.meta.llama4-maverick-17b-instruct-v1:0",
|
|
"us.meta.llama4-scout-17b-instruct-v1:0",
|
|
"us.meta.llama3-3-70b-instruct-v1:0",
|
|
// Mistral
|
|
"mistral.mistral-large-3-675b-instruct",
|
|
"us.mistral.pixtral-large-2502-v1:0",
|
|
],
|
|
openrouter: [
|
|
// Anthropic
|
|
"anthropic/claude-opus-5.5",
|
|
"anthropic/claude-sonnet-5.5",
|
|
"anthropic/claude-fable-5.1",
|
|
"anthropic/claude-opus-4.8",
|
|
"anthropic/claude-sonnet-4.6",
|
|
"anthropic/claude-haiku-4.5",
|
|
// OpenAI
|
|
"openai/gpt-6.1-sol",
|
|
"openai/gpt-6-luna",
|
|
"openai/gpt-5.5",
|
|
"openai/gpt-5.4",
|
|
"openai/gpt-5.4-mini",
|
|
"openai/gpt-4o-mini",
|
|
// Google
|
|
"google/gemini-3.1-pro-preview",
|
|
"google/gemini-3.5-flash",
|
|
"google/gemini-2.5-flash-lite",
|
|
// xAI
|
|
"x-ai/grok-4.3",
|
|
// Meta Llama
|
|
"meta-llama/llama-4-maverick",
|
|
"meta-llama/llama-4-scout",
|
|
"meta-llama/llama-3.3-70b-instruct",
|
|
// DeepSeek
|
|
"deepseek/deepseek-v4-pro",
|
|
"deepseek/deepseek-v3.2",
|
|
// Qwen
|
|
"qwen/qwen3.7-max",
|
|
"qwen/qwen3-coder",
|
|
// MiniMax
|
|
"minimax/minimax-m3",
|
|
],
|
|
aihubmix: [
|
|
// Fallback list. The settings UI loads the live model list from AIHubMix when available.
|
|
// Anthropic Claude
|
|
"claude-fable-5",
|
|
"claude-opus-4-8",
|
|
"claude-sonnet-4-6",
|
|
// OpenAI
|
|
"gpt-5.5",
|
|
"gpt-5.5-pro",
|
|
"gpt-5.4",
|
|
// Google Gemini
|
|
"gemini-3.5-flash",
|
|
"gemini-3.1-pro-preview",
|
|
"gemini-3-flash-preview",
|
|
// DeepSeek
|
|
"deepseek-v4-pro",
|
|
"deepseek-v4-flash",
|
|
// Qwen
|
|
"qwen3.7-max",
|
|
"qwen3-coder-next",
|
|
// Z.ai
|
|
"glm-5.1",
|
|
// Moonshot AI
|
|
"kimi-k2.6",
|
|
// MiniMax
|
|
"minimax-m3",
|
|
// xAI
|
|
"grok-4.3",
|
|
// Baidu
|
|
"ernie-5.1",
|
|
// Mistral
|
|
"mistral-large-3",
|
|
// Meta
|
|
"llama-4-maverick",
|
|
],
|
|
deepseek: [
|
|
"deepseek-v4-pro",
|
|
"deepseek-v4-flash",
|
|
"deepseek-chat",
|
|
"deepseek-reasoner",
|
|
],
|
|
siliconflow: [
|
|
// DeepSeek
|
|
"deepseek-ai/DeepSeek-V4-Pro",
|
|
"deepseek-ai/DeepSeek-V4-Flash",
|
|
"deepseek-ai/DeepSeek-V3.2",
|
|
// MiniMax
|
|
"MiniMaxAI/MiniMax-M3",
|
|
// Moonshot
|
|
"moonshotai/Kimi-K2.6",
|
|
// Z.ai
|
|
"zai-org/GLM-5",
|
|
// Qwen
|
|
"Qwen/Qwen3.6-35B-A3B",
|
|
"Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
|
"Qwen/Qwen3-30B-A3B-Instruct-2507",
|
|
"Qwen/Qwen3-VL-32B-Instruct",
|
|
// OpenAI open-weights
|
|
"openai/gpt-oss-120b",
|
|
],
|
|
sglang: [
|
|
// SGLang is OpenAI-compatible, models depend on deployment
|
|
"default",
|
|
],
|
|
gateway: [
|
|
"anthropic/claude-opus-5.5",
|
|
"anthropic/claude-sonnet-5.5",
|
|
"openai/gpt-6.1-sol",
|
|
"openai/gpt-6-luna",
|
|
"openai/gpt-5.5",
|
|
"anthropic/claude-opus-4.7",
|
|
"google/gemini-3.1-pro-preview",
|
|
"xai/grok-4.3",
|
|
"anthropic/claude-sonnet-4.6",
|
|
"anthropic/claude-haiku-4.5",
|
|
"openai/gpt-5.4-mini",
|
|
],
|
|
edgeone: ["@tx/deepseek-ai/deepseek-v32"],
|
|
doubao: [
|
|
// ByteDance Doubao models (Volcengine Ark IDs use dash form)
|
|
"doubao-seed-2-0-pro-260215",
|
|
"doubao-seed-2-0-lite-260428",
|
|
"doubao-seed-2-0-mini-260428",
|
|
"doubao-seed-1-8-251228",
|
|
"doubao-seed-1-6-251015",
|
|
"doubao-seed-1-6-flash-250828",
|
|
"doubao-seed-1-6-vision-250815",
|
|
"doubao-1-5-pro-32k-250115",
|
|
"doubao-1-5-lite-32k-250115",
|
|
],
|
|
modelscope: [
|
|
// DeepSeek
|
|
"deepseek-ai/DeepSeek-V4-Pro",
|
|
"deepseek-ai/DeepSeek-V3.2",
|
|
"deepseek-ai/DeepSeek-R1-0528",
|
|
"deepseek-ai/DeepSeek-R1",
|
|
// Qwen
|
|
"Qwen/Qwen3-235B-A22B-Instruct-2507",
|
|
"Qwen/Qwen3-VL-235B-A22B-Instruct",
|
|
"Qwen/Qwen3-Coder-30B-A3B-Instruct",
|
|
"Qwen/Qwen3-32B",
|
|
"Qwen/Qwen2.5-72B-Instruct",
|
|
],
|
|
minimax: [
|
|
// MiniMax models (Anthropic-compatible API)
|
|
"MiniMax-M3",
|
|
"MiniMax-M2.7",
|
|
"MiniMax-M2.7-highspeed",
|
|
"MiniMax-M2.5",
|
|
],
|
|
novita: [
|
|
// Novita AI models (OpenAI-compatible API)
|
|
"minimax/minimax-m3",
|
|
"deepseek/deepseek-v4-pro",
|
|
"zai-org/glm-5.1",
|
|
"moonshotai/kimi-k2.6",
|
|
"deepseek/deepseek-v4-flash",
|
|
],
|
|
mimo: ["mimo-v2.5-pro", "mimo-v2.5"],
|
|
atlascloud: ["qwen/qwen3.5-flash", "deepseek-ai/deepseek-v4-pro"],
|
|
}
|
|
|
|
// Helper to generate UUID
|
|
export function generateId(): string {
|
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
|
}
|
|
|
|
// Create empty config
|
|
export function createEmptyConfig(): MultiModelConfig {
|
|
return {
|
|
version: 1,
|
|
providers: [],
|
|
selectedModelId: undefined,
|
|
}
|
|
}
|
|
|
|
// Create new provider config
|
|
export function createProviderConfig(provider: ProviderName): ProviderConfig {
|
|
return {
|
|
id: generateId(),
|
|
provider,
|
|
apiKey: "",
|
|
baseUrl: PROVIDER_INFO[provider].defaultBaseUrl,
|
|
models: [],
|
|
validated: false,
|
|
}
|
|
}
|
|
|
|
// Create new model config
|
|
export function createModelConfig(modelId: string): ModelConfig {
|
|
return {
|
|
id: generateId(),
|
|
modelId,
|
|
}
|
|
}
|
|
|
|
// Get all models as flattened list for dropdown (user-defined only)
|
|
export function flattenModels(config: MultiModelConfig): FlattenedModel[] {
|
|
const models: FlattenedModel[] = []
|
|
|
|
for (const provider of config.providers) {
|
|
// Use custom name if provided, otherwise use default provider label
|
|
const providerLabel =
|
|
provider.name || PROVIDER_INFO[provider.provider].label
|
|
|
|
for (const model of provider.models) {
|
|
models.push({
|
|
id: model.id,
|
|
modelId: model.modelId,
|
|
provider: provider.provider,
|
|
providerLabel,
|
|
apiKey: provider.apiKey,
|
|
baseUrl: provider.baseUrl,
|
|
// AWS Bedrock fields
|
|
awsAccessKeyId: provider.awsAccessKeyId,
|
|
awsSecretAccessKey: provider.awsSecretAccessKey,
|
|
awsRegion: provider.awsRegion,
|
|
awsSessionToken: provider.awsSessionToken,
|
|
// Vertex AI fields
|
|
vertexApiKey: provider.vertexApiKey,
|
|
|
|
validated: model.validated,
|
|
source: "user",
|
|
isDefault: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
return models
|
|
}
|
|
|
|
// Find model by ID
|
|
export function findModelById(
|
|
config: MultiModelConfig,
|
|
modelId: string,
|
|
): FlattenedModel | undefined {
|
|
return flattenModels(config).find((m) => m.id === modelId)
|
|
}
|
|
|
|
/**
|
|
* A base URL the way the SDKs expect it: no spaces, no trailing slash, and
|
|
* no endpoint path users often paste along (".../v1/chat/completions"),
|
|
* which the SDK would append a second time.
|
|
*/
|
|
export function normalizeBaseUrl(url: string): string {
|
|
return url
|
|
.trim()
|
|
.replace(/\/+$/, "")
|
|
.replace(/\/(?:chat\/completions|completions|messages|responses)$/, "")
|
|
}
|
|
|
|
/** Where a chat request goes for a base URL, or null when the SDK decides */
|
|
export function chatRequestUrl(
|
|
provider: ProviderName,
|
|
baseUrl: string,
|
|
): string | null {
|
|
const url = normalizeBaseUrl(baseUrl)
|
|
if (!url) return null
|
|
if (provider === "anthropic") return `${url}/messages`
|
|
// These SDKs build their own paths (or, for MiniMax, pick the protocol
|
|
// from the URL)
|
|
const ownPaths: ProviderName[] = [
|
|
"google",
|
|
"vertexai",
|
|
"azure",
|
|
"bedrock",
|
|
"ollama",
|
|
"gateway",
|
|
"minimax",
|
|
"edgeone",
|
|
]
|
|
return ownPaths.includes(provider) ? null : `${url}/chat/completions`
|
|
}
|